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

Serializing a custom subclass of NameValueCollection with Json.Net

我有以下 class 我尝试序列化到 Json 失败。

class HL7 : NameValueCollection
{
  public List<HL7> Children { get; set; }
  public HL7()
  {
    Children = new List<HL7>();
  }
}

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

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");

当我打电话时

JsonConvert.SerializeObject(hl7)

我收到

["a","b"]

我期待以下内容:

{
  "a": "123",
  "b": "456",
  "Children": [
    {
      "c": "123",
      "d": "456",
    }
  ]
} 

我在使用 JSON.Net 序列化 NameValueCollections 时遇到问题,我发现的唯一方法是将其转换为字典,然后像这样序列化它:

var jsonString = JsonConvert.SerializeObject(new
{
    Parent = hl7.AllKeys.ToDictionary(r => r, r => hl7[r]),
    Children = hl7.Children.Select(c => c.AllKeys.ToDictionary(sub => sub, sub => c[sub]))
}, Newtonsoft.Json.Formatting.Indented);

你最终会得到:

{
  "Parent": {
    "a": "123",
    "b": "456"
  },
  "Children": [
    {
      "c": "123",
      "d": "456"
    }
  ]
}

但这对于顶级项目也会 return "Parent",因为您必须以匿名类型 为 属性 指定名称]

受此答案启发 how to convert NameValueCollection to JSON string? ,这是工作代码(唯一不好的部分可能是 "Children" 字符串,即 属性 名称。如果你进行重构,这将导致错误。

JsonConvert.SerializeObject(NvcToDictionary(hl7, false));

以及函数:

static Dictionary<string, object> NvcToDictionary(HL7 nvc, bool handleMultipleValuesPerKey)
    {
        var result = new Dictionary<string, object>();
        foreach (string key in nvc.Keys)
        {
            if (handleMultipleValuesPerKey)
            {
                string[] values = nvc.GetValues(key);
                if (values.Length == 1)
                {
                    result.Add(key, values[0]);
                }
                else
                {
                    result.Add(key, values);
                }
            }
            else
            {
                result.Add(key, nvc[key]);
            }
        }


        if (nvc.Children.Any())
        {
            var listOfChildrenDictionary = new List<Dictionary<string, object>>();
            foreach (var nvcChildren in nvc.Children){
                listOfChildrenDictionary.Add(NvcToDictionary(nvcChildren, false));
            }

            result.Add("Children", listOfChildrenDictionary);
        }

        return result;
    }

这里发生了一些事情:

  1. Json.NET 无法序列化 NameValueCollection without a custom converter because NameValueCollection implements IEnumerable for iterating over the keys, but does not implement IDictionary for iterating over keys and values. See this answer 以更全面地解释为什么这会导致 Json.NET.

  2. 出现问题
  3. 因为 NameValueCollection 实现了 IEnumerable,Json.NET 将您的 class 视为一个集合,因此将其序列化为 JSON数组而不是具有命名属性的 JSON 对象。因此,您的 Children 未序列化。同样,需要自定义转换器来解决此问题。

  4. 假设上述问题已解决,如果您的 NameValueCollectionHL7 subclass 碰巧有一个名为 "Children" 的密钥,您将生成序列化时无效 JSON,即具有重复 属性 名称的对象。我建议将名称和值移动到嵌套的 属性(命名,例如 "Values")以明确序列化。

  5. 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
}

使用以下测试用例:

        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);

给出以下 JSON:

{
  "Values": {
    "a": [
      "123"
    ],
    "b": [
      "456"
    ],
    "Children": [
      "Children"
    ]
  },
  "Children": [
    {
      "Values": {
        "c": [
          "123"
        ],
        "d": [
          "456",
          "789"
        ]
      },
      "Children": []
    }
  ]
}

这是一个自定义序列化程序,它将按照您正在寻找的方式编写 JSON,示例程序已附上。序列化程序在底部。请注意,您需要将此转换器添加到 JSON 序列化程序设置中,可以像我所做的那样通过默认设置,也可以通过序列化程序的构造函数。或者,由于您有一个 subclass,您可以使用 HL7 class 上的 JsonConverterAttribute 来分配序列化程序

 public class Program
   {
      static int Main(string[] args) {
         JsonConvert.DefaultSettings = () => new JsonSerializerSettings {
            Converters = new []{ new HL7Converter() }
         };

         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");

         Console.WriteLine (JsonConvert.SerializeObject (hl7));
         return 0;
      }
   }

   public class HL7 : NameValueCollection
   {
      public List<HL7> Children { get; set; }
      public HL7()
      {
         Children = new List<HL7> ();
      }
   }

   public class HL7Converter : Newtonsoft.Json.JsonConverter {
      #region implemented abstract members of JsonConverter

      public override void WriteJson (Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
      {
         var collection = (HL7)value;

         writer.WriteStartObject ();
         foreach (var key in collection.AllKeys) {
            writer.WritePropertyName (key);
            writer.WriteValue (collection [key]);
         }
         writer.WritePropertyName ("Children");
         serializer.Serialize (writer,collection.Children);
         writer.WriteEndObject ();
      }

      public override object ReadJson (Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
      {
         HL7 collection = existingValue as HL7 ?? new HL7 ();
         JObject jObj = JObject.Load (reader);
         foreach (var prop in jObj.Properties()) {
            if (prop.Name != "Children") {
               collection.Add (prop.Name, prop.Value.ToObject<string> ());
            } else {
               collection.Children = jObj.ToObject<List<HL7>> ();
            }
         }
         return collection;
      }

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

      #endregion
   }