How can I use ReadAsAsync<T> with this data schema?

不打扰是莪最后的温柔 提交于 2019-12-08 09:15:32

问题


I am using System.Net.Http.HttpClient, the version currently available in NuGet, to retrieve data from a service in json format. The data roughly looks like this:

{
  "schema": "Listing",
  "data": {
    "key": "28ba648c-de24-45d4-a7d9-70f810cf5438",
    "children": [{
      "kind": "type1",
      "data": {
        "body": "Four score and seven years ago...",
        "parent_id": "2qh3l",
        "report_count": 0,
        "name": "c4j6yeh"
      }
    }, {
      "kind": "type3",
      "data": {
        "domain": "abc.def.com",
        "flagged": true,
        "category": "news",
        "saved": false,
        "id": "t3dz0",
        "created": 1335998011.0
        }
    }]
  }
}

I use HttpContentExtensions.ReadAsAsync<T> to de-serialize that json string into an object graph. The type definitions looks roughly like this:

public class Response
{
    public String schema { get;set; }
    public ListingData data { get;set; }
}

public class ListingData
{
    public string key { get;set; }
    public List<OneItem> children { get;set; }
}

Here's the problem: I desire the type of the items in children to vary depending on the kind property. If kind is "type1" then I want to de-serialize an object of... let's call it Type1 . If kind is "type3" then I want an object of type Type3.

Right now, I can deserialize a List<Type1> or a List<Type3>, but I don't know how to tell the de-serialization logic to distinguish between the two.

I could merge all the properties of the "type1" data object and the "type3" data object into a single .NET Type. But the number of properties is large enough that this gets messy.

If the name of the property in the JSON (in this case data) were different, I could distinguish using that. If, for example, the data looked like this:

    "children": [{
      "kind": "type1",
      "t1data": { ... }
    }, {
      "kind": "type3",
      "t3data": { ... }
    }]

...then I could do something like this in .NET:

public class OneItem
{
    public string kind { get;set; }
    public Type1 t1data { get;set; }
    public Type3 t3data { get;set; }
}

But my data schema doesn't look like that.

Is it possible to choose the type for de-serialization by the content of the data? In other words, look at the value of one property (in this case, kind) to determine how to de-serialize the content for another property (in this case, data).

Or is it possible to inject a filter or transformer that acts on the JSON before ReadAsAsync tries to deserialize it?

If so, How?


回答1:


If you're ok w/ doing some pre-processing on your response and you can use Json.NET, you should be able to do what you want.

Given the following classes:

public class Response
{
    public string schema
    {
        get;
        set;
    }

    public ListingData data
    {
        get;
        set;
    }
}

public class ListingData
{
    public string key
    {
        get;
        set;
    }

    public List<object> children
    {
        get;
        set;
    }
}

public class Type1
{
    public string body
    {
        get;
        set;
    }

    public string parent_id
    {
        get;
        set;
    }

    public int report_count
    {
        get;
        set;
    }

    public string name
    {
        get;
        set;
    }
}

public class Type3
{
    public string domain
    {
        get;
        set;
    }

    public bool flagged
    {
        get;
        set;
    }

    public string category
    {
        get;
        set;
    }

    public bool saved
    {
        get;
        set;
    }

    public string id
    {
        get;
        set;
    }

    public double created
    {
        get;
        set;
    }
}

This test passes:

[Test]
public void RoundTrip()
{
    var response = new Response
                        {
                            schema = "Listing",
                            data = new ListingData
                                        {
                                            key = "28ba648c-de24-45d4-a7d9-70f810cf5438",
                                            children = new List<object>
                                                            {
                                                                new Type1
                                                                    {
                                                                        body = "Four score and seven years ago...",
                                                                        parent_id = "2qh3l",
                                                                        report_count = 0,
                                                                        name = "c4j6yeh"
                                                                    },
                                                                new Type3
                                                                    {
                                                                        domain = "abc.def.com",
                                                                        flagged = true,
                                                                        category = "news",
                                                                        saved = false,
                                                                        id = "t3dz0",
                                                                        created = 1335998011.0
                                                                    }
                                                            }
                                        }
                        };

    var jsonSerializerSettings = new JsonSerializerSettings
                                        {
                                            Formatting = Formatting.Indented,
                                            TypeNameHandling = TypeNameHandling.Objects
                                        };

    string serializedResponse = JsonConvert.SerializeObject(response, jsonSerializerSettings);
    Console.WriteLine(serializedResponse);
    var roundTrippedResponse = JsonConvert.DeserializeObject<Response>(serializedResponse, jsonSerializerSettings);
    Assert.That(roundTrippedResponse.data.children.First().GetType(), Is.EqualTo(typeof(Type1)));
    Assert.That(roundTrippedResponse.data.children.Last().GetType(), Is.EqualTo(typeof(Type3)));
}

The output written to the console is:

{
  "$type": "Test.Response, Test",
  "schema": "Listing",
  "data": {
    "$type": "Test.ListingData, Test",
    "key": "28ba648c-de24-45d4-a7d9-70f810cf5438",
    "children": [
      {
        "$type": "Test.Type1, Test",
        "body": "Four score and seven years ago...",
        "parent_id": "2qh3l",
        "report_count": 0,
        "name": "c4j6yeh"
      },
      {
        "$type": "Test.Type3, Test",
        "domain": "abc.def.com",
        "flagged": true,
        "category": "news",
        "saved": false,
        "id": "t3dz0",
        "created": 1335998011.0
      }
    ]
  }
}

So if you can transform your received response to match that of Json.NET's expected format, this will work.

To piece all of this together, you would need to write a custom MediaTypeFormatter and pass it to the ReadAsAsync<>() call.



来源:https://stackoverflow.com/questions/10417942/how-can-i-use-readasasynct-with-this-data-schema

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!