Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>Updated Answer:</p> <p>Looking at how JSON.Net maps XML, it takes the approach that what it sees is what it serializes, except that if it sees multiples, it will make an array. This is great for many XML DOM trees with consistent layout, but unfortunately cannot work for your purposes.</p> <p>You can verify this by looking at the body for the functions <code>SerializeGroupedNodes()</code> and <code>SerializeNode()</code> in the following file source. </p> <p><a href="http://json.codeplex.com/SourceControl/changeset/view/63616#128123" rel="nofollow">XmlNodeConverter.cs source code @ CodePlex, ChangeSet #63616</a></p> <p>There's another option that I'd previously thought might be overkill but would be helpful now that we know what to expect from the default behavior on the serializing end.</p> <p>Json.Net supports using custom converters derived from <code>JsonConverter</code> to map particular cases of values on a case-by-case basis.</p> <p>We can handle this either at the serializing side or the deserializing side. I've chosen to write a solution on the deserializing side, as you probably have other existing reasons to map XML to JSON.</p> <p>One great benefit is that your class can stay intact except for the override, which requires that you apply an attribute. Here's a code sample demonstrating how to use <code>JsonAttribute</code> and a custom converter class (<code>MMArrayConverter</code>) to fix your problem. Note that you will probably want to test this more thoroughly and maybe update the converter to handle other cases, say if you eventually migrate to <code>IList&lt;string&gt;</code> or some other funky case like <code>Lazy&lt;List&lt;string&gt;&gt;</code>, or even make it work with generics.</p> <pre><code>using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Converters; namespace JsonArrayImplictConvertTest { public class MMArrayConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType.Equals(typeof(List&lt;string&gt;)); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.StartArray) { List&lt;string&gt; parseList = new List&lt;string&gt;(); do { if (reader.Read()) { if (reader.TokenType == JsonToken.String) { parseList.Add((string)reader.Value); } else { if (reader.TokenType == JsonToken.Null) { parseList.Add(null); } else { if (reader.TokenType != JsonToken.EndArray) { throw new ArgumentException(string.Format("Expected String/Null, Found JSON Token Type {0} instead", reader.TokenType.ToString())); } } } } else { throw new InvalidOperationException("Broken JSON Input Detected"); } } while (reader.TokenType != JsonToken.EndArray); return parseList; } if (reader.TokenType == JsonToken.Null) { // TODO: You need to decide here if we want to return an empty list, or null. return null; } if (reader.TokenType == JsonToken.String) { List&lt;string&gt; singleList = new List&lt;string&gt;(); singleList.Add((string)reader.Value); return singleList; } throw new InvalidOperationException("Unhandled case for MMArrayConverter. Check to see if this converter has been applied to the wrong serialization type."); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { // Not implemented for brevity, but you could add this if needed. throw new NotImplementedException(); } } public class ModifiedXX { public string yy { get; set; } [JsonConverter(typeof(MMArrayConverter))] public List&lt;string&gt; mm { get; set; } public void Display() { Console.WriteLine("yy is {0}", this.yy); if (null == mm) { Console.WriteLine("mm is null"); } else { Console.WriteLine("mm contains these items:"); mm.ForEach((item) =&gt; { Console.WriteLine(" {0}", item); }); } } } class Program { static void Main(string[] args) { string jsonTest1 = "{\"yy\":\"nn\", \"mm\": [ \"zzz\", \"aaa\" ] }"; ModifiedXX obj1 = JsonConvert.DeserializeObject&lt;ModifiedXX&gt;(jsonTest1); obj1.Display(); string jsonTest2 = "{\"yy\":\"nn\", \"mm\": \"zzz\" }"; ModifiedXX obj2 = JsonConvert.DeserializeObject&lt;ModifiedXX&gt;(jsonTest2); obj2.Display(); // This test is now required in case we messed up the parser state in our converter. string jsonTest3 = "[{\"yy\":\"nn\", \"mm\": [ \"zzz\", \"aaa\" ] },{\"yy\":\"nn\", \"mm\": \"zzz\" }]"; List&lt;ModifiedXX&gt; obj3 = JsonConvert.DeserializeObject&lt;List&lt;ModifiedXX&gt;&gt;(jsonTest3); obj3.ForEach((obj) =&gt; { obj.Display(); }); Console.ReadKey(); } } } </code></pre> <hr> <p>Original Answer:</p> <p>It would be best to fix the JSON you're receiving at the source, as many have already pointed out. You may wish to post an update showing how the XML in your updated comment is being mapped to JSON, as that would be the best route overall.</p> <p>However, if you find that this is not possible and you want some way to serialize and handle the variant value after-the-fact, you can patch things up by declaring <code>mm</code> to be type <code>object</code>, and then handling the possible cases yourself using JSON.Net's Linq support. In the two scenarios you described, you'll find that declaring <code>mm</code> to be type <code>object</code> will result in either a <code>null</code>, a <code>string</code>, or a <code>JArray</code> being assigned to <code>mm</code> by the call to <code>DeserializeObject&lt;&gt;</code>.</p> <p>Here's a code sample that shows this in action. There's also a case in other circumstances where you could receive a <code>JObject</code>, which is also covered in this sample. Note that the member function <code>mmAsList()</code> does the work of patching up the difference for you. Also note that I've handled <code>null</code> here by returning a <code>null</code> for <code>List&lt;string&gt;</code>; you will probably want to revise this for your implementation.</p> <pre><code>using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace JsonArrayUnionTest { public class ModifiedXX { public string yy { get; set; } public object mm { get; set; } public List&lt;string&gt; mmAsList() { if (null == mm) { return null; } if (mm is JArray) { JArray mmArray = (JArray)mm; return mmArray.Values&lt;string&gt;().ToList(); } if (mm is JObject) { JObject mmObj = (JObject)mm; if (mmObj.Type == JTokenType.String) { return MakeList(mmObj.Value&lt;string&gt;()); } } if (mm is string) { return MakeList((string)mm); } throw new ArgumentOutOfRangeException("unhandled case for serialized value for mm (cannot be converted to List&lt;string&gt;)"); } protected List&lt;string&gt; MakeList(string src) { List&lt;string&gt; newList = new List&lt;string&gt;(); newList.Add(src); return newList; } public void Display() { Console.WriteLine("yy is {0}", this.yy); List&lt;string&gt; mmItems = mmAsList(); if (null == mmItems) { Console.WriteLine("mm is null"); } else { Console.WriteLine("mm contains these items:"); mmItems.ForEach((item) =&gt; { Console.WriteLine(" {0}", item); }); } } } class Program { static void Main(string[] args) { string jsonTest1 = "{\"yy\":\"nn\", \"mm\": [ \"zzz\", \"aaa\" ] }"; ModifiedXX obj1 = JsonConvert.DeserializeObject&lt;ModifiedXX&gt;(jsonTest1); obj1.Display(); string jsonTest2 = "{\"yy\":\"nn\", \"mm\": \"zzz\" }"; ModifiedXX obj2 = JsonConvert.DeserializeObject&lt;ModifiedXX&gt;(jsonTest2); obj2.Display(); Console.ReadKey(); } } } </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload