.Net Core 3.0 JsonSerializer заполняет существующий объект - программирование

.Net Core 3.0 JsonSerializer заполняет существующий объект

Я готовлюсь к переходу с ASP.NET Core 2.2 на 3.0.

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

Мне удалось сделать это везде, например,

var obj = JsonSerializer.Parse<T>(jsonstring);

var jsonstring = JsonSerializer.ToString(obj);

но в одном месте, где я заселяю существующий объект.

С Newtonsoft.Json можно сделать

JsonConvert.PopulateObject(jsonstring, obj);

Во встроенном пространстве имен System.Text.Json есть несколько дополнительных классов, таких как JsonDocumnet, JsonElement и Utf8JsonReader, хотя я не могу найти ни одного, который принимает существующий объект в качестве параметра.

Также я недостаточно опытен, чтобы понять, как использовать уже имеющиеся.

В .Net Core может быть возможная функция (спасибо Мустафе Гурселу за ссылку), но между тем (и что, если это не так),...

... Теперь я задаюсь вопросом, возможно ли достичь чего-то подобного тому, что можно сделать с PopulateObject?

Я имею в виду, возможно ли с любым другим классом System.Text.Json выполнить то же самое и обновить/заменить только установленные свойства?...... или какой-нибудь другой умный обходной путь?


Вот пример того, как он может выглядеть (и он должен быть универсальным, поскольку объект, передаваемый в метод десериализации, имеет тип <T>). У меня есть 2 строки Json, которые нужно разобрать в объект, где первый имеет некоторые свойства по умолчанию, а второй некоторые, например

Обратите внимание, что значение свойства может быть любого другого типа, кроме string.

JSON строка 1:

{
  "Title": "Startpage",
  "Link": "/index",
}

JSON строка 2:

{
  "Head": "Latest news"
  "Link": "/news"
}

Используя 2 строки Json выше, я хочу получить объект, который приведет к:

{
  "Title": "Startpage",
  "Head": "Latest news",
  "Link": "/news"
}

Как видно из приведенного выше примера, если свойства в 2nd имеют значения/установлены, он заменяет значения в 1st (как в случае с "Head" и "Link"), если нет, существующие значения сохраняются (как в случае с "Title")

4b9b3361

Ответ 1

Обходной путь также может быть таким простым (также поддерживается многоуровневый JSON):

using System;
using System.Linq;
using System.Reflection;
using System.Text.Json.Serialization;

namespace ConsoleApp
{
    public class Model
    {
        public Model()
        {
            SubModel = new SubModel();
        }

        public string Title { get; set; }
        public string Head { get; set; }
        public string Link { get; set; }
        public SubModel SubModel { get; set; }
    }

    public class SubModel
    {
        public string Name { get; set; }
        public string Description { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var model = new Model();

            Console.WriteLine(JsonSerializer.ToString(model));

            var json1 = "{ \"Title\": \"Startpage\", \"Link\": \"/index\" }";

            model = Map<Model>(model, json1);

            Console.WriteLine(JsonSerializer.ToString(model));

            var json2 = "{ \"Head\": \"Latest news\", \"Link\": \"/news\", \"SubModel\": { \"Name\": \"Reyan Chougle\" } }";

            model = Map<Model>(model, json2);

            Console.WriteLine(JsonSerializer.ToString(model));

            var json3 = "{ \"Head\": \"Latest news\", \"Link\": \"/news\", \"SubModel\": { \"Description\": \"I am a Software Engineer\" } }";

            model = Map<Model>(model, json3);

            Console.WriteLine(JsonSerializer.ToString(model));

            var json4 = "{ \"Head\": \"Latest news\", \"Link\": \"/news\", \"SubModel\": { \"Description\": \"I am a Software Programmer\" } }";

            model = Map<Model>(model, json4);

            Console.WriteLine(JsonSerializer.ToString(model));

            Console.ReadKey();
        }

