如何实现json的自定义序列化,将类型附加到属性名称?

时间:2017-08-02 12:45:06

标签: c# json json.net

我正在尝试使用newtonsoft.json将对象序列化为JSON。唯一的事情是我不能将json类型附加到字段名称。考虑这个例子:

var item = new {
    value = "value",
    data = new []{"str", "str"},
    b = true
};

我想将其转换为

{
    "value.string" : "value",
    "data.array" : ["str", "str"],
    "b.bool" : true
}

或类似的东西。我们的想法是将json类型(不是c#类型)附加到json字段。我不想附加C#类型的原因是因为它可能很复杂(有时类型是匿名的,有时它是IEnumerable等)。

我见过许多可以转换为C#类型的解决方案,例如实现IContractResolver。不幸的是,这种情况并不适用。

我也不知道我之前会转换的类型。

我能找到的最接近的是

public JObject Convert(JObject data)
{
    var queue = new Queue<JToken>();

    foreach (var child in data.Children())
    {
        queue.Enqueue(child);
    }

    while (queue.Count > 0)
    {
        var token = queue.Dequeue();

        if (token is JProperty p)
        {
            if (p.Value.Type != JTokenType.Object)
            {
                token.Replace(new JProperty(
                    $"{p.Name}.{p.Value.Type}",
                    p.Value
                ));
            }
        }

        foreach (var child in token.Children())
        {
            queue.Enqueue(child);
        }
    }

    return data;
}

但它不适用于像

这样的嵌套对象
var result = convertor.Convert(JObject.FromObject(new { nested = new { item = "str"}}));

出于某种原因,Replace不适用于嵌套对象。不确定它是否是一个bug。

1 个答案:

答案 0 :(得分:3)

您的主要问题是,当您向父级添加子级JToken并且该子级已有父级时,该子级克隆,克隆将添加到父级 - 在这种情况下是新的JProperty。然后,当您使用new属性替换原始属性时,克隆的值层次结构将替换整个JToken树中的原始值层次结构。最后,当你做

foreach (var child in token.Children())
{
    queue.Enqueue(child);
}

您最终会循环遍历已经克隆和替换的原始子项。虽然当属性值是基元时,这并不重要,但如果值是数组或其他容器,则会导致问题。

(一个次要的,潜在的问题是你不能处理根容器是一个数组的可能性。)

修复是为了防止在将属性值添加到新属性之前从旧属性中删除属性值来批量克隆属性值,然后循环遍历新属性的子属:

public static class JsonExtensions
{
    public static TJToken Convert<TJToken>(this TJToken data) where TJToken : JToken
    {
        var queue = new Queue<JToken>();

        foreach (var child in data.Children())
        {
            queue.Enqueue(child);
        }

        while (queue.Count > 0)
        {
            var token = queue.Dequeue();

            if (token is JProperty)
            {
                var p = (JProperty)token;
                if (p.Value.Type != JTokenType.Object)
                {
                    var value = p.Value;
                    // Remove the value from its parent before adding it to a new parent, 
                    // to prevent cloning.
                    p.Value = null;
                    var replacement = new JProperty(
                        string.Format("{0}.{1}", p.Name, value.Type),
                        value
                    );
                    token.Replace(replacement);
                    token = replacement;
                }
            }

            foreach (var child in token.Children())
            {
                queue.Enqueue(child);
            }
        }

        return data;
    }
}

工作.Net fiddle

为什么Json.NET在将其添加到新JProperty时克隆该值?这是因为{{1}中父母和子女之间存在双向引用层次结构:

因此JToken不能有两个父母 - 即,它不能同时存在于JToken层次结构中的两个位置。因此,当您将属性值添加到新的JToken时,前一个父级应该会发生什么?可能性包括:

  1. 以前的父级未经修改,并且将子级的克隆添加到新父级。

  2. 通过将子项替换为其子项的克隆来修改上一个父项。

  3. 通过使用null JValue替换孩子来修改上一个父级。

  4. 事实证明,Json.NET采用选项#1,导致你的错误。