【问题标题】:Serializing a custom subclass of NameValueCollection with Json.Net使用 Json.Net 序列化 NameValueCollection 的自定义子类
【发布时间】:2015-02-24 19:00:24
【问题描述】:

我有以下类,但尝试序列化为 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


    【解决方案1】:

    这里发生了一些事情:

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

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

    3. 1234563为了明确的序列化,我建议将名称和值移动到嵌套属性(命名,例如“值”)中。
    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
    }
    

    使用以下测试用例:

            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 问题,以避免出现重复的“儿童”属性。
    • 感谢所有回复。选择这个作为答案是因为背景信息很好。
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      我在使用 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",因为您必须为匿名类型的属性指定名称

      【讨论】:

        【解决方案4】:

        这是一个自定义序列化程序,它将按照您的要求编写 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
           }
        

        【讨论】:

          猜你喜欢
          • 2017-03-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多