        public static T Map<T>(T obj, string jsonString) where T : class
        {
            var newObj = JsonSerializer.Parse<T>(jsonString);

            foreach (var property in newObj.GetType().GetProperties())
            {
                if (obj.GetType().GetProperties().Any(x => x.Name == property.Name && property.GetValue(newObj) != null))
                {
                    if (property.GetType().IsClass && property.PropertyType.Assembly.FullName == typeof(T).Assembly.FullName)
                    {
                        MethodInfo mapMethod = typeof(Program).GetMethod("Map");
                        MethodInfo genericMethod = mapMethod.MakeGenericMethod(property.GetValue(newObj).GetType());
                        var obj2 = genericMethod.Invoke(null, new object[] { property.GetValue(newObj), JsonSerializer.ToString(property.GetValue(newObj)) });

                        foreach (var property2 in obj2.GetType().GetProperties())
                        {
                            if (property2.GetValue(obj2) != null)
                            {
                                property.GetValue(obj).GetType().GetProperty(property2.Name).SetValue(property.GetValue(obj), property2.GetValue(obj2));
                            }
                        }
                    }
                    else
                    {
                        property.SetValue(obj, property.GetValue(newObj));
                    }
                }
            }

            return obj;
        }
    }
}

Выход:

enter image description here

Ответ 2

Предполагая, что Core 3 не поддерживает это из коробки, давайте попробуем обойти это. Итак, в чем наша проблема?

Нам нужен метод, который перезаписывает некоторые свойства существующего объекта таковыми из строки json. Таким образом, наш метод будет иметь подпись:

void PopulateObject<T>(T target, string jsonSource) where T : class

На самом деле мы не хотим никакого пользовательского анализа, так как это громоздко, поэтому мы попробуем очевидный подход - десериализовать jsonSource и скопировать свойства результата в наш объект. Мы не можем, однако, просто пойти

T updateObject = JsonSerializer.Parse<T>(jsonSource);
CopyUpdatedProperties(target, updateObject);

Это потому, что для типа

class Example
{
    int Id { get; set; }
    int Value { get; set; }
}

и JSON

{
    "Id": 42
}

мы получим updateObject.Value == 0. Теперь мы не знаем, является ли 0 новым обновленным значением или оно просто не было обновлено, поэтому нам нужно точно знать, какие свойства содержит jsonSource.

К счастью, API System.Text.Json позволяет нам исследовать структуру анализируемого JSON.

var json = JsonDocument.Parse(jsonSource).RootElement;

Теперь мы можем перечислять все свойства и копировать их.

foreach (var property in json.EnumerateObject())
{
    OverwriteProperty(target, property);
}

Мы скопируем значение, используя отражение:

void OverwriteProperty<T>(T target, JsonProperty updatedProperty) where T : class
{
    var propertyInfo = typeof(T).GetProperty(updatedProperty.Name);

    if (propertyInfo == null)
    {
        return;
    }

    var propertyType = propertyInfo.PropertyType;
    v̶a̶r̶ ̶p̶a̶r̶s̶e̶d̶V̶a̶l̶u̶e̶ ̶=̶ ̶J̶s̶o̶n̶S̶e̶r̶i̶a̶l̶i̶z̶e̶r̶.̶P̶a̶r̶s̶e̶(̶u̶p̶d̶a̶t̶e̶d̶P̶r̶o̶p̶e̶r̶t̶y̶.̶V̶a̶l̶u̶e̶,̶ ̶p̶r̶o̶p̶e̶r̶t̶y̶T̶y̶p̶e̶)̶;̶
    var parsedValue = JsonSerializer.Deserialize(
        updatedProperty.Value.GetRawText(), 
        propertyType);

    propertyInfo.SetValue(target, parsedValue);
} 

Здесь мы видим, что то, что мы делаем, является мелким обновлением. Если объект содержит в качестве своего свойства другой сложный объект, он будет скопирован и перезаписан целиком, а не обновлен. Если вам требуются глубокие обновления, этот метод необходимо изменить, чтобы извлечь текущее значение свойства, а затем рекурсивно вызвать PopulateObject, если тип свойства является ссылочным типом (что также потребует принятия Type в качестве параметра в PopulateObject).

