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



我有以下类,我试图序列化到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",
    }
  ]
} 

这里发生了一些事情:

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

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

  3. 假设上述问题得到解决,如果NameValueCollectionHL7子类碰巧有一个名为"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
}

使用以下测试用例:

        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": []
    }
  ]
}

受此答案启发如何将NameValueCollection转换为JSON字符串?,这是工作代码(唯一不好的部分可能是作为属性名称的"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;
    }

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

这里有一个自定义序列化程序,它将按照您所寻找的方式编写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
   }

相关内容

  • 没有找到相关文章

最新更新