3

I want to parse a JSON string to a C# object that could be polymorph.

To sum it up: I don't want to instantiate the root object, but I want to instantiate the inherited object depending on the JSON input.

Here's an example of the C# objects I use:

public class Module {
  public string name;
}

public class Wheel : Module {
  public int amount;
  public Wheel(string name, int amount) : base(name) {...}
}

public class Break : Module {
  public double delay;
  public Break(string name, double delay) : base(name) {...}
}

And I have this JSON string that is an array containing two JSON objects:

[{
  "name":"Wheel",
  "amount":4
},{
  "name":"Break",
  "delay":1.0
}]

I want to have this JSON-string deserialized as a C# object (list/array). Each item should be instantiated as a subclass (Wheel or Break), but since List items have to be on the same denominator, the list type has to be of type Module.

3
  • what is difference between your old question and this question? Commented Dec 5, 2014 at 8:30
  • clearer description , old one has been deleted (or at least is in progress) Commented Dec 5, 2014 at 8:35
  • Have you tried Newtonsoft library? Commented Dec 5, 2014 at 9:57

2 Answers 2

6

If you use the Newtonsoft JSON Library, you can create some custom converters, as follows:

public class ModuleObjectConverter : JsonCreationConverter<Module>
{
    protected override Module Create(Type objectType, JObject jObject)
    {
        // This is the important part - we can query what json properties are present
        // to figure out what type of object to construct and populate
        if (FieldExists("amount", jObject)) {
            return new Wheel();
        } else if (FieldExists("delay", jObject)) {
            return new Break();
        } else {
            return null;
        }
    }

    private bool FieldExists(string fieldName, JObject jObject)
    {
        return jObject[fieldName] != null;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // We don't deal with writing JSON content, and generally Newtonsoft would make a good job of
        // serializing these type of objects without having to use a custom writer anyway
    }
}

// Generic converter class - could combine with above class if you're only dealing
// with one inheritance chain, but this way it's reusable
public abstract class JsonCreationConverter<T> : JsonConverter
{
    protected abstract T Create(Type objectType, JObject jObject);

    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Load JObject from stream
        JObject jObject = JObject.Load(reader);

        // Create target object based on JObject
        T target = Create(objectType, jObject);

        // Populate the object properties
        serializer.Populate(jObject.CreateReader(), target);

        return target;
    }
}

You then pass an instance of this helper to Newtonsoft when deserializing:

var modules = JsonConvert.DeserializeObject<List<Module>>(jsonString, new ModuleObjectConverter());
Sign up to request clarification or add additional context in comments.

Comments

1

I don't think you can do it in one neat swoop. If I'd had to do it, I'd probably do it like this.

var json = @"
[{
""name"":""Wheel"",
""amount"":4
},{
""name"":""Break"",
""delay"":1.0
}]";

// Get a list of possible types from the assembly containing Module.
// I don't know of a better way of doing this.
var types = typeof (Module).Assembly.GetTypes();

// Parse the original JSON into an array.
var joList = JArray.Parse(json);

// List I want to populate
var listModule = new List<Module>();

foreach (dynamic token in joList)
{
    string name = token.name;

    // Get the actual type.
    var type = types.FirstOrDefault(x=>x.Name == name);

    // If type is not found then continue.
    if (type == null)
        continue;

    // If type is not a subclass of Module, continue.
    if (!type.IsSubclassOf(typeof(Module)))
        continue;

    // Now deserialize that token into the actual type and add it to the list
    listModule.Add(JsonConvert.DeserializeObject(token.ToString(), type));

}

And ReSharper then converted all that code in the foreach loop into this nifty little one-liner.

var listModule = (from dynamic token in joList
    let name = token.name
    let type = types.FirstOrDefault(x => x.Name == (string) name)
    where type != null
    where type.IsSubclassOf(typeof (Module))
    select JsonConvert.DeserializeObject(token.ToString(), type)).Cast<Module>().ToList();

You need http://james.newtonking.com/json for that.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.