【问题标题】:.Net Core 3.0 JsonSerializer populate existing object.Net Core 3.0 JsonSerializer 填充现有对象
【发布时间】:2022-02-18 05:11:18
【问题描述】:

我正在准备从 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 命名空间有一些额外的类,例如JsonDocumnetJsonElementUtf8JsonReader,尽管我找不到任何将现有对象作为参数的类。

我也没有足够的经验来了解如何利用现有的。

可能有a possible upcoming feature in .Net Core(感谢Mustafa Gursel 的链接),但与此同时(如果没有的话怎么办),...

...我现在想知道,是否有可能实现与使用PopulateObject 所做的类似的事情?

我的意思是,是否可以使用任何其他 System.Text.Json 类来完成相同的操作,并且只更新/替换属性集?,...或其他一些巧妙的解决方法?


这是我正在寻找的示例输入/输出,它需要是通用的,因为传递给反序列化方法的对象是 &lt;T&gt;) 类型。我有 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"
}

如上例所示,如果第二个中的属性有值/已设置,它将替换第一个中的值(如“Head”和“Link”),如果没有,则现有值保持(如“Title” )

【问题讨论】:

标签: c# asp.net-core razor-pages asp.net-core-3.0 system.text.json


【解决方案1】:

所以假设 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 包含哪些属性。

幸运的是,System.Text.Json API 允许我们检查解析后的 JSON 的结构。

using 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);
} 

我们可以在这里看到我们正在做的是一个的更新。如果对象包含另一个复杂对象作为其属性,则该对象将作为一个整体被复制和覆盖,而不是更新。如果您需要 deep 更新,则需要更改此方法以提取属性的当前值,然后如果属性的类型是引用类型,则递归调用 PopulateObject(这也需要接受 @ 987654335@作为PopulateObject中的参数)。

将它们结合在一起,我们得到:

void PopulateObject<T>(T target, string jsonSource) where T : class
{
    using 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 必须替换为异常抛出。

编辑:

我继续实施了一个深拷贝:

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)
{
    using 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 || propertyType == typeof(string))
    {
        ̶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 的值。 an active issue in the corefx repo 允许直接反序列化 JsonElement。目前最接近的解决方案是使用GetRawText()。我继续编辑上面的代码以使其正常工作,留下旧版本。

【讨论】:

  • 对于这个用例来说,深拷贝不是矫枉过正吗?因为要复制的对象已经是对象的反序列化克隆。
  • 啊哈!我明白你的意思了。整个源对象图可以是目标对象图的部分子集。这是有道理的。
  • 我将奖励你最初的赏金,因为这个答案让我很好地了解了使用 JsonDocument 可以做什么,这也是我问题的主要部分。谢谢你的回答。
  • 由于 .NET Core 3 已经过预览版,所以这不太适用。
  • @ldam 是的,已解决。
【解决方案2】:

这是一些执行此操作的示例代码。它使用新的Utf8JsonReader struct,因此它在解析对象的同时填充对象。它支持 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)字典或expando对象。 3) 性能可以提高,因为它使用反射而不是 MemberAccessor/delegate 技术

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.Deserialize<JsonElement>(ref reader, options).GetString(); 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;
    }
}

【讨论】:

  • 我开始了第二次赏金,并将奖励您的答案(需要等待 24 小时直到我可以),因为它让我很好地了解了使用 Utf8JsonReader 可以做什么,这也我的问题的主要部分。谢谢你的回答。
  • 尝试使用 JsonPopulator 但出现错误:“JsonSerializer”不包含“ReadValue”的定义
  • @tb-mtg - 是的,看起来 ReadValue 在 .NET core 3 beta 和最终版本之间被重命名为 Serialize:github.com/dotnet/corefx/commit/…我已经更新了我的答案。
  • 我尝试使用它,但是当我尝试断言您的 MyClass 可以往返而不会丢失数据时,我的断言失败,因为字符串属性的值添加了双引号。见dotnetfiddle.net/BsAeIu。修复似乎是调用JsonSerializer.Deserialize&lt;JsonElement&gt;(ref reader, options).GetString(); 而不是GetRawText(),见dotnetfiddle.net/Q7SxQp
  • @dbc - 感谢您指出这一点。这很奇怪,我 100% 确定我已经在初始版本中对此进行了测试,所以从那时起 json 类中发生了一些变化。我已经更新了代码。
