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
        });
    }
3
  • 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. Commented Sep 5, 2014 at 5:11
  • Whoa, and look here: stackoverflow.com/questions/8030538/… (in the first fews hit after searching for a "custom converter", actually) Commented Sep 5, 2014 at 5:14
  • sucks you used my answer but accepted a different answer? Commented Sep 6, 2014 at 17:53

3 Answers 3

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""
            }]";
Sign up to request clarification or add additional context in comments.

1 Comment

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

3 Comments

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: The TypeNameHandling.Auto/Object property will write out the $type information during serialization, which the deserializer can use to deserialize to appropriate type.
debug the code and look at the json produced after JsonConvert.SerializeObject. You'll see
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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.