首页 > 解决方案 > Newtonsoft 调用 Getter 反序列化属性

问题描述

我创建了一个简单的类模型 ( AnchorMetaData),如下所示,其中有两个项目。一个是列表字段Vector3SerializableVector3我希望将此属性与 Newtonsoft 一起使用来保存/加载模型。

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

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

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

我正在使用 Unity 2019.1.14,但没有它也应该可以工作,只需将矢量列表更改为您拥有的东西。

当我加载显示的 JSON 文件时,该文件是通过序列化创建的,AnchorMetaData它在attachedTaskLocations. 为什么会这样?为什么 setter 没有被调用?


我为保存/加载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 时堆栈。 Getter 调用堆栈

标签: c#unity3dserializationjson.netdeserialization

解决方案


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

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

所以似乎正在发生的是:

  1. 序列化程序调用AttachedTaskLocationsgetter,它返回一个新的空列表。
  2. 序列化程序从 JSON 填充该列表。
  3. 填充的列表被丢弃(序列化程序假定AnchorMetaData实例已经具有对列表的引用,因此它永远不会调用设置器)。
  4. 当您稍后访问AttachedTaskLocationsgetter 时,它会再次返回一个新的空列表。

您可以通过将ObjectCreationHandling设置设置为来更改序列化程序的行为Replace。仅此更改似乎就解决了我的测试中的问题。

Vector3但是,我认为当有更好的解决方案时,您在这里跳过一堆箍来正确序列化/反序列化:使用自定义JsonConverter. 这是转换器所需的代码。没那么多:

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

例如:

var settings = new JsonSerializerSettings();
settings.Converters.Add(new Vector3Converter());
var metaData = JsonConvert.DeserializeObject<AnchorMetaData>(json, settings);

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


如果您无权访问序列化程序(很难从您的问题中判断您是在自己的代码中进行序列化/反序列化,还是某些第三方组件正在处理),那么使用转换器的另一种方法是通过属性。对于像这样的列表属性,AttachedTaskLocations您可以ItemConverterType在属性中指定权限,[JsonProperty]如下所示:

    [JsonProperty("AttachedTaskLocations", ItemConverterType = typeof(Vector3Converter))]
    public List<Vector3> AttachedTaskLocations { get; set; } = new List<Vector3>();

如果您有一个实例属性,那么您将使用一个[JsonConverter]属性,如下所示:

    [JsonConverter(typeof(Vector3Converter))]
    public Vector3 SingleVector { get; set; }

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


推荐阅读