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

时间:2015-02-24 19:00:24

标签: json.net

我有以下课程,但我没有成功尝试序列化为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",
    }
  ]
} 

4 个答案:

答案 0 :(得分:5)

这里有一些事情发生:

  1. Json.NET无法在没有自定义转换器的情况下序列化NameValueCollection因为NameValueCollection实现了IEnumerable来迭代密钥,但是没有实现IDictionary进行迭代超过键和值。有关为何导致Json.NET出现问题的详细解释,请参阅this answer

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

  3. 假设上述问题已得到解决,如果HL7的{​​{1}}子类恰好有一个名为NameValueCollection的密钥,则在序列化时会生成无效的JSON,即一个对象具有重复的属性名称。我建议移动名字和为了明确的序列化,将值放入嵌套属性(命名为例如&#34;值&#34;)。

  4. "Children"实际上可以为给定的键字符串提供多个字符串值,因此其条目值需要序列化为JSON数组而不是单个字符串。

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

    NameValueCollection

    使用以下测试用例:

    [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
    }
    

    提供以下JSON:

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

答案 1 :(得分:3)

受到这个答案的启发 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;
    }

答案 2 :(得分:1)

我遇到了使用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"
    }
  ]
}

但是这将返回"Parent"以及顶级项目,因为您必须为匿名类型的属性指定名称

答案 3 :(得分:1)

这是一个自定义序列化程序,可以按照您的要求编写JSON,附带示例程序。序列化器位于底部。请注意,您需要将此转换器添加到JSON序列化程序设置中,或者通过默认情况下完成,或者通过序列化程序的构造函数。或者,由于您有一个子类,您可以使用HL7类上的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
   }