JsonSerializer.CreateDefault().Populate(..) 重置我的值

JsonSerializer.CreateDefault().Populate(..) resets my values

我关注class:

public class MainClass
{
    public static MainClass[] array = new MainClass[1]
    {
        new MainClass
        {
            subClass = new SubClass[2]
            {
                new SubClass
                {
                    variable1 = "my value"
                },
                new SubClass
                {
                    variable1 = "my value"
                }
            }
        }
    };

    public SubClass[] subClass;
    [DataContract]
    public  class SubClass
    {
        public string variable1 = "default value";
        [DataMember] // because only variable2 should be saved in json
        public string variable2 = "default value";
    }
}

我保存如下:

File.WriteAllText("data.txt", JsonConvert.SerializeObject(new
{
    MainClass.array
}, new JsonSerializerSettings { Formatting = Formatting.Indented }));

data.txt:

{
  "array": [
    {
      "subClass": [
        {
          "variable2": "value from json"
        },
        {
          "variable2": "value from json"
        }
      ]
    }
  ]
}

然后我像这样反序列化并填充我的对象:

JObject json = JObject.Parse(File.ReadAllText("data.txt"));
if (json["array"] != null)
{
    for (int i = 0, len = json["array"].Count(); i < len; i++)
    {
        using (var sr = json["array"][i].CreateReader())
        {
            JsonSerializer.CreateDefault().Populate(sr, MainClass.array[i]);
        }
    }
}

然而,当我打印以下变量时:

Console.WriteLine(MainClass.array[0].subClass[0].variable1);
Console.WriteLine(MainClass.array[0].subClass[0].variable2);
Console.WriteLine(MainClass.array[0].subClass[1].variable1);
Console.WriteLine(MainClass.array[0].subClass[1].variable2);

那么它的输出是:

default value
value from json
default value
value from json

但应该 "my value" 而不是 "default value" 因为那是我在创建 class 实例时使用的,而 JsonSerializer 应该只使用 [=38] 中的值填充对象=].

如何在不重置 json 中未包含的属性的情况下正确填充整个对象?

看起来好像JsonSerializer.Populate() lacks the MergeArrayHandling setting that is available for JObject.Merge()。通过测试我发现:

  • 填充数组成员或某些其他类型的只读集合似乎像 MergeArrayHandling.Replace 一样工作。

    这就是您遇到的问题 -- 现有数组和其中的所有项目都被丢弃并替换为包含具有默认值的新建项目的新数组。相反,您需要 MergeArrayHandling.Merge将数组项合并在一起,按索引匹配。

  • 填充 read/write 集合的成员,例如 List<T> 似乎像 MergeArrayHandling.Concat.

  • 一样工作

request an enhancement 认为 Populate() 支持此设置似乎是合理的 -- 虽然我不知道实现起来有多容易。 Populate() 的文档至少应该解释这种行为。

与此同时,这里有一个自定义 JsonConverter,其中包含模拟 MergeArrayHandling.Merge 行为的必要逻辑:

public class ArrayMergeConverter<T> : ArrayMergeConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType.IsArray && objectType.GetArrayRank() == 1 && objectType.GetElementType() == typeof(T);
    }
}

public class ArrayMergeConverter : JsonConverter
{
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (!objectType.IsArray)
            throw new JsonSerializationException(string.Format("Non-array type {0} not supported.", objectType));
        var contract = (JsonArrayContract)serializer.ContractResolver.ResolveContract(objectType);
        if (contract.IsMultidimensionalArray)
            throw new JsonSerializationException("Multidimensional arrays not supported.");
        if (reader.TokenType == JsonToken.Null)
            return null;
        if (reader.TokenType != JsonToken.StartArray)
            throw new JsonSerializationException(string.Format("Invalid start token: {0}", reader.TokenType));
        var itemType = contract.CollectionItemType;
        var existingList = existingValue as IList;
        IList list = new List<object>();
        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.Comment:
                    break;
                case JsonToken.Null:
                    list.Add(null);
                    break;
                case JsonToken.EndArray:
                    var array = Array.CreateInstance(itemType, list.Count);
                    list.CopyTo(array, 0);
                    return array;
                default:
                    // Add item to list
                    var existingItem = existingList != null && list.Count < existingList.Count ? existingList[list.Count] : null;
                    if (existingItem == null)
                    {
                        existingItem = serializer.Deserialize(reader, itemType);
                    }
                    else
                    {
                        serializer.Populate(reader, existingItem);
                    }
                    list.Add(existingItem);
                    break;
            }
        }
        // Should not come here.
        throw new JsonSerializationException("Unclosed array at path: " + reader.Path);
    }

    public override bool CanWrite { get { return false; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override bool CanConvert(Type objectType)
    {
        throw new NotImplementedException();
    }
}

然后将转换器添加到您的 subClass 成员中,如下所示:

    [JsonConverter(typeof(ArrayMergeConverter))]
    public SubClass[] subClass;

或者,如果您不想向您的数据模型添加 Json.NET 属性,您可以在序列化程序设置中添加它:

    var settings = new JsonSerializerSettings
    {
        Converters = new[] { new ArrayMergeConverter<MainClass.SubClass>() },
    };
    JsonSerializer.CreateDefault(settings).Populate(sr, MainClass.array[i]);

该转换器专为数组设计,但可以轻松地为 read/write 集合(例如 List<T>.

创建类似的转换器