5

I just downloaded a huge JSON file with all current MTG sets/cards, and am looking to deserialize it all.

I've gotten most of each set deserialized, but I'm running into a snag when trying to deserialize the booster object within each Set:

enter image description here

enter image description here

As you can see from the above two pictures, in each booster object there is a list of strings, but for some booster objects there is also an additional array of more strings. Deserializing an array of exclusively strings isn't a problem. My issue arises when I run into those instances where there is an array of strings within the booster object that need deserializing.

Currently the property I have set up to handle this deserialization is:

public IEnumerable<string> booster { get; set; }

But when I run into those cases where there's another array within booster I get an exception thrown, where Newtonsoft.Json complains it doesn't know how to handle the deserialization.

So, my question then becomes: how can I go about deserializing an array of strings contained within an array of strings? And what would an object need to look like in C# code to handle that sort of deserialization?

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
Delfino
  • 967
  • 4
  • 21
  • 46
  • You asked an important question yourself there, "what would the object need to look like?". In my opinion this is where you should start, deciding how YOU want to model your data. Until then, deserialization is not really the problem. – MEMark Jan 19 '18 at 20:17
  • From what I understand of `Newtonsoft.Json`, it tries to map each object to a property name inside C# code. My only idea thus far is to create a `Booster` object for it to map to, but then I'm not sure how to include an "optional" `IEnumerable` part for those cases where there does exist another array within the `booster` object. – Delfino Jan 19 '18 at 20:23
  • 2
    Take a look at this: https://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n Short answer: you have to write your own logic to handle that. As to the end-result model, model it as you wish. You can model it as array of Booster, where each Booster has a collection of Strings and in case of single items the collection would contain only one item. – Vlad Stryapko Jan 19 '18 at 20:30
  • `StringConverter ` from this one seems even more relevant: [How to handle json that returns both a string and a string array?](https://stackoverflow.com/a/22053420/3744182). Then declare your `booster` property as follows: `[JsonProperty(ItemConverterType = typeof(StringConverter))] public List> booster { get; set; }` – dbc Jan 19 '18 at 20:49

4 Answers4

1

If you are using C# as your programming language then use the below link to generate C# class from JSON string

http://json2csharp.com/

You can then use the generated class in your code to deserialize your json string to object using JsonConvert.DeserializeObject(jssonstring)

Maddy
  • 774
  • 5
  • 14
1

You could deserialize the per item as string[] even thought the item wouldn't be a collection. So, provide a custom serializer;

public class StringArrayConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JArray array = JArray.Load(reader);
        for (int i = 0; i < array.Count; i++)
        {
            //If item is just a string, convert it string collection
            if (array[i].Type == JTokenType.String)
            {
                array[i] = JToken.FromObject(new List<string> {array[i].ToObject<string>()});
            }
        }
        return array.ToObject<List<string[]>>();
    }

    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(List<string[]>));
    }
}
public class JsonObject
{
    [JsonConverter(typeof(StringArrayConverter))]
    public List<string[]> booster { get; set; }
}

Then deserialize the json;

var data = JsonConvert.DeserializeObject<JsonObject>(json);

Finally, you can deserialize a json like I provided below;

{
    "booster": [
      "1",
      "2",
          ["3","4"]
    ]
}
lucky
  • 12,734
  • 4
  • 24
  • 46
0

The easiest solution would be to change the type to IEnumerable<object>. So it can store string or string[].

Or you could create a class Item with two properties of types string and string[]. Then you could create another property that returns the one that's not null, so now instead of the whole item being an object, you can have a special class that only returns one of two types so you can be sure that you get either a string or a string[]. Hope that makes sense.

Your property: public IEnumerable<Item> booster { get; set; }

public class Item
{
    private string _text;
    private string[] _array;
    public object Value => (object)_text ?? (object)_array;

    public Item(string text) { _text = text; }
    public Item(string[] array) { _array = array; }
}

When you need to use this value, you can check if it's string or string[] like this:

if(myItem is string text)
{
  // operate on variable text
}
else // you can be sure that myItem is of type string[]  because we covered this up in the Item class
{
  var array = (string[])myItem;
  // operate on variable array
}
Furkan Kambay
  • 751
  • 1
  • 7
  • 18
0

Another option would be to model "booster" as IEnumerable<string[]> and then use a custom JsonConverter to force strings to arrays. In the process of testing this theory, I wrote a (minimally tested, but functional) converter for you :)

public class ForceStringToArrayConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(IEnumerable<string[]>));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var listObject = new List<string[]>();

        var jObject = JToken.Load(reader);

        foreach (JToken token in jObject.Children())
        {
            if (token.Type == JTokenType.Array)
            {
                var arrayObj = (JArray)token;
                listObject.Add(arrayObj.ToObject<string[]>());
            }
            else if (token.Type == JTokenType.String)
            {
                listObject.Add(new string[] { token.ToString() });
            }
        }

        return listObject;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Then when you invoke DeserializeObject, pass it your custom converter:

    var obj = Newtonsoft.Json.JsonConvert.DeserializeObject<YourTypeHere>(testJson, new ForceStringToArrayConverter() );
Rick Riensche
  • 1,050
  • 1
  • 12
  • 25