Serializing a custom subclass of NameValueCollection using Json.Net

I have the following class which I unsuccessfully try to serialize in Json.

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

I created the object like this and added data to it:

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

When i call

 JsonConvert.SerializeObject(hl7) 

I got

 ["a","b"] 

I expected the following:

 { "a": "123", "b": "456", "Children": [ { "c": "123", "d": "456", } ] } 
+8
source share
4 answers

Here are a few things going on:

  1. Json.NET cannot serialize NameValueCollection without a custom converter, because NameValueCollection implements IEnumerable to enumerate keys, but does not implement IDictionary for IDictionary keys and values. See this answer for a more complete explanation of why this is causing problems for Json.NET.

  2. Since NameValueCollection implements IEnumerable , Json.NET sees your class as a collection and therefore serializes it as a JSON array, not a JSON object with named properties. This way your Children not serialized. Again, this will require a special converter.

  3. Assuming that the above problems are resolved, if your subclass of HL7 NameValueCollection has a key named "Children" you will generate invalid JSON when it is serialized, namely, an object with duplicate property names. I suggest moving names and values ​​to a nested property (called, for example, "Values") for unambiguous serialization.

  4. NameValueCollection can actually have multiple string values ​​for a given key string, so its record values ​​should be serialized as a JSON array, and not as a single string.

Putting it all together, the following code:

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

Using the following test case:

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

Gives the following JSON:

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

Inspired by this answer how to convert NameValueCollection to JSON string? , here is the working code (the only bad part is probably the string "Children", which is the name of the property. If you do the refactoring, this will lead to an error.

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

And function:

 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
source share

I'm having problems serializing NameValueCollections using JSON.Net. The only way I found is to convert it to a dictionary and then serialize it like this:

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

and you will receive:

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

But this will return "Parent" also for top-level elements, since you must specify a name for the property in an anonymous type

+1
source share

Here's a custom serializer that will write JSON as you searched, for example, the program is attached. The serializer is at the bottom. Please note that you will need to add this converter to the settings of the JSON serializer, either by default, as I did, or through the constructor of your serializer. Alternatively, since you have a subclass, you can use the JsonConverterAttribute in the HL7 class to assign a serializer

  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 } 
+1
source share

All Articles