如何使用自定义JsonConverter将平面JSON映射到嵌套对象?

时间:2018-08-15 17:47:14

标签: c# json.net jsonconvert

我需要将平面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 :更新了代码。它在某些情况下有效,但对于某些(非固定的)属性,在尝试读取JSON时会抛出InvalidCastException(请参阅ReadObject,其中有带异常消息的注释)。例如。我有类似DTO的课程

class FooDto { ... }
class BarDto {
    public string Name { get; set; }
    public FooDto Foo { get; set; }
    ...
}

Name的处理正确,但是尝试读取Foo会导致异常。 最小的可重现示例:https://pastebin.com/2gXNiLMz

0 个答案:

没有答案