【解决方案3】:

解决方法也可以这么简单(也支持多级 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;
        }
    }
}

输出:

【讨论】:

    【解决方案4】:

    我对这个新版本的插件了解不多,但是我找到了一个可以学习的教程tutorial with some examples

    基于他我想到了这个方法,我想他能够解决他的问题

    //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);
        }
    }
    

    【讨论】:

    • 您是否总是会收到任何类型的对象,它是否总是未定义的?
    • 是的,任何类型的对象,不,有时未定义,有时不是,我的 json 字符串示例显示。
    • 您正在寻找一种将任何对象的属性复制到另一个对象的方法,这与 JSON 无关。
    • 但目的不是统一json,而是非序列化对象
    【解决方案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; }
    }
    

    【讨论】:

    • 想解释否决票?显然,该功能尚未在 .NET Core 3.0 中实现。所以基本上有两种方法:要么创建一些自定义实现,要么利用可以完成这项工作的现有工具。
    • 它总是会收到一个T型对象,它不能为它创建一个固定的类
    • @SuperPenguino 我假设项目中有一定数量的对象需要合并。所以应该可以在应用程序启动时注册它们。甚至按惯例自动。
    • 我不使用AutoMapper,如果需要,我最好坚持Newtonsoft 更适合这份工作。我想要/更喜欢的是使用处理 Json 数据的内置工具。而且我没有投反对票,因为这实际上可能有效,尽管不是我想要的。
    • @LGSon 我在发布之前对其进行了测试,它需要在图中注册所有嵌套类型,但它可以自动完成并且肯定会工作。当然,如果可以的话,最好坚持使用 Newtonsoft,尤其是如果您不使用 AutoMapper。
    【解决方案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"
    }
    

    【讨论】:

    • 请注意,多级 json 可能会破坏此代码。
    • 抱歉,并不是说你说我投了反对票,只是想提一下(我现在删除了该评论)。感谢您的回答,简单是有好处的,我稍后会看看。
    • 顺便说一句,在查看了其他答案之后,对多级 json 的某种修复将是检查对象 值类型,如果不是 IsValueType 然后执行递归调用。
    • 我意识到我可以让它支持多级 JSON,但这超出了问题的范围。我试图为手头的问题提供最简单的解决方案,这样就不会有太多的混乱。
    • 越简单/越干净越好,如果需要,我可以自己解决。
    【解决方案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);
        }
    

    【讨论】:

    • 谢谢,稍后会检查JsonSerializer 是否允许在 json 字符串中出现两次相同的属性。在我找到Newtonsoft的方法之前,我自己一开始也喜欢这个。我的问题可能不清楚的是,我同时进行了 templateinput 反序列化,因此这个技巧不像看起来那么低效,因为我没有不需要额外的JsonSerializer.ToString();。我还缓存了结果,除非编辑了任何字符串,否则我不会运行它,这使得 效率低下 的问题更小。
    • 它似乎在 3.0 preview6 中被允许,我猜它在解析时检查重复项效率低下。缺点是它只合并对象的“顶级”,因此如果您需要在 templateinput 之间合并复杂的类型/数组属性,则会失败。
    • 我知道这个缺点...我自己的版本也有同样的问题,但如果归结为最佳选择,我仍然可以忍受。
    猜你喜欢
    • 1970-01-01
    • 2020-02-10
    • 1970-01-01
    • 2020-04-23
    • 2016-01-13
    • 1970-01-01
    • 2017-02-22
    • 1970-01-01
    • 2018-08-26
    相关资源
    最近更新 更多