2016-11-04 80 views
2

我有以下类:JsonSerializer.CreateDefault()填充(..)重置我的价值观

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 

而不是“默认值”应该有“我的价值”,因为那是什么我在创建类的实例时使用了JsonSerializer,应该只使用来自json的值填充对象。

如何正确填充整个对象,而不重置其未包含在json中的属性?

回答

0

看起来好像JsonSerializer.Populate()缺少可用于JObject.Merge()MergeArrayHandling设置。通过测试我发现:

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

    这是您正在经历的行为 - 现有数组及其中的所有项目将被丢弃,并替换为包含具有默认值的新构建项目的新数组。相反,您需要MergeArrayHandling.Merge将数组项合并在一起,并按索引进行匹配。

  • 填充读/写集合的成员,如List<T>似乎像MergeArrayHandling.Concat一样工作。

这似乎是合理的request an enhancementPopulate()支持此设置 - 尽管我不知道这将是多么容易实现。至少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]); 

转换器是专门为阵列设计的,但类似的转换器可以很容易地进行读/写的集合,如List<T>创建。

+0

感谢您的解释。我担心Populate()将无法合并当前和现有的数组,但其名称和文档混淆了我。最后,我通过添加或分配每个数组成员的属性与来自json的值来做到这一点。你的函数可能不太容易在我的真实代码中工作,因为我有一个具有各种属性和其他数组的类的数组。 – LukAss741