Наиболее вероятное решение заключается в том, что вы предлагаете создать собственный конвертер для анализа нужных моделей.
В этом случае пользовательский конвертер должен иметь возможность считывать вложенные пути.
Это должно обеспечить простой обходной путь.
public class NestedJsonPathConverter : JsonConverter {
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer) {
JObject jo = JObject.Load(reader);
var properties = jo.Properties();
object targetObj = existingValue ?? Activator.CreateInstance(objectType);
var resolver = serializer.ContractResolver as DefaultContractResolver;
foreach (PropertyInfo propertyInfo in objectType.GetProperties()
.Where(p => p.CanRead && p.CanWrite)) {
var attributes = propertyInfo.GetCustomAttributes(true).ToArray();
if (attributes.OfType<JsonIgnoreAttribute>().Any())
continue;
var jsonProperty = attributes.OfType<JsonPropertyAttribute>().FirstOrDefault();
var jsonPath = (jsonProperty != null ? jsonProperty.PropertyName : propertyInfo.Name);
if (resolver != null) {
jsonPath = resolver.GetResolvedPropertyName(jsonPath);
}
JToken token = jo.SelectToken(jsonPath) ?? GetTokenCaseInsensitive(properties, jsonPath);
if (token != null && token.Type != JTokenType.Null) {
object value = token.ToObject(propertyInfo.PropertyType, serializer);
propertyInfo.SetValue(targetObj, value, null);
}
}
return targetObj;
}
JToken GetTokenCaseInsensitive(IEnumerable<JProperty> properties, string jsonPath) {
var parts = jsonPath.Split('.');
var property = properties.FirstOrDefault(p =>
string.Equals(p.Name, parts[0], StringComparison.OrdinalIgnoreCase)
);
for (var i = 1; i < parts.Length && property != null && property.Value is JObject; i++) {
var jo = property.Value as JObject;
property = jo.Properties().FirstOrDefault(p =>
string.Equals(p.Name, parts[i], StringComparison.OrdinalIgnoreCase)
);
}
if (property != null && property.Type != JTokenType.Null) {
return property.Value;
}
return null;
}
public override bool CanConvert(Type objectType) {
//Check if any JsonPropertyAttribute has a nested property name {name}.{sub}
return objectType
.GetProperties()
.Any(p =>
p.CanRead
&& p.CanWrite
&& p.GetCustomAttributes(true)
.OfType<JsonPropertyAttribute>()
.Any(jp => (jp.PropertyName ?? p.Name).Contains('.'))
);
}
public override bool CanWrite {
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
throw new NotImplementedException();
}
}
Исходная структура класса теперь не нуждается в изменении, только со свойствами, которые требуют пользовательских путей, которые должны быть украшены JsonPropertyAttribute
, который указывает путь для заполнения свойства.
В этом примере
public class Customer {
public Guid Id { get; set; }
public string Country { get; set; }
[JsonProperty("_embedded.company")]
public LegalPerson Company { get; set; }
}
public class LegalPerson {
public string Name { get; set; }
public string IndustrySector { get; set; }
public string Owner { get; set; }
[JsonProperty("_embedded.emailAddresses")]
public ContactInfo[] EmailAddresses { get; set; }
[JsonProperty("_embedded.phoneNumbers")]
public ContactInfo[] PhoneNumbers { get; set; }
}
Просто включите конвертер при необходимости.
var settings = new JsonSerializerSettings {
ContractResolver = new DefaultContractResolver {
NamingStrategy = new CamelCaseNamingStrategy()
}
};
settings.Converters.Add(new NestedJsonPathConverter());
var customer = JsonConvert.DeserializeObject<Customer>(json, settings);
Двумя важными частями кода является метод GetTokenCaseInsensitive
, который ищет запрошенный токен и позволяет использовать вложенные пути без учета регистра.
JToken GetTokenCaseInsensitive(IEnumerable<JProperty> properties, string jsonPath) {
var parts = jsonPath.Split('.');
var property = properties.FirstOrDefault(p =>
string.Equals(p.Name, parts[0], StringComparison.OrdinalIgnoreCase)
);
for (var i = 1; i < parts.Length && property != null && property.Value is JObject; i++) {
var jo = property.Value as JObject;
property = jo.Properties().FirstOrDefault(p =>
string.Equals(p.Name, parts[i], StringComparison.OrdinalIgnoreCase)
);
}
if (property != null && property.Type != JTokenType.Null) {
return property.Value;
}
return null;
}
и переопределенные CanConvert
, которые будут проверять любые свойства, имеют вложенные пути
public override bool CanConvert(Type objectType) {
//Check if any JsonPropertyAttribute has a nested property name {name}.{sub}
return objectType
.GetProperties()
.Any(p =>
p.CanRead
&& p.CanWrite
&& p.GetCustomAttributes(true)
.OfType<JsonPropertyAttribute>()
.Any(jp => (jp.PropertyName ?? p.Name).Contains('.'))
);
}