CustomJsonConverter使用一个可能是多个自定义对象的字段反序列化JSON

时间:2017-07-17 19:06:36

标签: c# json json.net

我正在与各种端点上的API进行通信。所有回复都在meta中有一些共享字段,然后是一个data对象,可以包含任意数量的自定义对象。

一些例子: 1)返回data的有效负载包含accounts[]

{
"meta": {
    "timestamp": "2017-07-14T12:59:34-04:00",
    "code": 200
},
"data": {
    "accounts": [
        {
            "address": "Main Street",
            "country": "US",
            "email": "1@23.com",
            "city": "MyTown",
            "accountName": "MyAccount",
            "account": 123456,
            "contact": ""
        }
    ]
}
}

2)有效负载返回data包含shipments[]

{
"meta": {
    "timestamp": "2017-07-14T13:46:42-04:00",
    "code": 400
},
"data": {
    "shipments": [
        {
            "distributionCenter": "123",
            "packages": [
                {
                    "packageDetails": {},
                    "consigneeAddress": {},
                    "errors": [
                        {
                            "errorCode": "VALIDATION_ERROR",
                            "errorId": 0,
                            "errorMessage": "INVALID_LOGIN"
                        }
                    ],
            ],
            "status": {
                "timestamp": "2017-07-14T13:46:42-0400",
                "numAccepted": 0,
                "numRejected": 1,
                "code": "ERROR"
            },
        }
    ]
}
}

当我点击shipments[]端点时,我需要有shipments的访问权限,当遇到accounts[]端点时,我需要访问accounts。这样data.accounts[0].email可以访问。

执行此操作的hack方式似乎是创建一个具有所有可能对象类型的Data类,因此在序列化/反序列化发生时,那些不存在的类只是null。

public class ApiResponse
{
    public Meta meta { get; set; }
    public Data data { get; set; }
}

public class Meta
{
    public string code { get; set; }
    public DateTime timestamp { get; set; }
}

public class Data
{
    public Account[] accounts { get; set; }
    public Shipment[] shipments { get; set; }
}

但我想知道是否有办法用Newtonsoft JSON自定义JsonConverter来做到这一点。 Sa Data类看起来像:

[JsonConverter(typeof(DataConverter))]
public class Data
{
    \\not sure what to put here...
}

有关如何让ReadJson识别自定义对象的任何输入?或者在其他json.net上实现这个目标的方式?

class DataConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(Data));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.Value.GetType() == Shipment)  
        /// this throws error "not valid type in this context"


        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}
}

4 个答案:

答案 0 :(得分:3)

我会重新编写类以提供不同的data类型。首先,我只需要一个通用ApiResponse<T>,如下所示:

public class ApiResponse<T> where T : class {
    public Meta Meta { get; set; }
    public T Data { get; set; }
}

public class AccountData {
    public Account[] Accounts { get; set; }
}

public class ShipmentData {
    public Shipment[] Shipments { get; set; }
}
  

不要对数据的外观有什么看法

当您遇到特定的终点时,您就会知道将返回什么API。如果您要求账户,它不会给您发货或其他东西,反之亦然。如果可用,请查看API文档。

此外,您可以简单地创建一个通用API请求方法,以询问不同类型的数据,即AccountData,ShipmentData等。

ApiResponse<T> MakeApiRequest<T>(string apiUrl) where T : class {
    // Call API the way you want and get JSON response
    var jsonResponse = "{ \"data\": { \"accounts\": [ { \"address\": \"Main Street\" } ] } }"; // Example data hard-coded
    // Desrialize JSON response into ApiResponse<T>
    var apiResponse = JsonConvert.DeserializeObject(jsonResponse, typeof(ApiResponse<T>)) as ApiResponse<T>;
    return apiResponse;
}

使用此方法调用端点:

var accountApiResponse = MakeApiRequest<AccountData>("<accounts_api_url>");
var shipmentApiResponse = MakeApiRequest<ShipmentData>("<shipments_api_url>");

答案 1 :(得分:1)

您需要将任何特定数据移动到这样的子类:

public class AccountData
{
    public Account[] accounts { get; set; }
}

public class ShipmentData
{
    public Shipment[] shipments { get; set; }
}

从那里你可以从使用一个响应类到一个具有公共数据的根的抽象,以及具有如下特定信息的两个具体类:

public abstract class ApiResponse
{
    public Meta meta { get; set; }
}

public class AccountApiResponse : ApiResponse
{
    public AccountData data { get; set; }
}

public class ShipmentApiResponse : ApiResponse
{
    public ShipmentData data { get; set; }
}

当您拨打帐户终端时,可以通过以下方式检索您的数据:

var accountResponse = JsonConvert.DeserializeObject<AccountApiResponse>(json);

对于货件:

var shipmentResponse = JsonConvert.DeserializeObject<ShipmentApiResponse>(json);

希望它有所帮助。

答案 2 :(得分:0)

如果使Data类只是一个对象数组

,该怎么办?
public class Data
{
    public object[] ResponsePayload {get;set;}
}

答案 3 :(得分:0)

您可以直接从HttpClient

执行此操作
var myResponsePayload = await response.Content.ReadAsAsync<MyTypeHere>().ConfigureAwait(false);

当您点击API时,他们会以这种格式进入。只需确保您要在班级中设置的属性与JSON中的内容对齐。

如果您没有访问网络服务,而是从文件或数据库中读取数据,我建议Newtonsoft Json,这是一个广泛使用的用于json反序列化的NuGet包。

你最终可能会遇到这样的事情:

var results = _serializer.Deserialize<IEnumerable<MyBingyMathob>>(reader);

只要您序列化的类具有公共setter的公共可访问属性,这将有效。如果您不想要所有数据,则会删除丢失的字段。