0

I have a string that contains a list of fruits. My C# program can't use Fruit directly, it needs to cast them to Banana or Apple. The information about in what to cast them is included in the constructor attribute in the json.

Note: I'm using JSON.NET.

This is what I want to achieve:

    class Fruit{ public string constructor;}
    class Banana : Fruit { public int monkey;}
    class Apple : Fruit { public string shape;}

    string json = @"[
            {
                ""constructor"":""banana"",
                ""monkey"":1
            },
            {
                ""constructor"":""apple"",
                ""shape"":""round""
            }]";
        List<Fruit> fruitList = JsonConvert.DeserializeObject<List<Fruit>>(json);
        List<Banana> bananaList = new List<Banana>();
        List<Apple> appleList = new List<Apple>();
        foreach(var fruit in fruitList){
            if(fruit.constructor == "banana") bananaList.Add((Banana)fruit); //bug
            if(fruit.constructor == "apple") appleList.Add((Apple)fruit); //bug
        }

        //use bananaList and appleList for stuff

However, I can't do the cast. Is there any way so bananaList contains all objects in the json that has the constructor attributes set to "banana" and same for appleList?


SOLUTION:

    public static string objToJson(object obj) {
        return JsonConvert.SerializeObject(obj, Formatting.Indented, new JsonSerializerSettings {
            TypeNameHandling = TypeNameHandling.Auto
        });
    }
    public static T jsonToObj<T>(string str) {
        return JsonConvert.DeserializeObject<T>(str, new JsonSerializerSettings {
            TypeNameHandling = TypeNameHandling.Auto
        });
    }
RainingChain
  • 7,397
  • 10
  • 36
  • 68
  • 1
    The objects created are Fruit. A class-cast works on the *actual* object, so a Fruit cannot be "changed" into a Banana/Apple - it is only valid going the other way. That being said it is likely possible to handle this with a CustomConverter (Json.NET has support for restoring types via `$type`) where such converter would appropriately parse each object into a *real* Banana/Apple instance. – user2864740 Sep 05 '14 at 05:11
  • Whoa, and look here: http://stackoverflow.com/questions/8030538/how-to-implement-custom-jsonconverter-in-json-net-to-deserialize-a-list-of-base (in the first fews hit after searching for a "custom converter", actually) – user2864740 Sep 05 '14 at 05:14
  • sucks you used my answer but accepted a different answer? – Chad Grant Sep 06 '14 at 17:53

3 Answers3

2

In JSON.Net, there's $type that you can use to specify which type to deserialize to:

string json = @"[
            {
                ""$type"":""Assembly.Namespace.banana"",
                ""monkey"":1
            },
            {
                ""$type"":""Assembly.Namespace.apple"",
                ""shape"":""round""
            }]";
Mrchief
  • 75,126
  • 20
  • 142
  • 189
1

You're attempting to do a polymorphic deserialization but you are asking the deserializer to deserialize as List of Fruit. You cannot upcast to Apple/Banana because the instances will be of type Fruit.

If you have control of the json, you can save the json with type information in it and the deserializer will automagically create the correct types.

public class Fruit{  }
public class Banana : Fruit { public int monkey;}
public class Apple : Fruit { public string shape;}

class Program
{
    static void Main(string[] args)
    {
        var list = new List<Fruit>(new Fruit[] {new Banana(), new Apple()});

        var serializerSettings = new JsonSerializerSettings{ TypeNameHandling = TypeNameHandling.Auto };

        var json = JsonConvert.SerializeObject(list, serializerSettings);

        List<Fruit> fruitList = JsonConvert.DeserializeObject<List<Fruit>>(json, serializerSettings);
        List<Banana> bananaList = new List<Banana>();
        List<Apple> appleList = new List<Apple>();
        foreach (var fruit in fruitList)
        {
            if (fruit is Banana) bananaList.Add((Banana) fruit); 
            if (fruit is Apple) appleList.Add((Apple) fruit); 
        }
    }
}

You can also use some linq to find the apples / bananas easier than foreach:

List<Banana> bananaList = fruitList.Where(f => f is Banana).Cast<Banana>().ToList();
List<Apple>  appleList  = fruitList.Where(f => f is Apple).Cast<Apple>().ToList();

Or you can use converters as discussed here Deserializing polymorphic json classes without type information using json.net

Community
  • 1
  • 1
Chad Grant
  • 44,326
  • 9
  • 65
  • 80
  • What do you mean by "you can save the json with type information in it"? And where do I use my json string exactly? – RainingChain Sep 05 '14 at 05:37
  • 1
    @RainingChain: The TypeNameHandling.Auto/Object property will write out the $type information during serialization, which the deserializer can use to deserialize to appropriate type. – Mrchief Sep 05 '14 at 05:48
  • debug the code and look at the json produced after JsonConvert.SerializeObject. You'll see – Chad Grant Sep 05 '14 at 06:07
0

JSON.NET will only be able to parse what it expects to parse. There's no way for it to assume which polymorphic child of the class it wants to use.

If you have control over the source JSON, you can tell the serializer to include data about what type is being used.

If you don't, I believe you'll need to read through the data manually (or via JTokens or similar) to find the constructor properties, before telling it to parse the proper types.

Matthew Haugen
  • 12,916
  • 5
  • 38
  • 54