Десериализовать значение JSON для модели в System.Json

#c# #json #.net #json-deserialization #system.json

#c# #json #.net #json-десериализация #system.json

Вопрос:

Прежде чем кто-нибудь начнет предлагать какие-либо библиотеки, подобные Newtonsoft.Json System.Text.Json или любую другую приятную и простую вещь, которую я хотел бы использовать, знайте, что я не могу использовать ничего, кроме System.Json , потому что я работаю в рамках ограничений приложения (я создаю плагин), я не имею никакого влияния и перестал быть активнымразработка (это ERP-система, которая имеет исправления безопасности только один раз в год, а запросы функций приводят к пассивно-агрессивным ответам; даже когда я предложил внести изменения самостоятельно бесплатно).

У меня есть несколько json и несколько хороших моделей предметной области (объекты, классы, модели, сущности, все, что вам нравится называть общедоступными классами со свойствами), и я хочу, чтобы они поженились. И использование отражения — это боль, когда есть вложенность.

Не мог бы кто-нибудь, пожалуйста, показать мне несколько хороших способов сделать это, которые не требуют каких-либо nugets или dll? Все, что я нахожу при поиске, связано со всеми другими библиотеками, кроме System.Json .

Вот что я делал до того, как сдался (я перестроился на что-то похожее на прецедент, но это из-за договорных причин):

 public void BuildSettings(string settingsPath = "appsettings.json", params Type[] types)
{
    if (!types.Any())
        throw new ArgumentException("The type parameters cannot be empty", nameof(types));

    var file = new FileInfo(settingsPath);
    if (!file.Exists)
        throw new ArgumentException($"No settings file found in the path '{settingsPath}'", nameof(settingsPath));

    using (var reader = file.OpenText())
    {
        var rootJson = JsonValue.Load(reader);

        if (rootJson.JsonType != JsonType.Object)
            throw new ArgumentException($"The settings file must be a Json Object, but a '{rootJson.JsonType}' was found", nameof(settingsPath));

        var jsonObject = rootJson as JsonObject;

        if (jsonObject == null)
            throw new NullReferenceException("The json object is null");

        foreach (var type in types)
        {
            if (jsonObject.ContainsKey(type.Name))
            {
                var jsonSetting = jsonObject[type.Name] as JsonObject;
                var properties = type.GetProperties();

                foreach (var property in properties)
                {
                    var value = jsonSetting[property.Name];
                    var propertyType = property.PropertyType;
                    property.SetValue();
                    // TODO: Ask StackOwerflow
                }
            }
        }
    }
}
  

В этом есть некоторая глупость, но я не устанавливаю правила

Ответ №1:

Я думаю, вам предстоит несколько долгих часов… мне кажется, что вам нужно разработать ORM, я однажды столкнулся с тем же, где мне пришлось создавать ORM для банкоматов.

Этот код не будет работать для вас, поскольку он предполагает IDataReader, однако вам нужно сопоставление свойств с использованием отражения, и этот код находится там.

Дайте мне знать, если вы собираетесь это сделать, поскольку в нем есть некоторая оптимизация для повторного использования отраженных типов и т. Д.

Я думаю, вам нужно проверить, является ли свойство классом, создать его с помощью активатора и использовать эффективное приведение с помощью

 if (property.PropertyType.BaseType == typeof(Enum))
{
    property.SetValue(obj, (int)value);
}
else if (property.PropertyType.BaseType == typeof(Guid))
{
   property.SetValue(obj, Guid.Parse(value.ToString().ToUpper()));
}
else
{
  property.SetValue(obj, Convert.ChangeType(value, property.PropertyType));
}
  

Я не знаю, какие мои вызовы вам нужно поддерживать, или если это фиксированная сумма, возможно, вы просто создаете статический метод T Parse(this T target, string json)
, в котором вы фрагментируете строку json на основе скобок { и } для получения свойств и [ и] для получения массивов.

Вот код для этого, я использовал его для синтаксического анализатора Json VCARD, который я собирался создать некоторое время назад

 /// <summary>
/// Splits the specified string in sections of open en closing characters.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="open">The opening char indicating where to start to read .</param>
/// <param name="close">The close char, indicating the part where should stop reading.</param>
/// <returns>IReadOnlyListamp;<System.Stringamp;>.</returns>
/// <exception cref="System.ArgumentNullException">text</exception>
/// <exception cref="ArgumentNullException">Will throw an exception if the string that needs to be split is null or empty</exception>
public static IReadOnlyList<string> Split(this string text, char open, char close)
{
    if (text is null)
    {
        throw new ArgumentNullException(nameof(text));
    }

    var counted = 0;
    var result = new List<string>();
    var sb = new StringBuilder();
    foreach (char c in text)
    {
        if (c == open)
        {
            if (counted != 0)
                sb.Append(c);

            counted  ;
            continue;
        }
        if (c == close)
        {
            counted--;
            if (counted != 0)
                sb.Append(c);
            continue;
        }

        if (counted > 0)
        {
            sb.Append(c);
        }
        else if (counted == 0 amp;amp; sb.Length > 0)
        {
            result.Add(sb.ToString());
            sb.Clear();
        }
    }
    return resu<
}
  

Вот полный картограф, который я должен был сделать, где вы видите упомянутое отражение

 class Mapper
{
    ConcurrentDictionary<Type, PropertyInfo[]> _properties = new ConcurrentDictionary<Type, PropertyInfo[]>();
    ConcurrentDictionary<string, List<string>> _fieldNames = new ConcurrentDictionary<string, List<string>>();

