Как отобразить плоский JSON на вложенный объект, используя пользовательский JsonConverter?
Мне нужно отобразить плоский JSON на вложенный объект, где плоские свойства помечены как Flat
приписывать. Например, отображение
{
"x": 1,
"y": 2,
"z": 3
}
к следующей модели
public class Foo {
public int X { get; set; }
public int Y { get; set; }
}
public class Bar {
[JsonFlat]
public Foo Foo { get; set; }
public int Z { get; set; }
}
должен дать Bar { Foo { X = 1, Y = 2 }, Z = 3 }
, Я реализовал преобразование сложного объекта в JSON, но не наоборот:
public class FlatJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartObject();
WriteObject(writer, value, serializer);
writer.WriteEndObject();
}
private void WriteObject(JsonWriter writer, object value, JsonSerializer serializer)
{
var t = JToken.FromObject(value);
var flatNames = new List<string>();
if (t.Type == JTokenType.Object)
{
var type = value.GetType();
foreach (var propertyInfo in type.GetProperties())
{
var flatAttr = propertyInfo.GetCustomAttribute(typeof(JsonFlatAttribute));
if (flatAttr != null) flatNames.Add(propertyInfo.Name.ToLower());
}
foreach (var token in t.Children())
{
if (token.Type == JTokenType.Property && flatNames.Contains(((JProperty) token).Name.ToLower()))
{
foreach (var child in token.Children())
{
WriteObject(writer, child.Value<object>(), serializer);
}
}
else
{
WriteObject(writer, token.Value<object>(), serializer);
}
}
}
else
{
t.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var value = objectType.GetConstructor(new Type[] { }).Invoke(new object[] { });
return ReadObject(objectType, jObject, value);
}
private object ReadObject(Type objectType, JObject jObject, object value)
{
foreach (var propertyInfo in objectType.GetProperties())
{
var flatAttr = propertyInfo.GetCustomAttribute(typeof(JsonFlatAttribute));
if (flatAttr != null)
{
var obj = propertyInfo.PropertyType.GetConstructor(new Type[] { }).Invoke(new object[] { });
foreach (var property in obj.GetType().GetProperties())
{
var method = jObject.GetType().GetMethod("Value").MakeGenericMethod(property.PropertyType);
var val = method.Invoke(jObject, new object[] { property.Name.ToLower() });
property.SetValue(obj, val);
}
propertyInfo.SetValue(value, obj);
}
else
{
var method = jObject.GetType().GetMethod("Value").MakeGenericMethod(propertyInfo.PropertyType);
var val = method.Invoke(jObject, new object[] { propertyInfo.Name.ToLower() }); // <- System.InvalidCastException: Cannot cast JObject to JToken.
propertyInfo.SetValue(value, val);
}
}
return value;
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override bool CanRead => true;
public override bool CanWrite => true;
}
UPD: обновил код. В некоторых случаях это работает, но для некоторых (не плоских) свойств InvalidCastException
при попытке прочитать JSON (см. ReadObject
есть комментарий с сообщением об исключении). Например, у меня есть класс DTO, как
class FooDto { ... }
class BarDto {
public string Name { get; set; }
public FooDto Foo { get; set; }
...
}
Name
обрабатывается правильно, но пытается прочитать Foo
приводит к исключению. Минимальный воспроизводимый пример: https://pastebin.com/2gXNiLMz