如何使用 Json.Net 序列化/反序列化具有附加属性的自定义集合

Posted

技术标签:

【中文标题】如何使用 Json.Net 序列化/反序列化具有附加属性的自定义集合【英文标题】:How to serialize/deserialize a custom collection with additional properties using Json.Net 【发布时间】:2013-01-01 06:24:21 【问题描述】:

我有一个自定义集合(实现 IList),它有一些自定义属性,如下所示:

class FooCollection : IList<Foo> 

    private List<Foo> _foos = new List<Foo>();
    public string Bar  get; set;         

    //Implement IList, ICollection and IEnumerable members...


当我序列化时,我使用以下代码:

JsonSerializerSettings jss = new JsonSerializerSettings() 
    TypeNameHandling = TypeNameHandling.Auto
;
string serializedCollection = JsonConvert.SerializeObject( value , jss );

它正确地序列化和反序列化所有集合项;但是,FooCollection 类中的任何额外属性都不会被考虑在内。

到底有没有将它们包含在序列化中?

【问题讨论】:

【参考方案1】:

问题如下:当一个对象实现IEnumerable时,JSON.net将它识别为一个值数组,并在数组Json syntax之后序列化它(不包括属性), 例如:

 [ "FooProperty" : 123, "FooProperty" : 456, "FooProperty" : 789]

如果要序列化它并保留属性,则需要通过定义自定义JsonConverter 手动处理该对象的序列化:

// intermediate class that can be serialized by JSON.net
// and contains the same data as FooCollection
class FooCollectionSurrogate

    // the collection of foo elements
    public List<Foo> Collection  get; set; 
    // the properties of FooCollection to serialize
    public string Bar  get; set; 


public class FooCollectionConverter : JsonConverter

    public override bool CanConvert(Type objectType)
    
        return objectType == typeof(FooCollection);
    

    public override object ReadJson(
        JsonReader reader, Type objectType, 
        object existingValue, JsonSerializer serializer)
    
        // N.B. null handling is missing
        var surrogate = serializer.Deserialize<FooCollectionSurrogate>(reader);
        var fooElements = surrogate.Collection;
        var fooColl = new FooCollection  Bar = surrogate.Bar ;
        foreach (var el in fooElements)
            fooColl.Add(el);
        return fooColl;
    

    public override void WriteJson(JsonWriter writer, object value, 
                                   JsonSerializer serializer)
    
        // N.B. null handling is missing
        var fooColl = (FooCollection)value;
        // create the surrogate and serialize it instead 
        // of the collection itself
        var surrogate = new FooCollectionSurrogate() 
         
            Collection = fooColl.ToList(), 
            Bar = fooColl.Bar 
        ;
        serializer.Serialize(writer, surrogate);
    

然后按如下方式使用:

var ss = JsonConvert.SerializeObject(collection, new FooCollectionConverter());

var obj = JsonConvert.DeserializeObject<FooCollection>(ss, new FooCollectionConverter());

【讨论】:

这对我有帮助,但是当我使用 Foo[] 作为代理中的类型时,JSON.NET 不喜欢它 - 我不得不使用 IEnumerable&lt;Foo&gt; 嗯...奇怪,你用的是什么版本的 json.net? 不确定,目前无法检查,但来自针对 .NET 4.0 的掘金 我刚刚检查过,它适用于 4.5.11.15520 和 5.0.8.16617 版本......我怀疑你做的事情与上面的代码略有不同...... @Jeff:我已经用列表替换了数组,所以在 preserve-reference = true 的情况下人们不会崩溃【参考方案2】:

我个人喜欢尽可能避免编写自定义JsonConverters,而是使用为此目的设计的各种 JSON 属性。您可以简单地用JsonObjectAttribute 装饰FooCollection,这会强制序列化为JSON 对象而不是数组。您必须用JsonIgnore 装饰CountIsReadOnly 属性,以防止它们出现在输出中。如果您想将_foos 保留为私有字段,则还必须使用JsonProperty 来装饰它。

[JsonObject]
class FooCollection : IList<Foo> 
    [JsonProperty]
    private List<Foo> _foos = new List<Foo>();
    public string Bar  get; set;   

    // IList implementation
    [JsonIgnore]
    public int Count  ... 
    [JsonIgnore]
    public bool IsReadOnly  ... 

序列化产生如下内容:


  "_foos": [
    "foo1",
    "foo2"
  ],
  "Bar": "bar"

显然,这只有在您能够更改 FooCollection 的定义以添加这些属性时才有效,否则您必须采用自定义转换器。

【讨论】:

因为我使用的是持久化 json 文档的 c# 模型,所以这对我来说是最好的选择。我完全可以用反映其物理持久性的信息来装饰我的模型。在这种情况下,文档是 100% 耦合到 json 的。 我认为你可以通过显式实现接口来避免JsonIgnore 今天我了解到,使用显式接口不会序列化这些成员。问题是我不知道为什么?这个 Little Endian 背后的故事是什么 非常感谢【参考方案3】:

如果您不想编写自定义 JsonConverter 或使用 JSON 属性 (JsonObjectAttribute),您可以使用以下扩展方法:

