How to deserialize JSON with duplicate property names in the same object

后端 未结 3 1157
长发绾君心
长发绾君心 2020-11-29 12:24

I have a JSON string that I expect to contain duplicate keys that I am unable to make JSON.NET happy with.

I was wondering if anybody knows the best way (maybe usin

3条回答
  •  既然无缘
    2020-11-29 12:55

    Interesting question. I played around with this for a while and discovered that while a JObject cannot contain properties with duplicate names, the JsonTextReader used to populate it during deserialization does not have such a restriction. (This makes sense if you think about it: it's a forward-only reader; it is not concerned with what it has read in the past). Armed with this knowledge, I took a shot at writing some code that will populate a hierarchy of JTokens, converting property values to JArrays as necessary if a duplicate property name is encountered in a particular JObject. Since I don't know your actual JSON and requirements, you may need to make some adjustments to it, but it's something to start with at least.

    Here's the code:

    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);
        }
    }
    

    And here's a demo:

    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 + "  ");
        }
    }
    

    Output:

    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)
    

提交回复
热议问题