В настоящее время я сталкиваюсь со следующей проблемой, когда пытаюсь десериализовать строку json, полученную от третьей стороны (-> я не могу изменить полученную строку json самостоятельно) с помощью Newtonsoft. Json :

JSON содержит словарь (и некоторые другие записи, которые я здесь не перечисляю):

"food": {
        "Menu 1": "abc",
        "Menu 2": "def"
}

Я создал класс, содержащий свойство (плюс свойства, которые я здесь не перечислил):

Dictionary<string, string> Food {get; set;}

В этом случае десирализация json работает нормально. Проблема возникает, когда еда пуста:

{
      "food": [],
}

В этом случае кажется, что еда - это не словарь, а массив. Это причина, по которой десириализация не выполняется со следующей ошибкой:

Newtonsoft.Json.JsonSerializationException: «Невозможно десериализовать текущий массив JSON (например, [1,2,3]) в тип 'System.Collections.Generic.Dictionary`2 [System.String, System.String]', потому что тип требует Объект JSON (например, {"name": "value"}) для правильной десериализации. Чтобы исправить эту ошибку, либо измените JSON на объект JSON (например, {"name": "value"}), либо измените десериализованный тип на массив. или тип, реализующий интерфейс коллекции (например, ICollection, IList), например List, который может быть десериализован из массива JSON. JsonArrayAttribute также может быть добавлен к типу, чтобы заставить его десериализоваться из массива JSON. Путь 'food' ".

Кто-нибудь может помочь мне решить эту проблему, пожалуйста?

ИЗМЕНИТЬ Код десирализации:

public T DeserializeAPIResults<T>(string json)
{
        JObject obj = JsonConvert.DeserializeObject<JObject>(json);
        return obj.GetValue("canteen").ToObject<T>();
}

ИЗМЕНИТЬ 2 Полный json со значениями:

        {
        "canteen": [
            {
                "name": "Canteen1",
                "src": "a link",
                "food": {
                    "Menu 1": "abc",
                    "Menu 2": "def",
                    "Menu 3": "ghi",
                    "Menu 4": "jkl",
                    "Menu 5": "mno"
                }
            },
            {
                "name": "Canteen2",
                "src": "a link",
                "food": {
                    "Menu 1": "abc",
                    "Menu 2": "def",
                    "Menu 3": "ghi"
                }
            },
            {
                "name": "Canteen3",
                "src": "a link",
                "food": {
                    "Line 1": "abc",
                    "Line 2": "def",
                    "Line 3": "ghi"
                }
            }
        ]
    }

Полный json без значений:

{
    "canteen": [
        {
            "name": "Canteen1",
            "src": "a link",
            "food": [],
        },
        {
            "name": "Canteen2",
            "src": "a link",
            "food": [],
        },
        {
            "name": "Canteen3",
            "src": "a link",
            "food": [],
       }
    ]
}

ИЗМЕНИТЬ 3 Класс:

public sealed class Canteen
{
    [JsonProperty("name")]
    public string Name { get; set; }
    [JsonProperty("src")]
    public string Src { get; set; }
    [JsonProperty("food")]
    public Dictionary<string, string> Food { get; set; }
    public Canteen() { }
}

И вызов метода:

Canteen[] canteens = DeserializeAPIResults<Canteen[]>(json);
3
CLRW97 24 Ноя 2018 в 16:01

1 ответ

Лучший ответ

Если ваше значение для определенного ключа не фиксировано и данные должны быть настраиваемыми, тогда Newtonsoft.json имеет одну функцию, которую можно использовать здесь, а именно [JsonExtensionData]. Подробнее

Данные расширения теперь записываются при сериализации объекта. Чтение и запись данных расширения позволяет автоматически выполнять обход всех JSON без добавления каждого свойства к типу .NET, к которому вы десериализуетесь. Объявите только те свойства, которые вас интересуют, и пусть данные расширений сделают все остальное.

Когда ваш сторонний json имеет ключ с именем food и его значение как объект, вы пытаетесь десериализовать его в Dictionary<string, string> Food {get; set;}, и ваш метод десерилизации правильно десериализует ваш json.

Но когда ключ food имеет массив, ваш метод не может десериализоваться, потому что вы пытаетесь десериализовать массив [] в string.

Если вы используете

[JsonExtensionData]
public Dictionary<string, JToken> Food { get; set; }

Вместо того

Dictionary<string, string> Food {get; set;}

Тогда ваша десериализация работает.

Итак, наконец, ваш класс будет

public sealed class Canteen
{
    [JsonProperty("name")]
    public string Name { get; set; }
    [JsonProperty("src")]
    public string Src { get; set; }

    [JsonExtensionData]
    public Dictionary<string, JToken> Food { get; set; }
    public Canteen() { }
}

Альтернатива:

Если вы объявите свой тип данных свойства Food как JToken в своем классе Canteen, например

public sealed class Canteen
{
    [JsonProperty("name")]
    public string Name { get; set; }
    [JsonProperty("src")]
    public string Src { get; set; }

    [JsonProperty("food")]
    public JToken Food { get; set; }

    public Canteen() { }
}

Затем вы можете успешно десериализовать свой json независимо от того, является ли ваш ключ food объектом или массивом.

А затем вы можете получить доступ к каждой из столовых из массива столовых и получить пары ключ / значение каждой столовой name, src и food, например.

Преимущество JToken в том, что вы можете проверить его тип, является ли его объект или массив

Canteen[] canteens = DeserializeAPIResults<Canteen[]>(json);

foreach (var canteen in canteens)
{
    string name = canteen.Name;
    string src = canteen.Src;
    JToken food = canteen.Food;

    if (food.Type == JTokenType.Object)
    {
        Dictionary<string, string> foods = food.ToObject<Dictionary<string, string>>();
    }
    else if (food.Type == JTokenType.Array)
    {
        //Do something if "foods" is empty array "[]"
    }
}
2
er-sho 24 Ноя 2018 в 15:44