public static string ToFooJson<T>(this FooCollection fooCollection)

     return JsonConvert.SerializeObject(new
     
         Bar = fooCollection.Bar,
         Collection = fooCollection
     );

【讨论】:

【参考方案4】:

如果您还想保留 List 或集合本身的内容, 您应该考虑公开一个属性以返回列表。它必须被包装以防止序列化时出现循环问题:

注意:此解决方案同时支持序列化/反序列化

[JsonObject]
public class FooCollection : List<int>

    public string Bar  get; set;  = "Bar";

    [JsonProperty]
    ICollection<int> Items => new _<int>(this);


internal class _<T> : ICollection<T>

    public _(ICollection<T> collection) => inner = collection;
    private ICollection<T> inner;
    int ICollection<T>.Count => inner.Count;
    bool ICollection<T>.IsReadOnly => inner.IsReadOnly;
    void ICollection<T>.Add(T item) => inner.Add(item);
    void ICollection<T>.Clear() => inner.Clear();
    bool ICollection<T>.Contains(T item) => inner.Contains(item);
    void ICollection<T>.CopyTo(T[] array, int arrayIndex) => inner.CopyTo(array, arrayIndex);
    IEnumerator<T> IEnumerable<T>.GetEnumerator() => inner.GetEnumerator();
    bool ICollection<T>.Remove(T item) => inner.Remove(item);
    IEnumerator IEnumerable.GetEnumerator() => inner.GetEnumerator();

new FooCollection 1, 2, 3, 4, 4 =>


  "bar": "Bar",
  "items": [
    1,
    2,
    3
  ],
  "capacity": 4,
  "count": 3

new FooCollection 1, 2, 3 .ToArray() => new []1, 2, 3.ToArray()

【讨论】:

【参考方案5】:

从 List 继承有效吗?

class FooCollection : List<Foo>, IList<Foo>

    public string Bar  get; set;         
    //Implement IList, ICollection and IEnumerable members...

【讨论】:

令人惊讶的是,除非绝对必要,否则我会走那条路,理想情况下我想保持继承不变。 目前你正在尝试序列化一个私有属性——通过继承你成为你要求序列化的对象(这显然不是唯一的方法,但很可能是最简单的)跨度> 正如@digEmAll 指出的那样,这不起作用,因为 JSON.net 使用集合序列化程序,它不检查其他属性。但是,我必须同意 qujck 的观点,即如果您的类是 List,那么最好直接从 List 继承,而不必实现整个 IList 接口。唯一的例外是它已经必须实现一些其他的胖基类。【参考方案6】:

我最近在测试类似的东西,并提出了以下不需要自定义序列化程序的示例(与 @Ahmed Alejo's answer 非常相似,但不需要公共属性):

void Main()

    var test = new Container();
    test.Children.Add("Item 1");
    test.Children.Add("Item 2");
    test.Children.Add("Item 3");
    
    Console.WriteLine(JsonConvert.SerializeObject(test));


class Container 
    public CustomList Children  get; set;  = new CustomList()  Name = Guid.NewGuid().ToString() ;


[JsonObject(MemberSerialization = MemberSerialization.Fields)]
class CustomList : IList<string>

    private List<string> items = new List<string>();
    private string name;

    public string this[int index]  get => items[index]; set => items[index] = value; 

    public string Name  get => name; set  name = value;  

    public int Count => items.Count;

    public bool IsReadOnly => false;

    public void Add(string item)
    
        items.Add(item);
    

    public void Clear()
    
        items.Clear();
    

    public bool Contains(string item) => items.Contains(item);

    public void CopyTo(string[] array, int arrayIndex)
    
        items.CopyTo(array, arrayIndex);
    

    public IEnumerator<string> GetEnumerator() => items.GetEnumerator();

    public int IndexOf(string item) => items.IndexOf(item);

    public void Insert(int index, string item)
    
        items.Insert(index, item);
    

    public bool Remove(string item) => items.Remove(item);

    public void RemoveAt(int index)
    
        items.RemoveAt(index);
    

    IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)items).GetEnumerator();

关键在于将显式JsonObjectAttribute 添加到类中,这会将其视为对象而不是数组(也可以使用JsonArrayAttribute 显式表示)。上述测试的(美化)输出如下:


  "Children": 
    "items": [
      "Item 1",
      "Item 2",
      "Item 3"
    ],
    "name": "ff8768f7-5efb-4622-b7e2-f1472e80991c"
  

【讨论】:

以上是关于如何使用 Json.Net 序列化/反序列化具有附加属性的自定义集合的主要内容,如果未能解决你的问题,请参考以下文章

JSON.net 反序列化对象嵌套数据

使用 JSON.NET 反序列化 DateTime 时如何保留时区? [复制]

如何使用 Json.NET 反序列化高精度十进制值?

使用Newtonsoft.Json.dll(JSON.NET)动态解析JSON.net 的json的序列化与反序列化

如何使用 Json.Net 序列化/反序列化带有自定义键的字典?

如何在 asp.net owin api 项目中启用 JSON.NET 序列化/反序列化跟踪?