使用Json.Net序列化NameValueCollection的自定义子类

use*_*779 8 json.net

我有以下类我尝试序列化到Json失败了.

class HL7 : NameValueCollection
{
  public List<HL7> Children { get; set; }
  public HL7()
  {
    Children = new List<HL7>();
  }
}
Run Code Online (Sandbox Code Playgroud)

我已经创建了这样的对象并向其添加了数据:

HL7 hl7 = new HL7();
hl7.Add("a", "123");
hl7.Add("b", "456");
hl7.Children.Add(new HL7());
hl7.Children[0].Add("c", "123");
hl7.Children[0].Add("d", "456");
Run Code Online (Sandbox Code Playgroud)

我打电话的时候

JsonConvert.SerializeObject(hl7)
Run Code Online (Sandbox Code Playgroud)

我收到

["a","b"]
Run Code Online (Sandbox Code Playgroud)

我期待以下内容:

{
  "a": "123",
  "b": "456",
  "Children": [
    {
      "c": "123",
      "d": "456",
    }
  ]
} 
Run Code Online (Sandbox Code Playgroud)

dbc*_*dbc 6

这里有一些事情:

  1. Json.NET无法在NameValueCollection没有自定义转换器的情况下序列化,因为NameValueCollection实现IEnumerable了迭代键,但没有实现IDictionary迭代键和值.请参阅此答案,以更全面地解释为什么这会导致Json.NET出现问题.

  2. 因为NameValueCollectionimplements IEnumerable,Json.NET将您的类视为集合,因此将其序列化为JSON数组而不是具有命名属性的JSON对象.因此,您Children没有序列化.同样,需要一个自定义转换器来解决这个问题.

  3. 假设上述问题已得到解决,如果您的HL7子类NameValueCollection恰好有一个名为key的键"Children",则在序列化时会生成无效的JSON,即具有重复属性名称的对象.我建议将名称和值移动到嵌套属性(命名为"Values"),以便进行明确的序列化.

  4. NameValueCollection 实际上,对于给定的键字符串,它可以有多个字符串值,因此其条目值需要序列化为JSON数组而不是单个字符串.

将所有这些放在一起,以下代码:

[JsonConverter(typeof(HL7Converter))]
public class HL7 : NameValueCollection
{
    public List<HL7> Children { get; set; }
    public HL7()
    {
        Children = new List<HL7>();
    }
}

public class HL7Converter : JsonConverter
{
    class HL7Proxy
    {
        public NameValueCollectionDictionaryWrapper Values { get; set; }
        public List<HL7> Children { get; set; }
    }


    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(HL7);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var proxy = serializer.Deserialize<HL7Proxy>(reader);
        if (proxy == null)
            return existingValue;
        var hl7 = existingValue as HL7;
        if (hl7 == null)
            hl7 = new HL7();
        hl7.Add(proxy.Values.GetCollection());
        if (proxy.Children != null)
            hl7.Children.AddRange(proxy.Children);
        return hl7;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        HL7 hl7 = (HL7)value;
        if (hl7 == null)
            return;

        serializer.Serialize(writer, new HL7Proxy { Children = hl7.Children, Values = new NameValueCollectionDictionaryWrapper(hl7) });
    }
}

// Proxy dictionary to serialize & deserialize a NameValueCollection.  We use a proxy dictionary rather than a real dictionary because NameValueCollection is an ordered collection but the generic dictionary class is unordered.
public class NameValueCollectionDictionaryWrapper: IDictionary<string, string []>
{
    readonly NameValueCollection collection;

    public NameValueCollectionDictionaryWrapper()
        : this(new NameValueCollection())
    {
    }

    public NameValueCollectionDictionaryWrapper(NameValueCollection collection)
    {
        this.collection = collection;
    }

    // Method instead of a property to guarantee that nobody tries to serialize it.
    public NameValueCollection GetCollection()
    {
        return collection;
    }

    #region IDictionary<string,string[]> Members

    public void Add(string key, string[] value)
    {
        if (collection.GetValues(key) != null)
            throw new ArgumentException("Duplicate key " + key);
        foreach (var str in value)
            collection.Add(key, str);
    }

    public bool ContainsKey(string key)
    {
        return collection.GetValues(key) != null;
    }

    public ICollection<string> Keys
    {
        get {
            return collection.AllKeys;
        }
    }

    public bool Remove(string key)
    {
        bool found = ContainsKey(key);
        if (found)
            collection.Remove(key);
        return found;
    }

    public bool TryGetValue(string key, out string[] value)
    {
        value = collection.GetValues(key);
        return value != null;
    }

    public ICollection<string[]> Values
    {
        get {
            return Enumerable.Range(0, collection.Count).Select(i => collection.GetValues(i)).ToArray();
        }
    }

    public string[] this[string key]
    {
        get
        {
            var value = collection.GetValues(key);
            if (value == null)
                throw new KeyNotFoundException();
            return value;
        }
        set
        {
            Remove(key);
            Add(key, value);
        }
    }

    #endregion

    #region ICollection<KeyValuePair<string,string[]>> Members

    public void Add(KeyValuePair<string, string[]> item)
    {
        Add(item.Key, item.Value);
    }

    public void Clear()
    {
        collection.Clear();
    }

    public bool Contains(KeyValuePair<string, string[]> item)
    {
        string [] value;
        if (!TryGetValue(item.Key, out value))
            return false;
        return EqualityComparer<string[]>.Default.Equals(item.Value, value); // Consistent with Dictionary<TKey, TValue>
    }

    public void CopyTo(KeyValuePair<string, string[]>[] array, int arrayIndex)
    {
        foreach (var item in this)
            array[arrayIndex++] = item;
    }

    public int Count
    {
        get { return collection.Count; }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool Remove(KeyValuePair<string, string[]> item)
    {
        if (Contains(item))
            return Remove(item.Key);
        return false;
    }

    #endregion

    #region IEnumerable<KeyValuePair<string,string[]>> Members

    public IEnumerator<KeyValuePair<string, string[]>> GetEnumerator()
    {
        foreach (string key in collection)
        {
            yield return new KeyValuePair<string, string[]>(key, collection.GetValues(key)); 
        }
    }

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion
}
Run Code Online (Sandbox Code Playgroud)

使用以下测试用例:

        HL7 hl7 = new HL7();
        hl7.Add("a", "123");
        hl7.Add("b", "456");
        hl7.Add("Children", "Children");
        hl7.Children.Add(new HL7());
        hl7.Children[0].Add("c", "123");
        hl7.Children[0].Add("d", "456");
        hl7.Children[0].Add("d", "789");

        var json = JsonConvert.SerializeObject(hl7, Formatting.Indented);

        Debug.WriteLine(json);
Run Code Online (Sandbox Code Playgroud)

提供以下JSON:

{
  "Values": {
    "a": [
      "123"
    ],
    "b": [
      "456"
    ],
    "Children": [
      "Children"
    ]
  },
  "Children": [
    {
      "Values": {
        "c": [
          "123"
        ],
        "d": [
          "456",
          "789"
        ]
      },
      "Children": []
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)