Задать вопрос
Viper029
@Viper029
Программирую за печенье

Как правильно описать класс для библиотеки Newtonsoft.Json C#?

Добрый день, уважаемые знатоки. Возник такой вопрос. Моя программа использует библиотеку Newtonsoft.Json.dll для десериализации JSON файла со сложной структурой. Сам файл огромный, приводить его не буду. Покажу только дерево, по нему мой вопрос должен быть понятен.
5b6af1d85db18972255046.png
А вот класс, его описывающий:
public class Row
    {
        public List<string> row { get; set; }
        public List<object> sub { get; set; }
    }
public class RootObject
    {
        public string _id { get; set; }
        public string _rev { get; set; }
        public string type { get; set; }
        public string hid { get; set; }
        public string title { get; set; }
        public string name { get; set; }
        public List<Row> rows { get; set; }
        public List<string> cols { get; set; }
    }

И вызывающий конвертацию код:
string json = File.ReadAllText(pathToFile);
RootObject root = JsonConvert.DeserializeObject<RootObject>(json);

Десериализация проходит успешно, но проблема в том, что, например, rows[0]sub[9] имеет встроенный массив row : и sub : , а rows[0]sub[10] уже сразу необходимые данные. При таком раскладе в rows[0]sub[9] остаются недодесериализированные(вух!) данные, над которыми будет необходимо снова производить десериализацию.
5b6af6e0affc5926166291.png
Есть ли какое-нибудь элегантное решение описать класс так, чтобы он мог принимать разнотипные данные и в итоге, после конвертации все данные хранились в нем? Понимаю, что могу пробегать по текущему варианту циклами и десериализовать заново, создавая новые экземпляры класса Row для хранения данных, но очень бы хотелось этого избежать. Спасибо ^_^
  • Вопрос задан
  • 1035 просмотров
Подписаться 1 Простой 1 комментарий
Решения вопроса 1
VoidVolker
@VoidVolker Куратор тега C#
Dark side eye. А у нас печеньки! А у вас?
Элементарно: https://app.quicktype.io/#r=json2csharp - слева вставляете JSON, справа на выходе получаете готовый класс для парсинга. Удобнейший сервис.

Пример:
{
  "greeting": "Welcome to quicktype!",
  "instructions": [
    "Type or paste JSON here",
    "Or choose a sample above",
    "quicktype will generate code in your",
    "chosen language to parse the sample data"
  ]
}

namespace QuickType
{
    using System;
    using System.Collections.Generic;

    using System.Globalization;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Converters;
    using J = Newtonsoft.Json.JsonPropertyAttribute;
    using R = Newtonsoft.Json.Required;
    using N = Newtonsoft.Json.NullValueHandling;

    public partial class Welcome
    {
        [J("greeting")]     public string Greeting { get; set; }      
        [J("instructions")] public string[] Instructions { get; set; }
    }

    public partial class Welcome
    {
        public static Welcome FromJson(string json) => JsonConvert.DeserializeObject<Welcome>(json, QuickType.Converter.Settings);
    }

    public static class Serialize
    {
        public static string ToJson(this Welcome self) => JsonConvert.SerializeObject(self, QuickType.Converter.Settings);
    }

    internal static class Converter
    {
        public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
        {
            MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
            DateParseHandling = DateParseHandling.None,
            Converters = {
                new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
            },
        };
    }
}


Я конвертер выношу в отдельный класс и файл:
using Newtonsoft.Json;

    /// <summary>
    /// Конвертер JSON 
    /// </summary>
    public static class Converter
    {
        /// <summary>
        /// Настройки конвертации JSON
        /// </summary>
        public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
        {
            MetadataPropertyHandling = MetadataPropertyHandling.Ignore,     // Пропускаем аттрибуты
            DateParseHandling = DateParseHandling.None,                     // Выключаем парсинг дат
            NullValueHandling = NullValueHandling.Ignore                    // Пропускаем пустые значения 
                                                                            // (API-вызов при создании запроса 
                                                                            // сам заполняет нужные поля)
        };
    }


А парсинг/конвертацию JSON выношу в отдельный класс, от которого уже наследую классы для парсинга данных:
namespace Foo
{
    /// <summary>
    /// Абстрактный класс - сетевое сообщение: парсинг и 
    /// генерация JSON из экземпляров наследуемого класса
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public abstract class APIMessage<T>
    {
        /// <summary>
        /// Конвертация в JSON
        /// </summary>
        /// <returns></returns>
        public string ToJson()
        {
            return JsonConvert.SerializeObject(this, Converter.Settings);
        }

        /// <summary>
        /// Парсинг JSON в указанный тип
        /// </summary>
        /// <param name="json"></param>
        /// <returns></returns>
        public static T FromJson(string json)
        {
            try
            {
                return JsonConvert.DeserializeObject<T>(json, Converter.Settings);
            }
            catch (Exception e)
            {                
                throw new ServerConnectionException(
                    "Server response parse error!\nResponse type: <" + typeof(T).FullName
                    + ">\nError: " + e.Message
                );
            }
        }
    }
}


Как-то так получается:
public class Request : APIMessage<Request>
{
    <описание JSON свойств>
}
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
@Beltoev
Живу в своё удовольствие
С такой неоднородной структурой файла нужно писать кастомный конвертер:

Наследуетесь от JsonConverter и переопределяете метод ReadJson, в котором проверяете тип очередного десериализуемого объекта и в зависимости от него создаете экземпляр Row, либо типизированного класса для данных. Таким образом любую вложенность rows можно корректно обработать.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы