Как десериализовать JSON с повторяющимися именами свойств в одном и том же объекте

c# serialization json.net

6957 просмотра

3 ответа

У меня есть строка JSON, которая, как я ожидаю, будет содержать дубликаты ключей, и я не могу порадовать JSON.NET.

Мне было интересно , если кто - нибудь знает , что лучший способ (возможно , используя JsonConverter?) , Чтобы получить JSON.NET , чтобы изменить JObjectребенка «s 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. По этой причине ошибочный тип свойства - «объект».

Автор: Brian Booth Источник Размещён: 12.11.2019 09:12

Ответы (3)


5 плюса

Решение

Интересный вопрос. Я поэкспериментировал с этим некоторое время и обнаружил, что хотя a JObjectне может содержать свойства с дублирующимися именами, JsonTextReaderдля заполнения, которое используется во время десериализации, такого ограничения нет. (Это имеет смысл, если вы подумаете об этом: это читатель только для форварда; его не интересует то, что он читал в прошлом). Вооружившись этими знаниями, я попытался написать некоторый код, который будет заполнять иерархию JTokens, преобразовывая значения свойств в JArrays по мере необходимости, если в конкретном JObject встречается повторяющееся имя свойства. Так как я не знаю ваш фактический 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 Размещён: 21.12.2013 06:14

2 плюса

Брайан Роджерс - вот вспомогательная функция JsonConverter, которую я написал. Я изменил его, основываясь на ваших комментариях о том, что 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;
        }
    }
}
Автор: Brian Booth Размещён: 21.12.2013 04:33

-1 плюса

Вы не должны использовать универсальный тип объекта, это должен быть более конкретный тип.

Тем не менее, вы JSON деформирован, что является вашей главной проблемой

У вас есть :

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

Но это должно быть:

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

Или же

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

Вы json - это один объект с 3 полями с одинаковыми именами («HiThere»). Который не будет работать.

Json, который я показал, дает: массив (HiThere) из трех объектов, каждый с полем HiThere или один объект с тремя полями с разными именами. (HiThereOne, HiThereTwo, "HiThereThree)

Взгляните на http://jsoneditoronline.org/index.html и http://json.org/

Автор: DarcyThomas Размещён: 21.12.2013 12:50
32x32