Объединяя все это вместе, мы получаем:

void PopulateObject<T>(T target, string jsonSource) where T : class
{
    var json = JsonDocument.Parse(jsonSource).RootElement;

    foreach (var property in json.EnumerateObject())
    {
        OverwriteProperty(target, property);
    }
}

void OverwriteProperty<T>(T target, JsonProperty updatedProperty) where T : class
{
    var propertyInfo = typeof(T).GetProperty(updatedProperty.Name);

    if (propertyInfo == null)
    {
        return;
    }

    var propertyType = propertyInfo.PropertyType;
    v̶a̶r̶ ̶p̶a̶r̶s̶e̶d̶V̶a̶l̶u̶e̶ ̶=̶ ̶J̶s̶o̶n̶S̶e̶r̶i̶a̶l̶i̶z̶e̶r̶.̶P̶a̶r̶s̶e̶(̶u̶p̶d̶a̶t̶e̶d̶P̶r̶o̶p̶e̶r̶t̶y̶.̶V̶a̶l̶u̶e̶,̶ ̶p̶r̶o̶p̶e̶r̶t̶y̶T̶y̶p̶e̶)̶;̶
    var parsedValue = JsonSerializer.Deserialize(
        updatedProperty.Value.GetRawText(), 
        propertyType);

    propertyInfo.SetValue(target, parsedValue);
} 

Насколько это устойчиво? Что ж, это определенно не будет делать ничего разумного для массива JSON, но я не уверен, как можно ожидать, что метод PopulateObject будет работать с массивом для начала. Я не знаю, как она сравнивается по производительности с версией Json.Net, вам придется проверить это самостоятельно. Он также автоматически игнорирует свойства, которые не относятся к целевому типу. Я подумал, что это самый разумный подход, но вы можете подумать иначе, в этом случае свойство null-check должно быть заменено на исключение.

EDIT:

Я пошел дальше и реализовал глубокую копию:

void PopulateObject<T>(T target, string jsonSource) where T : class => 
    PopulateObject(target, jsonSource, typeof(T));

void OverwriteProperty<T>(T target, JsonProperty updatedProperty) where T : class =>
    OverwriteProperty(target, updatedProperty, typeof(T));

void PopulateObject(object target, string jsonSource, Type type)
{
    var json = JsonDocument.Parse(jsonSource).RootElement;

    foreach (var property in json.EnumerateObject())
    {
        OverwriteProperty(target, property, type);
    }
}

void OverwriteProperty(object target, JsonProperty updatedProperty, Type type)
{
    var propertyInfo = type.GetProperty(updatedProperty.Name);

    if (propertyInfo == null)
    {
        return;
    }

    var propertyType = propertyInfo.PropertyType;
    object parsedValue;

    if (propertyType.IsValueType)
    {
        ̶p̶a̶r̶s̶e̶d̶V̶a̶l̶u̶e̶ ̶=̶ ̶J̶s̶o̶n̶S̶e̶r̶i̶a̶l̶i̶z̶e̶r̶.̶P̶a̶r̶s̶e̶(̶u̶p̶d̶a̶t̶e̶d̶P̶r̶o̶p̶e̶r̶t̶y̶.̶V̶a̶l̶u̶e̶,̶ ̶p̶r̶o̶p̶e̶r̶t̶y̶T̶y̶p̶e̶)̶;̶
        parsedValue = JsonSerializer.Deserialize(
            updatedProperty.Value.GetRawText(),
            propertyType);
    }
    else
    {
        parsedValue = propertyInfo.GetValue(target);
        P̶o̶p̶u̶l̶a̶t̶e̶O̶b̶j̶e̶c̶t̶(̶p̶a̶r̶s̶e̶d̶V̶a̶l̶u̶e̶,̶ ̶u̶p̶d̶a̶t̶e̶d̶P̶r̶o̶p̶e̶r̶t̶y̶.̶V̶a̶l̶u̶e̶,̶ ̶p̶r̶o̶p̶e̶r̶t̶y̶T̶y̶p̶e̶)̶;̶
        PopulateObject(
            parsedValue, 
            updatedProperty.Value.GetRawText(), 
            propertyType);
    }

    propertyInfo.SetValue(target, parsedValue);
}

