使用 Json.Net 反序列化嵌套属性而不使用数据注释
Deserialize nested properties using Json.Net without using data annotations
我有一个从多个 API 获取数据的应用程序。
为了尽量减少 类 的数量,我需要映射到每个 属性。
我实现了一个简单的 json.net ContractResolver
。
但是,当我尝试将 属性 映射到子 属性 时,我 运行 遇到了一些麻烦。
JSON 格式 1:
{
"event_id": 123,
"event_name": "event1",
"start_date": "2018-11-30",
"end_date": "2018-12-04",
"participants": {
"guests": [
{
"guest_id": 143,
"first_name": "John",
"last_name": "Smith",
},
{
"guest_id": 189,
"first_name": "Bob",
"last_name": "Duke",
}
]
}
}
JSON 格式 2:
{
"name": "event2",
"from": "2017-05-05",
"to": "2017-05-09",
"city":"Some other city",
"country":"US",
"guests": [
{
"email":"jane@smith.com",
"firstName":"Jane",
"lastName":"Smith",
"telephone":"1-369-81891"
}
],
}
这是我的模型类:
public class Event
{
public int EventId { get; set; }
public string EventName { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public List<Guest> Guests { get; set; }
}
public class Guest
{
public string GuestId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
我的解析器:
public class EventResolver : DefaultContractResolver
{
private Dictionary<string,string> PropertyMappings { get; set; }
public EventResolver()
{
this.PropertyMappings = new Dictionary<string, string>
{
{"EventId", "event_id"},
{"StartDate", "start_date" },
{"EndDate", "end_date" },
{"EventName", "event_name" },
{"Guests", "participants.guests"}
};
}
protected override JsonContract CreateContract(Type objectType)
{
return base.CreateContract(objectType);
}
protected override string ResolvePropertyName(string propertyName)
{
var resolved = this.PropertyMappings.TryGetValue(propertyName, out var resolvedName);
return (resolved) ? resolvedName : base.ResolvePropertyName(propertyName);
}
}
我知道路径不能替代 属性 名称。怎么能做到这一点?
我认为你过度设计了它,当你需要支持越来越多的格式和制作人时,它会变得一团糟。试想一下,如果您有 15 个不同格式的事件生产者,您的解析器会是什么样子?
您需要的是为您的域创建一组 类,它适合您的域和需要。
public class Event
{
public int EventId { get; set; }
public string EventName { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public List<Guest> Guests { get; set; }
}
public class Guest
{
public string GuestId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
public interface IEventProvider
{
Event[] GetEvents();
}
然后为每个外部制作者创建一组 类 并将其映射到您的域 类,例如使用 AutoMapper 配置文件或手动映射。
namespace YourCompany.EventProvider.Api1
{
// just an example with json2sharp, use data annotations if you want
public class Guest
{
public int guest_id { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
}
public class Participants
{
public List<Guest> guests { get; set; }
}
public class RootObject
{
public int event_id { get; set; }
public string event_name { get; set; }
public string start_date { get; set; }
public string end_date { get; set; }
public Participants participants { get; set; }
}
public class Api1EventProvider : IEventProvider
{
public Event[] GetEvents()
{
RootObject[] api1Response = GetFromApi();
return _mapper.Map<RootObject[], Event[]>(api1Response);
}
}
}
是的,我会更多类。但是这段代码会更好,更具可读性和可维护性;与创建解析器相比,您将花费更少的时间来创建它;未来的开发者不会在每次制作人改变他的 API.
时哭泣
代码质量不在于少创造类。
我认为解析器的想法不会奏效,因为您要重新映射的不仅仅是 属性 名称——您还试图反序列化为 class 结构始终匹配 JSON 的形状。这份工作更适合一组 JsonConverter
s.
基本方法如下:
- 为 JSON 变化的每个模型 class 创建一个
JsonConverter
。
- 在
ReadJson
方法中从 reader. 加载一个 JObject
- 通过查找众所周知的 属性 名称来检测您拥有的格式,这些名称始终存在于该格式中。例如,如果您可以依赖
event_id
始终出现在第一种格式中,这是检测它的好方法,因为您知道第二种格式没有 属性。如果需要,您可以根据是否存在多个属性进行此检查;关键只是使用一些只以一种格式出现而没有其他格式的组合。 (或者如果您提前知道期望的格式,您可以简单地参数化转换器,即在构造函数中传递格式标志。)
- 格式已知后,从
JObject
填充模型。
对于您问题中显示的 Event
模型,转换器可能如下所示:
public class EventConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Event);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
Event evt = new Event();
JObject obj = JObject.Load(reader);
if (obj["event_id"] != null)
{
// JSON format #1
evt.EventId = (int)obj["event_id"];
evt.EventName = (string)obj["event_name"];
evt.StartDate = (DateTime)obj["start_date"];
evt.EndDate = (DateTime)obj["end_date"];
evt.Guests = obj.SelectToken("participants.guests").ToObject<List<Guest>>(serializer);
}
else if (obj["name"] != null)
{
// JSON format #2
evt.EventName = (string)obj["name"];
evt.StartDate = (DateTime)obj["from"];
evt.EndDate = (DateTime)obj["to"];
evt.Guests = obj["guests"].ToObject<List<Guest>>(serializer);
}
else
{
throw new JsonException("Unknown format for Event");
}
return evt;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
与 Guest
模型类似,我们可能有这个 JsonConverter
:
public class GuestConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Guest);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
Guest guest = new Guest();
JObject obj = JObject.Load(reader);
if (obj["guest_id"] != null)
{
// JSON format #1
guest.GuestId = (string)obj["guest_id"];
guest.FirstName = (string)obj["first_name"];
guest.LastName = (string)obj["last_name"];
}
else if (obj["email"] != null)
{
// JSON format #2
guest.FirstName = (string)obj["firstName"];
guest.LastName = (string)obj["lastName"];
guest.Email = (string)obj["email"];
}
else
{
throw new JsonException("Unknown format for Guest");
}
return guest;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
要使用转换器,请将它们添加到 JsonSerializerSettings
对象的 Converters
集合中,然后像这样将设置传递给 DeserializeObject()
:
var settings = new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new EventConverter(), new GuestConverter() }
};
var evt = JsonConvert.DeserializeObject<Event>(json, settings);
演示 fiddle:https://dotnetfiddle.net/KI82KB
我有一个从多个 API 获取数据的应用程序。
为了尽量减少 类 的数量,我需要映射到每个 属性。
我实现了一个简单的 json.net ContractResolver
。
但是,当我尝试将 属性 映射到子 属性 时,我 运行 遇到了一些麻烦。
JSON 格式 1:
{
"event_id": 123,
"event_name": "event1",
"start_date": "2018-11-30",
"end_date": "2018-12-04",
"participants": {
"guests": [
{
"guest_id": 143,
"first_name": "John",
"last_name": "Smith",
},
{
"guest_id": 189,
"first_name": "Bob",
"last_name": "Duke",
}
]
}
}
JSON 格式 2:
{
"name": "event2",
"from": "2017-05-05",
"to": "2017-05-09",
"city":"Some other city",
"country":"US",
"guests": [
{
"email":"jane@smith.com",
"firstName":"Jane",
"lastName":"Smith",
"telephone":"1-369-81891"
}
],
}
这是我的模型类:
public class Event
{
public int EventId { get; set; }
public string EventName { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public List<Guest> Guests { get; set; }
}
public class Guest
{
public string GuestId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
我的解析器:
public class EventResolver : DefaultContractResolver
{
private Dictionary<string,string> PropertyMappings { get; set; }
public EventResolver()
{
this.PropertyMappings = new Dictionary<string, string>
{
{"EventId", "event_id"},
{"StartDate", "start_date" },
{"EndDate", "end_date" },
{"EventName", "event_name" },
{"Guests", "participants.guests"}
};
}
protected override JsonContract CreateContract(Type objectType)
{
return base.CreateContract(objectType);
}
protected override string ResolvePropertyName(string propertyName)
{
var resolved = this.PropertyMappings.TryGetValue(propertyName, out var resolvedName);
return (resolved) ? resolvedName : base.ResolvePropertyName(propertyName);
}
}
我知道路径不能替代 属性 名称。怎么能做到这一点?
我认为你过度设计了它,当你需要支持越来越多的格式和制作人时,它会变得一团糟。试想一下,如果您有 15 个不同格式的事件生产者,您的解析器会是什么样子?
您需要的是为您的域创建一组 类,它适合您的域和需要。
public class Event
{
public int EventId { get; set; }
public string EventName { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public List<Guest> Guests { get; set; }
}
public class Guest
{
public string GuestId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
public interface IEventProvider
{
Event[] GetEvents();
}
然后为每个外部制作者创建一组 类 并将其映射到您的域 类,例如使用 AutoMapper 配置文件或手动映射。
namespace YourCompany.EventProvider.Api1
{
// just an example with json2sharp, use data annotations if you want
public class Guest
{
public int guest_id { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
}
public class Participants
{
public List<Guest> guests { get; set; }
}
public class RootObject
{
public int event_id { get; set; }
public string event_name { get; set; }
public string start_date { get; set; }
public string end_date { get; set; }
public Participants participants { get; set; }
}
public class Api1EventProvider : IEventProvider
{
public Event[] GetEvents()
{
RootObject[] api1Response = GetFromApi();
return _mapper.Map<RootObject[], Event[]>(api1Response);
}
}
}
是的,我会更多类。但是这段代码会更好,更具可读性和可维护性;与创建解析器相比,您将花费更少的时间来创建它;未来的开发者不会在每次制作人改变他的 API.
时哭泣
代码质量不在于少创造类。
我认为解析器的想法不会奏效,因为您要重新映射的不仅仅是 属性 名称——您还试图反序列化为 class 结构始终匹配 JSON 的形状。这份工作更适合一组 JsonConverter
s.
基本方法如下:
- 为 JSON 变化的每个模型 class 创建一个
JsonConverter
。 - 在
ReadJson
方法中从 reader. 加载一个 - 通过查找众所周知的 属性 名称来检测您拥有的格式,这些名称始终存在于该格式中。例如,如果您可以依赖
event_id
始终出现在第一种格式中,这是检测它的好方法,因为您知道第二种格式没有 属性。如果需要,您可以根据是否存在多个属性进行此检查;关键只是使用一些只以一种格式出现而没有其他格式的组合。 (或者如果您提前知道期望的格式,您可以简单地参数化转换器,即在构造函数中传递格式标志。) - 格式已知后,从
JObject
填充模型。
JObject
对于您问题中显示的 Event
模型,转换器可能如下所示:
public class EventConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Event);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
Event evt = new Event();
JObject obj = JObject.Load(reader);
if (obj["event_id"] != null)
{
// JSON format #1
evt.EventId = (int)obj["event_id"];
evt.EventName = (string)obj["event_name"];
evt.StartDate = (DateTime)obj["start_date"];
evt.EndDate = (DateTime)obj["end_date"];
evt.Guests = obj.SelectToken("participants.guests").ToObject<List<Guest>>(serializer);
}
else if (obj["name"] != null)
{
// JSON format #2
evt.EventName = (string)obj["name"];
evt.StartDate = (DateTime)obj["from"];
evt.EndDate = (DateTime)obj["to"];
evt.Guests = obj["guests"].ToObject<List<Guest>>(serializer);
}
else
{
throw new JsonException("Unknown format for Event");
}
return evt;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
与 Guest
模型类似,我们可能有这个 JsonConverter
:
public class GuestConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Guest);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
Guest guest = new Guest();
JObject obj = JObject.Load(reader);
if (obj["guest_id"] != null)
{
// JSON format #1
guest.GuestId = (string)obj["guest_id"];
guest.FirstName = (string)obj["first_name"];
guest.LastName = (string)obj["last_name"];
}
else if (obj["email"] != null)
{
// JSON format #2
guest.FirstName = (string)obj["firstName"];
guest.LastName = (string)obj["lastName"];
guest.Email = (string)obj["email"];
}
else
{
throw new JsonException("Unknown format for Guest");
}
return guest;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
要使用转换器,请将它们添加到 JsonSerializerSettings
对象的 Converters
集合中,然后像这样将设置传递给 DeserializeObject()
:
var settings = new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new EventConverter(), new GuestConverter() }
};
var evt = JsonConvert.DeserializeObject<Event>(json, settings);
演示 fiddle:https://dotnetfiddle.net/KI82KB