【问题标题】:Newtonsoft Json converter for json with filters带有过滤器的 json 的 Newtonsoft Json 转换器
【发布时间】:2020-03-18 16:51:51
【问题描述】:

我正在为这样的 json 编写转换器:

{
    "datatable": {
        "data": [
            [
                "A85002072C",
                "1994-11-15",
                678.9
            ]
        ],
        "columns": [
            {
                "name": "series_id",
                "type": "String"
            },
            {
                "name": "date",
                "type": "Date"
            },
            {
                "name": "value",
                "type": "double"
            }
        ]
    },
    "meta": {
        "next_cursor_id": null
    }
}

目前我的转换器如下所示:

    public class AbsToModelConverter : JsonConverter
    {

        public override bool CanConvert(Type objectType)
        {
            return objectType.Name.Equals("AbsFseModel");
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {

            JArray array = JArray.Load(reader);
            return new QuandlAbsModel
            {
                SeriesId = array[0].ToString(),
                Date = array[1].ToObject<DateTime>(),
                Value = array[2].ToObject<decimal?>()
            };
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var orderItem = value as QuandlAbsModel;
            JArray arra = new JArray();
            arra.Add(orderItem.SeriesId);
            arra.Add(orderItem.Date);
            arra.Add(orderItem.Value);

            arra.WriteTo(writer);
        }
    }

目前可以使用,但是当我使用过滤器时,我的 json 可能包含不完整的数据,例如:

"data":[["1994-11-15",678.9]]

我的 JsonConverter 停止工作,因为没有元素数组 [2] 并且它抛出错误。问题是数据数组中的元素没有名称(我从 Web API 获取 JSON,所以我根本无法更改 json)。有什么方法可以让我的转换器使用过滤器反序列化 json?

我在数据表之后的 json 中有列名,也许这会有所帮助。但我不明白如何使用它们 atm。有什么建议吗?

【问题讨论】:

  • 您能否确保您的 C# 代码中的名称与 JSON 示例中的名称相匹配?我认为AbsFseModel对应dataQuandlAbsModel对应datatable
  • 当您使用过滤器使您的data 数组具有较少的项目时,JSON 中的columns 数组是否也具有较少的项目,对应于data 中剩余的项目?

标签: c# json json.net converters


【解决方案1】:

您不需要JsonConverter

定义类来表示您需要的 JSON 部分:

class APIResponse
{
    public DataTable DataTable { get; set; }
}

class DataTable
{
    public object[][] Data { get; set; }
}

使用JsonConvert.DeserializeObject&lt;T&gt;() 反序列化 JSON:

var parsed = JsonConvert.DeserializeObject<APIResponse>(json);

然后获取您的值:

var rows = parsed.DataTable.Data.Select(r => new QuandLabsModel
{
    SeriesId = Convert.ToString(r[0]),
    Date = Convert.ToDateTime(r[1]),
    Value = Convert.ToDecimal(r[2])
});

【讨论】:

  • 是的,但我使用转换器,因为有时我的 JSON 包含数千行。对我来说,直接转换为模型看起来比转换 json -> 对象数组(数据)-> 模型更快。但需要测试才能确定。无论如何,感谢您的出色回答!
【解决方案2】:

JLRishe 是正确的,您的问题无需自定义转换器即可解决。在许多情况下,这是一个很好的方法。如果您能够在 JSON 序列化器/反序列化器上插入翻译,则它可能比自定义 JsonConverter 更易于编写、理解和维护。它在精神上类似于 Java 世界中使用的“序列化代理模式”。本质上,您是在序列化之前将数据复制到新的序列化特定对象,然后执行相反的操作以重新序列化。

这个问题可以用自定义转换器解决,我写了一个例子来证明它可以解决,但请考虑先使用翻译代理/层。

这个例子是一个概念验证;不是生产就绪的代码。 我几乎没有努力防御格式错误的输入或其他错误。它对不同字段/类型的处理也非常初级——对字段/类型的任何更改都需要对转换器进行更改。随着时间的推移,这种脆弱性可能会导致错误和维护问题。

为了稍微缩小问题范围,我将原始问题的示例 JSON 减少到最低限度:

{
  "datatable": {
    "data": [
      "A85002072C",
      "1994-11-15",
      678.9
    ],
    "columns": [
      {
        "name": "series_id"
      },
      {
        "name": "date"
      },
      {
        "name": "value"
      }
    ]
  }
}

作为参考,这是我要反序列化的 C# 类定义:

public class Model
{
    public string SeriesId { get; set; }
    public DateTime Date { get; set; }
    public Decimal? Value { get; set; }
}

这是概念验证转换器:

public sealed class ModelConverter : JsonConverter
{
    public static readonly ModelConverter Instance = new ModelConverter();

    private ModelConverter() {}

    public override bool CanConvert(Type objectType) => objectType == typeof(Model);

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var obj = JObject.Load(reader);

        var data = (JArray)obj["datatable"]["data"];
        var columns = (JArray)obj["datatable"]["columns"];

        if (data.Count != columns.Count)
            throw new InvalidOperationException("data and columns must contain same number of elements");

        var model = new Model();

        for (int i = 0; i < data.Count; i++)
        {
            // A "switch" works well enough so long as the number of fields is finite and small.
            // There are smarter approaches, but I've kept the implementation basic
            // in order to focus on the core problem that was presented.
            switch (columns[i]["name"].ToString())
            {
                case "series_id":
                    model.SeriesId = data[i].ToString();
                    break;
                case "date":
                    model.Date = data[i].ToObject<DateTime>();
                    break;
                case "value":
                    model.Value = data[i].ToObject<decimal?>();
                    break;
            }
        }

        return model;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var data = new JArray();
        var columns = new JArray();

        var model = (Model)value;

        // Like the "switch" used in deserialization, these "if" blocks are
        // pretty rudimentary. There are better ways, but I wanted to keep
        // this proof-of-concept implementation simple.
        if (model.SeriesId != default(string))
        {
            data.Add(model.SeriesId);
            columns.Add(new JObject(new JProperty("name", "series_id")));
        }

        if (model.Date != default(DateTime))
        {
            data.Add(model.Date.ToString("yyyy-MM-dd"));
            columns.Add(new JObject(new JProperty("name", "date")));
        }

        if (model.Value != default(Decimal?))
        {
            data.Add(model.Value);
            columns.Add(new JObject(new JProperty("name", "value")));
        }

        var completeObj = new JObject();
        completeObj["datatable"] = new JObject();
        completeObj["datatable"]["data"] = data;
        completeObj["datatable"]["columns"] = columns;

        completeObj.WriteTo(writer);
    }
}