    /// <summary>
    /// Maps the specified reader to a given class. the reader must contain all properties of the type provided.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="reader">The reader.</param>
    /// <returns></returns>
    public IEnumerable<T> Map<T>(SqlDataReader reader)
    {
        var result = new List<T>();
        if (!reader.HasRows)
            return resu<


        var type = typeof(T);
        if (!_properties.TryGetValue(type, out PropertyInfo[] prop))
        {
            prop = type.GetProperties();
            _properties.TryAdd(type, prop);
        }

  
        if (!_fieldNames.TryGetValue(type.Name, out List<string> fieldNames))
        {
            var names = new List<string>(reader.FieldCount);
            for (int i = 0; i < reader.FieldCount; i  )
            {
                names.Add(reader.GetName(i));
            }
            fieldNames = names;
            _fieldNames.TryAdd(type.Name, fieldNames);
        }

        while (reader.Read())
        {
            var obj = Activator.CreateInstance<T>();
            foreach (var property in prop)
            {
                if (fieldNames.Contains(property.Name))
                {
                    var value = reader[property.Name];
                    if (value == DBNull.Value)
                        continue;

                    if (property.PropertyType.BaseType == typeof(Enum))
                    {
                        property.SetValue(obj, (int)value);
                    }
                    else if (property.PropertyType.BaseType == typeof(Guid))
                    {
                        property.SetValue(obj, Guid.Parse(value.ToString().ToUpper()));
                    }
                    else
                    {
                        property.SetValue(obj, Convert.ChangeType(value, property.PropertyType));
                    }

                }
            }
            result.Add(obj);
        }
        return resu<
    }

    public IEnumerable<T> Map<T,Y>(SqlDataReader reader,Y owner)
    {
        var result = new List<T>();
        if (!reader.HasRows)
            return resu<


        var type = typeof(T);
        if (!_properties.TryGetValue(type, out PropertyInfo[] prop))
        {
            prop = type.GetProperties();
            _properties.TryAdd(type, prop);
        }

        if (!_fieldNames.TryGetValue(type.Name, out List<string> fieldNames))
        {
            var names = new List<string>(reader.FieldCount);
            for (int i = 0; i < reader.FieldCount; i  )
            {
                names.Add(reader.GetName(i));
            }
            fieldNames = names;
            _fieldNames.TryAdd(type.Name, fieldNames);
        }

        while (reader.Read())
        {
            var obj = Activator.CreateInstance<T>();
            foreach (var property in prop)
            {

                if (property.PropertyType == typeof(Y))
                {
                    property.SetValue(obj, owner);
                    continue;
                }

                if (fieldNames.Contains(property.Name))
                {
                    var value = reader[property.Name];
                    if (value == DBNull.Value)
                        continue;

                    if (property.PropertyType.BaseType == typeof(Enum))
                    {
                        property.SetValue(obj, (int)value);
                    }
                    else
                    {
                        property.SetValue(obj, Convert.ChangeType(value, property.PropertyType));
                    }

                }
            }
            result.Add(obj);
        }
        return resu<
    }

    /// <summary>
    /// Maps the specified reader to a given class. the reader must contain all properties of the type provided.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="reader">The reader.</param>
    /// <returns></returns>
    public T MapOne<T>(SqlDataReader reader)
    {

        if (!reader.HasRows)
            return default;

            
        var type = typeof(T);
        if (!_properties.TryGetValue(type, out PropertyInfo[] prop))
        {
            prop = type.GetProperties();
            _properties.TryAdd(type, prop);
        }


        if (!_fieldNames.TryGetValue(type.Name, out  List<string> fieldNames))
        {
            var names = new List<string>(reader.FieldCount);
            for (int i = 0; i < reader.FieldCount; i  )
            {
                names.Add(reader.GetName(i));
            }
            fieldNames = names;
            _fieldNames.TryAdd(type.Name, fieldNames);
        }

        if (reader.Read())
        {
            var obj = Activator.CreateInstance<T>();
            foreach (var property in prop)
            {
                if (fieldNames.Contains(property.Name))
                    property.SetValue(obj, reader[property.Name]);
            }
            return obj;
        }
        else
        {
            return default;
        }

    }


    /// <summary>
    /// Maps the specified reader to a given class. the reader must contain all properties of the type provided.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="reader">The reader.</param>
    /// <returns></returns>
    public IEnumerable<T> Map<T>(SqlDataReader reader, object[] args)
    {
        var result = new List<T>();
        if (!reader.HasRows)
            return resu<

 
        var type = typeof(T);
        if (!_properties.TryGetValue(type, out PropertyInfo[] prop))
        {
            prop = type.GetProperties();
            _properties.TryAdd(type, prop);
        }


        if (!_fieldNames.TryGetValue(type.Name, out List<string> fieldNames))
        {
            var names = new List<string>(reader.FieldCount);
            for (int i = 0; i < reader.FieldCount; i  )
            {
                names.Add(reader.GetName(i));
            }
            fieldNames = names;
            _fieldNames.TryAdd(type.Name, fieldNames);
        }

        while (reader.Read())
        {
            var obj = (T)Activator.CreateInstance(type, args);
            foreach (var property in prop)
            {
                if (fieldNames.Contains(property.Name))
                    property.SetValue(obj, reader[property.Name]);
            }
            result.Add(obj);
        }
        return resu<
    }
}
  

Комментарии:

1. Это выглядит многообещающе, но отнимает много времени. Я попробую это, или я сдамся и потребую использовать CSV вместо этого (хотя я сомневаюсь, что мне повезет). Спасибо за огромное количество примеров кода