【问题标题】:How to deserialize JSON with duplicate property names in the same object如何在同一对象中反序列化具有重复属性名称的 JSON
【发布时间】:2013-12-21 00:43:32
【问题描述】:

我有一个 JSON 字符串,我希望它包含我无法让 JSON.NET 满意的重复键。

我想知道是否有人知道最好的方法(也许使用JsonConverter?)让JSON.NET在看到重复的键名时将JObject的孩子JObjects更改为JArrays

// For example: This gives me a JObject with a single "JProperty\JObject" child.
var obj = JsonConvert.DeserializeObject<object>("{ \"HiThere\":1}");

// This throws:
// System.ArgumentException : Can not add Newtonsoft.Json.Linq.JValue to Newtonsoft.Json.Linq.JObject.
obj = JsonConvert.DeserializeObject<object>("{ \"HiThere\":1, \"HiThere\":2, \"HiThere\":3 }");

我试图反序列化的实际 JSON 要复杂得多,并且重复项嵌套在多个级别。但是上面的代码说明了为什么它对我来说失败了。

我知道 JSON 不正确,这就是为什么我要问 JSON.NET 是否有办法解决这个问题。为了争论,假设我无法控制 JSON。实际上,我确实为父对象使用了特定类型,但有问题的特定属性将是字符串或另一个嵌套的 JSON 对象。由于这个原因,失败的属性类型是“对象”。

【问题讨论】:

  • 其实JSON对象的重名是完全合法的。 ECMA-404/json.org 对此问题保持沉默。同时,RFC7159 第 4 节说 The names within an object SHOULD be unique(应该 = 推荐,不是必须)以及对影响的一些讨论。

标签: c# serialization json.net


【解决方案1】:

有趣的问题。我玩了一会儿,发现虽然 JObject 不能包含具有重复名称的属性,但用于在反序列化期间填充它的 JsonTextReader 没有这样的限制。 (如果您考虑一下,这是有道理的:它是一个仅向前的阅读器;它不关心过去阅读的内容)。有了这些知识,我开始编写一些代码来填充 JTokens 的层次结构,如果在特定 JObject 中遇到重复的属性名称,则根据需要将属性值转换为 JArrays。由于我不知道您的实际 JSON 和要求,您可能需要对其进行一些调整,但至少要从头开始。

代码如下:

public static JToken DeserializeAndCombineDuplicates(JsonTextReader reader)
{
    if (reader.TokenType == JsonToken.None)
    {
        reader.Read();
    }

    if (reader.TokenType == JsonToken.StartObject)
    {
        reader.Read();
        JObject obj = new JObject();
        while (reader.TokenType != JsonToken.EndObject)
        {
            string propName = (string)reader.Value;
            reader.Read();
            JToken newValue = DeserializeAndCombineDuplicates(reader);

            JToken existingValue = obj[propName];
            if (existingValue == null)
            {
                obj.Add(new JProperty(propName, newValue));
            }
            else if (existingValue.Type == JTokenType.Array)
            {
                CombineWithArray((JArray)existingValue, newValue);
            }
            else // Convert existing non-array property value to an array
            {
                JProperty prop = (JProperty)existingValue.Parent;
                JArray array = new JArray();
                prop.Value = array;
                array.Add(existingValue);
                CombineWithArray(array, newValue);
            }

            reader.Read();
        }
        return obj;
    }

    if (reader.TokenType == JsonToken.StartArray)
    {
        reader.Read();
        JArray array = new JArray();
        while (reader.TokenType != JsonToken.EndArray)
        {
            array.Add(DeserializeAndCombineDuplicates(reader));
            reader.Read();
        }
        return array;
    }

    return new JValue(reader.Value);
}

private static void CombineWithArray(JArray array, JToken value)
{
    if (value.Type == JTokenType.Array)
    {
        foreach (JToken child in value.Children())
            array.Add(child);
    }
    else
    {
        array.Add(value);
    }
}

这是一个演示:

class Program
{
    static void Main(string[] args)
    {
        string json = @"
        {
            ""Foo"" : 1,
            ""Foo"" : [2],
            ""Foo"" : [3, 4],
            ""Bar"" : { ""X"" : [ ""A"", ""B"" ] },
            ""Bar"" : { ""X"" : ""C"", ""X"" : ""D"" },
        }";

        using (StringReader sr = new StringReader(json))
        using (JsonTextReader reader = new JsonTextReader(sr))
        {
            JToken token = DeserializeAndCombineDuplicates(reader);
            Dump(token, "");
        }
    }

    private static void Dump(JToken token, string indent)
    {
        Console.Write(indent);
        if (token == null)
        {
            Console.WriteLine("null");
            return;
        }
        Console.Write(token.Type);

        if (token is JProperty)
            Console.Write(" (name=" + ((JProperty)token).Name + ")");
        else if (token is JValue)
            Console.Write(" (value=" + token.ToString() + ")");

        Console.WriteLine();

        if (token.HasValues)
            foreach (JToken child in token.Children())
                Dump(child, indent + "  ");
    }
}

输出:

Object
  Property (name=Foo)
    Array
      Integer (value=1)
      Integer (value=2)
      Integer (value=3)
      Integer (value=4)
  Property (name=Bar)
    Array
      Object
        Property (name=X)
          Array
            String (value=A)
            String (value=B)
      Object
        Property (name=X)
          Array
            String (value=C)
            String (value=D)

【讨论】:

  • 感谢 Brian Rogers - 您填补了我对 JsonTextReader 理解的一些空白。实际上,我已经为我的“对象”属性分配了一个 JsonConverter,我用它来抽象出 JSON.NET 的痕迹。但是在我的单元测试中,我只是想在没有转换器的情况下直接反序列化我的“坏”JSON,所以我没有想到我可以编辑它来解决问题。我将发布下面的代码作为附加答案。
【解决方案2】:

Brian Rogers - 这是我编写的 JsonConverter 的辅助函数。我根据您的 cmets 对其进行了修改,关于 JsonTextReader 只是一个前向阅读器并不关心重复值。

private static object GetObject(JsonReader reader)
{
    switch (reader.TokenType)
    {
        case JsonToken.StartObject:
        {
            var dictionary = new Dictionary<string, object>();

            while (reader.Read() && (reader.TokenType != JsonToken.EndObject))
            {
                if (reader.TokenType != JsonToken.PropertyName)
                    throw new InvalidOperationException("Unknown JObject conversion state");

                string propertyName = (string) reader.Value;

                reader.Read();
                object propertyValue = GetObject(reader);

                object existingValue;
                if (dictionary.TryGetValue(propertyName, out existingValue))
                {
                    if (existingValue is List<object>)
                    {
                        var list = existingValue as List<object>;
                        list.Add(propertyValue);
                    }
                    else
                    {
                        var list = new List<object> {existingValue, propertyValue};
                        dictionary[propertyName] = list;
                    }
                }
                else
                {
                    dictionary.Add(propertyName, propertyValue);
                }
            }

            return dictionary;
        }
        case JsonToken.StartArray:
        {
            var list = new List<object>();

            while (reader.Read() && (reader.TokenType != JsonToken.EndArray))
            {
                object propertyValue = GetObject(reader);
                list.Add(propertyValue);
            }

            return list;
        }
        default:
        {
            return reader.Value;
        }
    }
}

【讨论】:

    【解决方案3】:

    您不应该使用通用类型的对象,它应该是更具体的类型。

    但是你的 json 格式不正确,这是你的主要问题

    你有:

    "{ \"HiThere\":1, \"HiThere\":2, \"HiThere\":3 }"
    

    但应该是:

    "{"HiTheres": [{\"HiThere\":1}, {\"HiThere\":2}, {\"HiThere\":3} ]}"
    

    或者

    "{ \"HiThereOne\":1, \"HiThereTwo\":2, \"HiThereThree\":3 }"
    

    您的 json 是一个具有 3 个字段的对象,所有字段都具有相同的名称(“HiThere”)。 哪个行不通。

    我展示的 json 给出了: 三个对象的数组 (HiTheres),每个对象都有一个 HiThere 字段 要么 一个对象具有三个不同名称的字段。 (HiThereOne、HiThereTwo、“HiThereThree”)

    看看http://jsoneditoronline.org/index.html 还有http://json.org/

    【讨论】:

    • 您使用了正确的类型吗?例如,JsonConvert.DeserializeObject()
    • 尽管您对重复名称的看法是正确的,但 "{"HiTheres": [\"HiThere\":1, \"HiThere\":2, \"HiThere\":3 ]}" 仍然不是有效的 JSON。
    • 复制粘贴失败,立即尝试。
    猜你喜欢
    • 2014-08-26
    • 1970-01-01
    • 2016-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多