Чтобы сделать это более надежным, вам потребуется либо отдельный метод PopulateObjectDeep, либо пройти PopulateObjectOptions, либо что-то похожее с глубоким/неглубоким флагом.

ОБНОВЛЕНИЕ 2:

Точка глубокого копирования заключается в том, что если у нас есть объект

{
    "Id": 42,
    "Child":
    {
        "Id": 43,
        "Value": 32
    },
    "Value": 128
}

и заполните его

{
    "Child":
    {
        "Value": 64
    }
}

мы бы получили

{
    "Id": 42,
    "Child":
    {
        "Id": 43,
        "Value": 64
    },
    "Value": 128
}

В случае мелкой копии мы получим Id = 0 в скопированном дочернем элементе.

ОБНОВЛЕНИЕ 3:

Как заметил @ldam, это больше не работает в стабильном .NET Core 3.0, потому что API был изменен. Метод Parse теперь является Deserialize, и вам нужно копать глубже, чтобы получить значение JsonElement. Существует активная проблема в репозитории corefx, разрешающая прямую десериализацию JsonElement. Прямо сейчас самым близким решением является использование GetRawText(). Я пошел дальше и отредактировал приведенный выше код для работы, оставив старую версию зачеркнутой.

Ответ 3

Вот пример кода, который это делает. Он использует новую структуру Utf8JsonReader, поэтому он заполняет объект одновременно с его анализом. Он поддерживает эквивалентность типов JSON/CLR, вложенные объекты (создает, если они не существуют), списки и массивы.

var populator = new JsonPopulator();
var obj = new MyClass();
populator.PopulateObject(obj, "{\"Title\":\"Startpage\",\"Link\":\"/index\"}");
populator.PopulateObject(obj, "{\"Head\":\"Latest news\",\"Link\":\"/news\"}");

public class MyClass
{
    public string Title { get; set; }
    public string Head { get; set; }
    public string Link { get; set; }
}

Обратите внимание, что он не поддерживает все то, что вы, вероятно, ожидаете, но вы можете переопределить или настроить его. Что можно добавить: 1) соглашение об именах. Вам придется переопределить метод GetProperty. 2) словари или расширенные объекты. 3) производительность может быть улучшена, потому что она использует Reflection вместо методов MemberAccessor/делегата

public class JsonPopulator
{
    public void PopulateObject(object obj, string jsonString, JsonSerializerOptions options = null) => PopulateObject(obj, jsonString != null ? Encoding.UTF8.GetBytes(jsonString) : null, options);
    public virtual void PopulateObject(object obj, ReadOnlySpan<byte> jsonData, JsonSerializerOptions options = null)
    {
        options ??= new JsonSerializerOptions();
        var state = new JsonReaderState(new JsonReaderOptions { AllowTrailingCommas = options.AllowTrailingCommas, CommentHandling = options.ReadCommentHandling, MaxDepth = options.MaxDepth });
        var reader = new Utf8JsonReader(jsonData, isFinalBlock: true, state);
        new Worker(this, reader, obj, options);
    }

    protected virtual PropertyInfo GetProperty(ref Utf8JsonReader reader, JsonSerializerOptions options, object obj, string propertyName)
    {
        if (obj == null)
            throw new ArgumentNullException(nameof(obj));

        if (propertyName == null)
            throw new ArgumentNullException(nameof(propertyName));

        var prop = obj.GetType().GetProperty(propertyName);
        return prop;
    }

