c# - .Net कोर 3.0 JsonSerializer मौजूदा ऑब्जेक्ट को पॉप्युलेट करता है




asp.net-core razor-pages (5)

मैं ASP.NET कोर 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 है जिसे किसी ऑब्जेक्ट में पार्स किया जाना है, जहाँ पहले में कुछ डिफ़ॉल्ट गुण सेट हैं, और दूसरे में कुछ।

ध्यान दें, एक संपत्ति का मूल्य string अलावा किसी अन्य प्रकार का हो सकता है

Json string 1:

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

Json string 2:

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

ऊपर दिए गए 2 Json स्ट्रिंग्स का उपयोग करके, मैं एक परिणाम चाहता हूं:

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

जैसा कि ऊपर के नमूने में देखा गया है, यदि 2 के गुणों में मान / सेट है, तो यह मानों को 1 में बदल देता है (जैसा कि "हेड" और "लिंक"), यदि नहीं, तो मौजूदा वैल्यू बनी रहती है (जैसे "शीर्षक")


तो यह मानते हुए कि कोर 3 इस बॉक्स से बाहर का समर्थन नहीं करता है, आइए इस चीज़ के आसपास काम करने की कोशिश करें। तो, हमारी समस्या क्या है?

हम एक ऐसी विधि चाहते हैं जो किसी मौजूदा वस्तु के कुछ गुणों को एक जॉगन स्ट्रिंग से लोगों के साथ अधिलेखित कर दे। तो हमारी पद्धति का एक हस्ताक्षर होगा:

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 एपीआई हमें पार्स 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;
    var parsedValue = JsonSerializer.Parse(updatedProperty.Value, 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;
    var parsedValue = JsonSerializer.Parse(updatedProperty.Value, propertyType);

    propertyInfo.SetValue(target, parsedValue);
} 

यह कितना मजबूत है? ठीक है, यह निश्चित रूप से एक JSON सरणी के लिए कुछ भी समझदार नहीं होगा, लेकिन मुझे यकीन नहीं है कि आप कैसे शुरू करने के लिए एक सरणी पर काम करने के लिए एक PopulateObject विधि की अपेक्षा करेंगे। मुझे नहीं पता कि यह Json.Net संस्करण के प्रदर्शन में कैसे तुलना करता है, आपको खुद से यह Json.Net होगा। यह चुपचाप उन गुणों को भी नजरअंदाज कर देता है जो डिजाइन द्वारा, लक्ष्य प्रकार में नहीं होते हैं। मैंने सोचा था कि यह सबसे समझदार दृष्टिकोण था, लेकिन आप अन्यथा सोच सकते हैं, उस स्थिति में संपत्ति की जांच को अपवाद फेंक के साथ बदलना होगा।

संपादित करें:

मैंने आगे बढ़कर एक गहरी प्रति लागू की:

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)
    {
        parsedValue = JsonSerializer.Parse(updatedProperty.Value, propertyType);
    }
    else
    {
        parsedValue = propertyInfo.GetValue(target);
        PopulateObject(parsedValue, updatedProperty.Value, propertyType);
    }

    propertyInfo.SetValue(target, parsedValue);
}

इसे और अधिक मजबूत बनाने के लिए आपके पास या तो एक अलग PopulateObjectDeep तरीका होना चाहिए या PopulateObjectDeep को पास करना होगा या एक गहरे / उथले झंडे के साथ कुछ ऐसा ही होगा।

संपादित करें 2:

गहरी-नकल की बात इतनी है कि अगर हमारे पास कोई वस्तु है

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

और इसके साथ आबाद करें

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

हमें मिलेगा

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

उथली कॉपी के मामले में हमें कॉपी किए गए बच्चे में Id = 0 मिलेगा।


मुझे प्लग-इन के इस नए संस्करण के बारे में ज्यादा जानकारी नहीं है, हालांकि मुझे एक ऐसा ट्यूटोरियल मिला, जिसे कुछ उदाहरणों के साथ ट्यूटोरियल के बाद किया जा सकता है

उसके आधार पर मैंने इस पद्धति के बारे में सोचा और मुझे लगा कि वह अपनी समस्या को हल करने में सक्षम है

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

यदि आप पहले से ही अपने प्रोजेक्ट में 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; }
}

यदि इसका सिर्फ एक उपयोग है और आप अतिरिक्त निर्भरता / बहुत सारे कोड नहीं जोड़ना चाहते हैं, तो आप थोड़ी अक्षमता का बुरा नहीं मानते हैं और मुझे कुछ स्पष्ट याद नहीं है, आप बस उपयोग कर सकते हैं:

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

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

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

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

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

            Map<Model>(model, json1);

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

            var json2 = "{ \"Head\": \"Latest news\", \"Link\": \"/news\" }";

            Map<Model>(model, json2);

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

            Console.ReadKey();
        }

        private 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))
                {
                    property.SetValue(obj, property.GetValue(newObj));
                }
            }

            return obj;
        }
    }
}

वर्कअराउंड भी इस तरह सरल हो सकता है:

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

आउटपुट:





asp.net-core-3.0