Newtonsoft Json 转换器 json 带过滤器
Newtonsoft Json converter for json with filters
我正在为 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] 并抛出错误。问题是数据数组中的元素没有名称(我从网络 API 获得 JSON,所以我根本无法更改 json)。有什么方法可以让我的转换器反序列化 json 使用过滤器吗?
我的 json 中有列名在数据 table 之后,也许这会有所帮助。但我不明白如何使用它们的 atm。有什么建议吗?
您不需要 JsonConverter
为此。
定义 类 来表示您需要的 JSON 部分:
class APIResponse
{
public DataTable DataTable { get; set; }
}
class DataTable
{
public object[][] Data { get; set; }
}
使用JsonConvert.DeserializeObject<T>()
反序列化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])
});
JLRishe 是正确的,您的问题无需自定义转换器即可解决。在许多情况下,这是一个很好的方法。如果您能够在 JSON serializer/deserializer 上插入翻译,它 可能 比自定义 JsonConverter 更易于编写、理解和维护。它在本质上类似于 Java 世界中使用的 "serialization proxy pattern"。本质上,您是在序列化之前将数据复制到一个新的特定于序列化的对象,然后执行相反的操作以重新序列化。
这个问题可以用自定义转换器解决,我写了一个例子来证明它可以解决,但请考虑先使用翻译 proxy/layer。
这个例子是一个概念验证;不是生产就绪代码。 我很少努力防止格式错误的输入或其他错误。它对不同 fields/types 的处理也非常初级——对 fields/types 的任何更改都需要对转换器进行更改。随着时间的推移,这种脆弱性可能会导致错误和维护问题。
为了缩小问题范围,我将原始问题的样本 JSON 减少到最低限度:
{
"datatable": {
"data": [
"A85002072C",
"1994-11-15",
678.9
],
"columns": [
{
"name": "series_id"
},
{
"name": "date"
},
{
"name": "value"
}
]
}
}
作为参考,这是我反序列化为的 C# class 定义:
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));
}
我正在为 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] 并抛出错误。问题是数据数组中的元素没有名称(我从网络 API 获得 JSON,所以我根本无法更改 json)。有什么方法可以让我的转换器反序列化 json 使用过滤器吗?
我的 json 中有列名在数据 table 之后,也许这会有所帮助。但我不明白如何使用它们的 atm。有什么建议吗?
您不需要 JsonConverter
为此。
定义 类 来表示您需要的 JSON 部分:
class APIResponse
{
public DataTable DataTable { get; set; }
}
class DataTable
{
public object[][] Data { get; set; }
}
使用JsonConvert.DeserializeObject<T>()
反序列化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])
});
JLRishe 是正确的,您的问题无需自定义转换器即可解决。在许多情况下,这是一个很好的方法。如果您能够在 JSON serializer/deserializer 上插入翻译,它 可能 比自定义 JsonConverter 更易于编写、理解和维护。它在本质上类似于 Java 世界中使用的 "serialization proxy pattern"。本质上,您是在序列化之前将数据复制到一个新的特定于序列化的对象,然后执行相反的操作以重新序列化。
这个问题可以用自定义转换器解决,我写了一个例子来证明它可以解决,但请考虑先使用翻译 proxy/layer。
这个例子是一个概念验证;不是生产就绪代码。 我很少努力防止格式错误的输入或其他错误。它对不同 fields/types 的处理也非常初级——对 fields/types 的任何更改都需要对转换器进行更改。随着时间的推移,这种脆弱性可能会导致错误和维护问题。
为了缩小问题范围,我将原始问题的样本 JSON 减少到最低限度:
{
"datatable": {
"data": [
"A85002072C",
"1994-11-15",
678.9
],
"columns": [
{
"name": "series_id"
},
{
"name": "date"
},
{
"name": "value"
}
]
}
}
作为参考,这是我反序列化为的 C# class 定义:
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));
}