    protected virtual bool SetPropertyValue(ref Utf8JsonReader reader, JsonSerializerOptions options, object obj, string propertyName)
    {
        if (obj == null)
            throw new ArgumentNullException(nameof(obj));

        if (propertyName == null)
            throw new ArgumentNullException(nameof(propertyName));

        var prop = GetProperty(ref reader, options, obj, propertyName);
        if (prop == null)
            return false;

        if (!TryReadPropertyValue(ref reader, options, prop.PropertyType, out var value))
            return false;

        prop.SetValue(obj, value);
        return true;
    }

    protected virtual bool TryReadPropertyValue(ref Utf8JsonReader reader, JsonSerializerOptions options, Type propertyType, out object value)
    {
        if (propertyType == null)
            throw new ArgumentNullException(nameof(reader));

        if (reader.TokenType == JsonTokenType.Null)
        {
            value = null;
            return !propertyType.IsValueType || Nullable.GetUnderlyingType(propertyType) != null;
        }

        if (propertyType == typeof(object)) { value = ReadValue(ref reader); return true; }
        if (propertyType == typeof(string)) { value = JsonSerializer.ReadValue<JsonElement>(ref reader, options).GetRawText(); return true; }
        if (propertyType == typeof(int) && reader.TryGetInt32(out var i32)) { value = i32; return true; }
        if (propertyType == typeof(long) && reader.TryGetInt64(out var i64)) { value = i64; return true; }
        if (propertyType == typeof(DateTime) && reader.TryGetDateTime(out var dt)) { value = dt; return true; }
        if (propertyType == typeof(DateTimeOffset) && reader.TryGetDateTimeOffset(out var dto)) { value = dto; return true; }
        if (propertyType == typeof(Guid) && reader.TryGetGuid(out var guid)) { value = guid; return true; }
        if (propertyType == typeof(decimal) && reader.TryGetDecimal(out var dec)) { value = dec; return true; }
        if (propertyType == typeof(double) && reader.TryGetDouble(out var dbl)) { value = dbl; return true; }
        if (propertyType == typeof(float) && reader.TryGetSingle(out var sgl)) { value = sgl; return true; }
        if (propertyType == typeof(uint) && reader.TryGetUInt32(out var ui32)) { value = ui32; return true; }
        if (propertyType == typeof(ulong) && reader.TryGetUInt64(out var ui64)) { value = ui64; return true; }
        if (propertyType == typeof(byte[]) && reader.TryGetBytesFromBase64(out var bytes)) { value = bytes; return true; }

        if (propertyType == typeof(bool))
        {
            if (reader.TokenType == JsonTokenType.False || reader.TokenType == JsonTokenType.True)
            {
                value = reader.GetBoolean();
                return true;
            }
        }

        // fallback here
        return TryConvertValue(ref reader, propertyType, out value);
    }

    protected virtual object ReadValue(ref Utf8JsonReader reader)
    {
        switch (reader.TokenType)
        {
            case JsonTokenType.False: return false;
            case JsonTokenType.True: return true;
            case JsonTokenType.Null: return null;
            case JsonTokenType.String: return reader.GetString();

            case JsonTokenType.Number: // is there a better way?
                if (reader.TryGetInt32(out var i32))
                    return i32;

                if (reader.TryGetInt64(out var i64))
                    return i64;

                if (reader.TryGetUInt64(out var ui64)) // uint is already handled by i64
                    return ui64;

                if (reader.TryGetSingle(out var sgl))
                    return sgl;

                if (reader.TryGetDouble(out var dbl))
                    return dbl;

                if (reader.TryGetDecimal(out var dec))
                    return dec;

                break;
        }
        throw new NotSupportedException();
    }

    // we're here when json types & property types don't match exactly
    protected virtual bool TryConvertValue(ref Utf8JsonReader reader, Type propertyType, out object value)
    {
        if (propertyType == null)
            throw new ArgumentNullException(nameof(reader));

        if (propertyType == typeof(bool))
        {
            if (reader.TryGetInt64(out var i64)) // one size fits all
            {
                value = i64 != 0;
                return true;
            }
        }

        // TODO: add other conversions

        value = null;
        return false;
    }

