1

So I have an API and front-end that I am developing, and I need a way to deserialize JSON when one of the values could be a List of multiple different types. Currently I'm deserializing it into a List<dynamic> but that's a huge pain to work with in my context.

public class WorkbenchAPI   
{
    public string status { get; set; }
    public HttpStatusCode code { get; set; }
    public string error { get; set; }
    public string guid { get; set; }
    public List<dynamic> results { get; set; }
}

Sample JSON

{
    "status": "ok",
    "code": 200,
    "error": null,
    "guid": "1234",
    "results": [
        {
            "SamAccountName": "dizbuster",
            "CN": "Buster, Diz",
            "EmailAddress": "dizbuster@whatever.com",
        }
    ]
}

In the above example JSON, results should be deserialized into type List<ClassA> for example.

{
    "status": "ok",
    "code": 200,
    "error": null,
    "guid": "1234",
    "results": [
        {
            "data": "127.0.0.1",
            "owner": "dizbuster",
            "email": "dizbuster@whatever.com",
        },
        {
            "data": "192.168.0.1",
            "owner": "dizbuster",
            "email": "dizbuster@whatever.com",
        }
    ]
}

Where as in this sample, results should be deserialized into List<ClassB>.

Some of the field names may show up in different types, but some are not. Functionally they are two different types representing the outputs from two separate API calls, so I'd like to have it deserialize into specific object types as opposed to using dynamic or a monolithic object that contains every possible field.

Currently I'm taking the List<dynamic> and serializing it back into a temporary JSON string, then deserializing it again into the List<ClassA> or whatever. This seems like a bad way to do it, and is cumbersome to work with.

Is there a better way to structure this or to handle the serialization/deserialization? I have full control over both ends so it's not too difficult for me to alter the JSON as well if needed. Also, I know that a particular API call will return JSON that looks like this, while another API call will return JSON that looks like that. Each case should result in a differently typed list

dbc
  • 104,963
  • 20
  • 228
  • 340
dizbuster
  • 67
  • 7
  • Assuming you can't change the JSON, do [Deserializing polymorphic json classes without type information using json.net](https://stackoverflow.com/q/19307752/3744182) and/or [How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?](https://stackoverflow.com/q/8030538/3744182) meet your needs? – dbc Oct 31 '19 at 20:11
  • How about `let myObj = JSON.parse(response); let myList = myObj.results;`? Which would return a JS array (vs. some strongly typed `List<>`). Wouldn't that suffice? – paulsm4 Oct 31 '19 at 20:11
  • *Functionally they are two different types representing the outputs from two separate API calls* -- does that mean you know the `results` type in advance? – dbc Oct 31 '19 at 20:12
  • Because if you know the type in advance, changing `WorkbenchAPI` to a generic `WorkbenchAPI { public List results { get; set; } // ... ` is the way to go. – dbc Oct 31 '19 at 20:22
  • @dbc I know what the JSON structure will be yes, for a given type. I know that a particular API call will return JSON that looks like *this*, while another API call will return JSON that looks like *that*. Each case should result in a differently typed list – dizbuster Oct 31 '19 at 20:23
  • Then why not use a generic root class? – dbc Oct 31 '19 at 20:29
  • Also thanks for the links, I'm going to try the polymorphic structure and see if that works better. – dizbuster Oct 31 '19 at 20:29

1 Answers1

1

Since you know in advance what type of data is being returned in "results" for each API call, you can make your WorkbenchAPI class generic:

public abstract class WorkbenchAPI
{
    protected abstract IReadOnlyCollection<dynamic> GetResults();

    public string status { get; set; }
    public HttpStatusCode code { get; set; }
    public string error { get; set; }
    public string guid { get; set; }
    public IReadOnlyCollection<dynamic> results => GetResults();
}

public class WorkbenchAPI<T> : WorkbenchAPI where T : class
{
    protected override IReadOnlyCollection<dynamic> GetResults() => results;
    public new List<T> results { get; set; } = new List<T>();
}

Notes:

  • I extracted the common properties into an abstract base class to allow them to be accessed by pre-existing non-generic code that does not care about the specific type of result.

  • I also added a IReadOnlyCollection<dynamic> results to the base class to ease integration with pre-existing code, e.g.:

    // Deserialize to the concrete, known type
    var root = JsonConvert.DeserializeObject<WorkbenchAPI<ClassA>>(json);
    
    // Access the results in a type-safe manner:
    var accounts = root.results.Select(r => r.SamAccountName).ToList();
    
    // Upcast to the base class in code where we don't need to know about the specific result type.
    // E.g.: accessing root properties like guid or getting the result count.
    WorkbenchAPI baseRoot = root;
    var count = baseRoot.results.Count;
    var guid = baseRoot.guid;
    

    However, adding access to the results as a non-generic read-only collection of dynamic objects is optional, and could be removed if your legacy code can be rewritten to be generic.

Demo fiddle here: https://dotnetfiddle.net/lbTs1z

dbc
  • 104,963
  • 20
  • 228
  • 340
  • This turned out to be exactly what I needed. I haven't used `` in that matter before but after reading the fiddle and playing around with it, I understand it now and met my needs perfectly. – dizbuster Nov 01 '19 at 15:05