mskuratowski
mskuratowski

Reputation: 4124

Convert JSON to list with generic types

I got a JSON response like:

{
  "items": [
    {
      "document": {
        "id": "123",
        "title": "title2",
        "description": "Description1",
        "type": "Typ1"
      }
    },
    {
      "document": {
        "id": "456",
        "title": "title2",
        "description": "desctiption2",
        "type": "Type2",
        "Type2Property": "Type2Property"
      }
    }
  ]
}

As you can see above I have two values (just for example) with different properties. In my code, I have two classes.

public class Type1
{
    public string Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public string Type { get; set; }
}

public class Type2
{
    public string Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public string Type { get; set; }
    public string Type2Property {get; set;}
}

Question: How can I create one generic list which combines Type1 and Type2. In the future, I can have more TypeX (with different properties). So, I'd like to parse JSON into a generic list.

Update: I can filter json by the Type property from the JSON.

Upvotes: 0

Views: 135

Answers (1)

Peter Csala
Peter Csala

Reputation: 22849

One way to solve this problem is to create a custom JsonConverter and override its ReadJson method.

I've introduced a couple of helper classes to be able to parse the whole sample json:

public class TopLevel
{
    public MidLevel[] Items { get; set; }
}

public class MidLevel
{
    public IDocument Document { get; set; }

}

[JsonConverter(typeof(DocumentTypeConverter))]
public interface IDocument
{
}
  • I've created an IDocument marker interface. If you wish you can use abstract class.
  • I've decorated the interface with a JsonConverterAttribute and specified there the custom converter.
  • I've changed the Type1 and Type2 classes to implement this interface:
public class Type1 : IDocument
{
    ...
    public string Type { get; set; }
}

public class Type2 : IDocument
{
    ...
    public string Type { get; set; }
    public string Type2Property { get; set; }
}

The DocumentTypeConverter naive implementation would look like this:
(Obviously you can make more type-safe)

public class DocumentTypeConverter : 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)
    {
        var jObject = JObject.Load(reader);
        switch (jObject["type"].Value<string>())
        {
            case "Typ1":
                {
                    var obj = new Type1();
                    serializer.Populate(jObject.CreateReader(), obj);
                    return obj;
                }
            case "Type2":
                {
                    var obj = new Type2();
                    serializer.Populate(jObject.CreateReader(), obj);
                    return obj;
                }
            default:
                throw new Exception();
        }
    }

    public override bool CanConvert(Type objectType) 
        => objectType == typeof(IDocument);
}
  • The CanConvert tells us that this convert can be used against IDocuments.
  • The ReadJson branches its logic based on the "type" field.
  • The actual conversion done with the Populate instead of JsonCovert.Deserialize to avoid infinite recursion.

Finally, the usage is that simple:

static void Main(string[] args)
{
    var sampleJson = File.ReadAllText("sample.json");
    var sample = JsonConvert.DeserializeObject<TopLevel>(sampleJson);
    Console.ReadLine();
}

Upvotes: 1

Related Questions