    protected virtual object CreateInstance(ref Utf8JsonReader reader, Type propertyType)
    {
        if (propertyType.GetConstructor(Type.EmptyTypes) == null)
            return null;

        // TODO: handle custom instance creation
        try
        {
            return Activator.CreateInstance(propertyType);
        }
        catch
        {
            // swallow
            return null;
        }
    }

    private class Worker
    {
        private readonly Stack<WorkerProperty> _properties = new Stack<WorkerProperty>();
        private readonly Stack<object> _objects = new Stack<object>();

        public Worker(JsonPopulator populator, Utf8JsonReader reader, object obj, JsonSerializerOptions options)
        {
            _objects.Push(obj);
            WorkerProperty prop;
            WorkerProperty peek;
            while (reader.Read())
            {
                switch (reader.TokenType)
                {
                    case JsonTokenType.PropertyName:
                        prop = new WorkerProperty();
                        prop.PropertyName = Encoding.UTF8.GetString(reader.ValueSpan);
                        _properties.Push(prop);
                        break;

                    case JsonTokenType.StartObject:
                    case JsonTokenType.StartArray:
                        if (_properties.Count > 0)
                        {
                            object child = null;
                            var parent = _objects.Peek();
                            PropertyInfo pi = null;
                            if (parent != null)
                            {
                                pi = populator.GetProperty(ref reader, options, parent, _properties.Peek().PropertyName);
                                if (pi != null)
                                {
                                    child = pi.GetValue(parent); // mimic ObjectCreationHandling.Auto
                                    if (child == null && pi.CanWrite)
                                    {
                                        if (reader.TokenType == JsonTokenType.StartArray)
                                        {
                                            if (!typeof(IList).IsAssignableFrom(pi.PropertyType))
                                                break;  // don't create if we can't handle it
                                        }

                                        if (reader.TokenType == JsonTokenType.StartArray && pi.PropertyType.IsArray)
                                        {
                                            child = Activator.CreateInstance(typeof(List<>).MakeGenericType(pi.PropertyType.GetElementType())); // we can't add to arrays...
                                        }
                                        else
                                        {
                                            child = populator.CreateInstance(ref reader, pi.PropertyType);
                                            if (child != null)
                                            {
                                                pi.SetValue(parent, child);
                                            }
                                        }
                                    }
                                }
                            }

                            if (reader.TokenType == JsonTokenType.StartObject)
                            {
                                _objects.Push(child);
                            }
                            else if (child != null) // StartArray
                            {
                                peek = _properties.Peek();
                                peek.IsArray = pi.PropertyType.IsArray;
                                peek.List = (IList)child;
                                peek.ListPropertyType = GetListElementType(child.GetType());
                                peek.ArrayPropertyInfo = pi;
                            }
                        }
                        break;

                    case JsonTokenType.EndObject:
                        _objects.Pop();
                        if (_properties.Count > 0)
                        {
                            _properties.Pop();
                        }
                        break;

                    case JsonTokenType.EndArray:
                        if (_properties.Count > 0)
                        {
                            prop = _properties.Pop();
                            if (prop.IsArray)
                            {
                                var array = Array.CreateInstance(GetListElementType(prop.ArrayPropertyInfo.PropertyType), prop.List.Count); // array is finished, convert list into a real array
                                prop.List.CopyTo(array, 0);
                                prop.ArrayPropertyInfo.SetValue(_objects.Peek(), array);
                            }
                        }
                        break;

                    case JsonTokenType.False:
                    case JsonTokenType.Null:
                    case JsonTokenType.Number:
                    case JsonTokenType.String:
                    case JsonTokenType.True:
                        peek = _properties.Peek();
                        if (peek.List != null)
                        {
                            if (populator.TryReadPropertyValue(ref reader, options, peek.ListPropertyType, out var item))
                            {
                                peek.List.Add(item);
                            }
                            break;
                        }

                        prop = _properties.Pop();
                        var current = _objects.Peek();
                        if (current != null)
                        {
                            populator.SetPropertyValue(ref reader, options, current, prop.PropertyName);
                        }
                        break;
                }
            }
        }

