Newtonsoft呼吁Getter反序列化属性

时间:2019-11-06 13:46:32

标签: c# unity3d serialization json.net deserialization

我创建了一个简单的类模型(AnchorMetaData),如下所示,其中有两个项目。一个是列表字段(Vector3),由于它的制作方式而无法序列化,因此我为类(SerializableVector3)创建了可以序列化和反序列化的属性。我希望与Newtonsoft一起使用此属性来保存/加载模型。

该类保存得很好,但是,当我尝试从JSON反序列化模型时,它会调用AttachedTaskLocations属性的getter而不是setter。这使得要初始化的字段为空。

我仅通过使用日志消息并设置一些断点来注意到这一点。反序列化时,它从不调用setter。这很奇怪,因为它应该起作用。

另一个奇怪的行为是,它确实使用文件中的正确值在SerializableVector3的x,y,z的设置器上暂停。这太奇怪了。

我正在使用Unity 2019.1.14,但这也可以在没有它的情况下工作,只需将向量列表更改为您拥有的东西即可。

当我加载显示的JSON文件时,该文件是通过序列化AnchorMetaData创建的,它在attachedTaskLocations中有零个项目。为什么会这样呢?为什么二传手没被叫到?


我为保存/加载Vector3创建的类称为SerializableVector3。 我要保存/加载的班级:

[Serializable]
public class AnchorMetaData
{
    // Cannot serialize this.
    [JsonIgnore]
    public List<Vector3> attachedTaskLocations = new List<Vector3>();

    /// <summary>
    /// This property servers as an interface for JSON de-/serialization.
    /// It uses a class that can be serialized by Newtonsoft.
    /// Should not be used in code except for serialization purposes.
    /// </summary>
    [JsonProperty("AttachedTaskLocations")]
    public List<SerializableVector3> AttachedTaskLocations
    {
        get
        {
            Debug.Log("Writing serialized vector.");
            return attachedTaskLocations
                .Select(vector3 => new SerializableVector3(vector3))
                .ToList();
        }
        set
        {
            Debug.Log("Loading serialized vector.");
            attachedTaskLocations = value
                .Select(sVector3 => new Vector3(sVector3.x, sVector3.y, sVector3.z))
                .ToList();
        }
    }

}

序列化JSON:

{
  "AttachedTaskLocations": [
      {
        "x": 1.0,
        "y": 1.0,
        "z": 1.0
      },
      {
        "x": 1E+12,
        "y": 2.0,
        "z": 3.0
      },
      {
        "x": 0.0,
        "y": 0.0,
        "z": 0.0
      }
    ]
  }

在反序列化时,当断点击中吸气剂时堆积。 Getter call stack

1 个答案:

答案 0 :(得分:3)

反序列化后AttachedTaskLocations为空的原因有两个:

  1. 默认情况下,Json.Net将在反序列化期间重用现有的对象值,而不是创建新的对象值。因此,对于诸如AttachedTaskLocations列表之类的属性,它将首先调用getter,然后查找现有值,然后它将继续从JSON填充它。
  2. 您的AttachedTaskLocations的getter每次不会返回相同的实例;它总是从attachedTaskLocations后备字段创建一个新实例。

所以看来是这样的:

  1. 序列化程序调用{​​{1}} getter,它返回一个新的空列表。
  2. 序列化程序从JSON填充该列表。
  3. 填充的列表将被丢弃(序列化程序假定AttachedTaskLocations实例已经具有对该列表的引用,因此它永远不会调用setter)。
  4. 以后您访问AnchorMetaData获取器时,它将再次返回一个新的空列表。

您可以通过将ObjectCreationHandling设置设置为AttachedTaskLocations来更改串行器的行为。仅此一项更改似乎就可以解决问题in my testing

但是,我认为您在这里遇到了很多麻烦,以便在有更好的解决方案时使用Replace正确地序列化/反序列化:使用自定义JsonConverter。这是转换器所需的代码。不算什么:

Vector3

有了此转换器,您就可以完全摆脱public class Vector3Converter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(Vector3); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.StartObject) { JObject obj = JObject.Load(reader); return new Vector3((float)obj["x"], (float)obj["y"], (float)obj["z"]); } if (reader.TokenType == JsonToken.Null) { return null; } throw new JsonException("Unexpected token type: " + reader.TokenType); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value != null) { Vector3 vector = (Vector3)value; JObject obj = new JObject( new JProperty("x", vector.x), new JProperty("y", vector.y), new JProperty("z", vector.z) ); obj.WriteTo(writer); } else { JValue.CreateNull().WriteTo(writer); } } } 类,并且可以将SerializableVector3类简化如下:

AnchorMetaData

要使用转换器,您可以:

  • 将其传递给public class AnchorMetaData { [JsonProperty("AttachedTaskLocations")] public List<Vector3> AttachedTaskLocations { get; set; } = new List<Vector3>(); } / JsonConvert.SerializeObject()方法;
  • 将其添加到DeserializeObject()上的Converters集合中,并将设置传递到JsonSerializerSettings / JsonConvert.SerializeObject(),或者
  • 直接在DeserializeObject()实例上将其添加到Converters集合中。

例如:

JsonSerializer

往返演示:https://dotnetfiddle.net/jmYIq9


如果您无权访问序列化程序(很难从您的问题中得知您是在使用自己的代码进行序列化/反序列化,还是某些第三方组件正在处理该序列化程序),则可以使用另一种使用方式转换器通过属性。对于var settings = new JsonSerializerSettings(); settings.Converters.Add(new Vector3Converter()); var metaData = JsonConvert.DeserializeObject<AnchorMetaData>(json, settings); 之类的列表属性,您可以像这样在AttachedTaskLocations属性中指定ItemConverterType

[JsonProperty]

如果您具有单个实例属性,则应使用 [JsonProperty("AttachedTaskLocations", ItemConverterType = typeof(Vector3Converter))] public List<Vector3> AttachedTaskLocations { get; set; } = new List<Vector3>(); 属性,如下所示:

[JsonConverter]

提琴:https://dotnetfiddle.net/yxwqDL