我编写了一些单元测试来验证序列化程序。测试基于xUnit.Net:

[Fact]
public void TestDeserializeSampleInputWithAllFields()
{
    var json = File.ReadAllText(BasePath + "sampleinput.json");

    var obj = JsonConvert.DeserializeObject<Model>(json, ModelConverter.Instance);

    Assert.Equal("A85002072C", obj.SeriesId);
    Assert.Equal(new DateTime(1994, 11, 15), obj.Date);
    Assert.Equal(678.9M, obj.Value);
}

[Fact]
public void TestSerializeSampleInputWithAllFields()
{
    var model = new Model
    {
        SeriesId = "A85002072C",
        Date = new DateTime(1994, 11, 15),
        Value = 678.9M,
    };

    var expectedJson = File.ReadAllText(BasePath + "sampleinput.json");

    Assert.Equal(expectedJson, JsonConvert.SerializeObject(model, Formatting.Indented, ModelConverter.Instance));
}

并证明序列化程序在所有字段都不存在的情况下工作:

{
  "datatable": {
    "data": [
      "B72008039G",
      543.2
    ],
    "columns": [
      {
        "name": "series_id"
      },
      {
        "name": "value"
      }
    ]
  }
}
[Fact]
public void TestDeserializeSampleInputWithNoDate()
{
    var json = File.ReadAllText(BasePath + "sampleinput_NoDate.json");

    var obj = JsonConvert.DeserializeObject<Model>(json, ModelConverter.Instance);

    Assert.Equal("B72008039G", obj.SeriesId);
    Assert.Equal(default(DateTime), obj.Date);
    Assert.Equal(543.2M, obj.Value);
}

[Fact]
public void TestSerializeSampleInputWithNoDate()
{
    var model = new Model
    {
        SeriesId = "B72008039G",
        Value = 543.2M,
    };

    var expectedJson = File.ReadAllText(BasePath + "sampleinput_NoDate.json");

    Assert.Equal(expectedJson, JsonConvert.SerializeObject(model, Formatting.Indented, ModelConverter.Instance));
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-12
    • 1970-01-01
    • 2014-07-10
    • 2019-12-13
    相关资源
    最近更新 更多