        private static Type GetListElementType(Type type)
        {
            if (type.IsArray)
                return type.GetElementType();

            foreach (Type iface in type.GetInterfaces())
            {
                if (!iface.IsGenericType) continue;
                if (iface.GetGenericTypeDefinition() == typeof(IDictionary<,>)) return iface.GetGenericArguments()[1];
                if (iface.GetGenericTypeDefinition() == typeof(IList<>)) return iface.GetGenericArguments()[0];
                if (iface.GetGenericTypeDefinition() == typeof(ICollection<>)) return iface.GetGenericArguments()[0];
                if (iface.GetGenericTypeDefinition() == typeof(IEnumerable<>)) return iface.GetGenericArguments()[0];
            }
            return typeof(object);
        }
    }

    private class WorkerProperty
    {
        public string PropertyName;
        public IList List;
        public Type ListPropertyType;
        public bool IsArray;
        public PropertyInfo ArrayPropertyInfo;

        public override string ToString() => PropertyName;
    }
}

Ответ 4

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

Основываясь на нем, я подумал об этом методе и представляю, что он способен решить свою проблему

//To populate an existing variable we will do so, we will create a variable with the pre existing data
object PrevData = YourVariableData;

//After this we will map the json received
var NewObj = JsonSerializer.Parse<T>(jsonstring);

CopyValues(NewObj, PrevData)

//I found a function that does what you need, you can use it
//source: https://stackoverflow.com/questions/8702603/merging-two-objects-in-c-sharp
public void CopyValues<T>(T target, T source)
{

    if (target == null) throw new ArgumentNullException(nameof(target));
    if (source== null) throw new ArgumentNullException(nameof(source));

    Type t = typeof(T);

    var properties = t.GetProperties(
          BindingFlags.Instance | BindingFlags.Public).Where(prop => 
              prop.CanRead 
           && prop.CanWrite 
           && prop.GetIndexParameters().Length == 0);

    foreach (var prop in properties)
    {
        var value = prop.GetValue(source, null);
        prop.SetValue(target, value, null);
    }
}

Ответ 5

Если вы уже используете AutoMapper в своем проекте или не возражаете против зависимости от него, вы можете объединить объекты следующим образом:

var configuration = new MapperConfiguration(cfg => cfg
    .CreateMap<Model, Model>()
    .ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != default)));
var mapper = configuration.CreateMapper();

var destination = new Model {Title = "Startpage", Link = "/index"};
var source = new Model {Head = "Latest news", Link = "/news"};

mapper.Map(source, destination);

class Model
{
    public string Head { get; set; }
    public string Title { get; set; }
    public string Link { get; set; }
}

Ответ 6

Я не уверен, что это решит вашу проблему, но это должно работать как временное решение. Все, что я сделал, это написал простой класс с методом populateobject.

public class MyDeserializer
{
    public static string PopulateObject(string[] jsonStrings)
    {
        Dictionary<string, object> fullEntity = new Dictionary<string, object>();

        if (jsonStrings != null && jsonStrings.Length > 0)
        {
            for (int i = 0; i < jsonStrings.Length; i++)
            {

                var myEntity = JsonSerializer.Parse<Dictionary<string, object>>(jsonStrings[i]);

                foreach (var key in myEntity.Keys)
                {
                    if (!fullEntity.ContainsKey(key))
                    {
                        fullEntity.Add(key, myEntity[key]);
                    }
                    else
                    {
                        fullEntity[key] = myEntity[key];
                    }
                }
            }
        }

        return JsonSerializer.ToString(fullEntity);
    }    
}

Я положил его в консольное приложение для тестирования. Ниже приведено все приложение, если вы хотите проверить его самостоятельно.

using System;
using System.Text.Json;
using System.IO;
using System.Text.Json.Serialization;

namespace JsonQuestion1
{
    class Program
    {
        static void Main(string[] args)
        {
            // Only used for testing
            string path = @"C:\Users\Path\To\JsonFiles";
            string st1 = File.ReadAllText(path + @"\st1.json");
            string st2 = File.ReadAllText(path + @"\st2.json");
            // Only used for testing ^^^

            string myObject = MyDeserializer.PopulateObject(new[] { st1, st2 } );

            Console.WriteLine(myObject);
            Console.ReadLine();

        }
    }

    public class MyDeserializer
    {
    public static string PopulateObject(string[] jsonStrings)
    {
        Dictionary<string, object> fullEntity = new Dictionary<string, object>();

        if (jsonStrings != null && jsonStrings.Length > 0)
        {
            for (int i = 0; i < jsonStrings.Length; i++)
            {

                var myEntity = JsonSerializer.Parse<Dictionary<string, object>>(jsonStrings[i]);

                foreach (var key in myEntity.Keys)
                {
                    if (!fullEntity.ContainsKey(key))
                    {
                        fullEntity.Add(key, myEntity[key]);
                    }
                    else
                    {
                        fullEntity[key] = myEntity[key];
                    }
                }
            }
        }

            return JsonSerializer.ToString(fullEntity);
      }
    }
}

Содержимое файла Json:

st1.json

{
    "Title": "Startpage",
    "Link": "/index"
}

st2.json

{
  "Title": "Startpage",
  "Head": "Latest news",
  "Link": "/news"
}

Ответ 7

Если это всего лишь одно использование, и вы не хотите добавлять дополнительные зависимости/много кода, вы не возражаете против неэффективности, и я не пропустил что-то очевидное, вы можете просто использовать:

    private static T ParseWithTemplate<T>(T template, string input) 
    {
        var ignoreNulls = new JsonSerializerOptions() { IgnoreNullValues = true };
        var templateJson = JsonSerializer.ToString(template, ignoreNulls);
        var combinedData = templateJson.TrimEnd('}') + "," + input.TrimStart().TrimStart('{');
        return JsonSerializer.Parse<T>(combinedData);
    }

Ответ 8

System.Text.Json не является полной заменой Json.Net. Это было действительно, чтобы удалить зависимость от Json.Net и реализует функции, необходимые для ASP.Net Core. Это не означает, что вы должны полностью отказаться от Json.Net, так как будут сценарии, которые System.Text.Json не сможет обработать.

Из будущего JSON в .NET Core 3.0

Сегодня нельзя использовать ASP.NET Core без Json.NET, потому что это зависимость самого ASP.NET Core. За прошедшие годы мы получили отзывы о том, что зависимость может конфликтовать с другими библиотеками, которые имеют собственную зависимость от другой версии Json.NET. В прошлом мы рассматривали решение этой проблемы с помощью закрытой копии Json.NET в ASP.NET. Однако это может создать проблемы, когда разработчики захотят настроить Json.NET (например, для управления поведением сериализатора при форматировании объектов JSON).

Двигаясь вперед, мы хотели бы:

  • Замените внутреннее использование Json.NET в ASP.NET Core новыми API-интерфейсами JSON, предоставляемыми платформой.

  • Включите использование Json.NET для общественности в дополнительный пакет интеграции, который можно приобрести в NuGet.

Таким образом, существующая интеграция между ASP.NET Core и Json.NET продолжит поддерживаться, но будет перемещаться из платформы в отдельный пакет. Тем не менее, поскольку интеграция разработана таким образом, чтобы она находилась на вершине платформы, она также позволит клиентам обновить Json.NET до более поздних версий.

Кроме того, клиенты, которым требуется более высокая производительность, также могут использовать новые API-интерфейсы JSON за счет богатого набора функций, предлагаемых Json.NET.