Bagaimana cara membatalkan serialisasi JSON yang berisi array objek dengan satu nama properti dan nilai masing-masing ke dalam model c#?

Saya memiliki model berikut:

public class UserPtr
{
    public int my_var1 { get; set; }
    public int my_var2 { get; set; }
    public int my_var3 { get; set; }
    public int my_var4 { get; set; }
}

Dan beberapa API respon JSON yaitu:

[ 
    {
        "name": "my_var1",
        "ptr": 1 // "Value_my_var1"
    },
    {
        "name": "my_var2",
        "ptr": 2 // "Value_my_var2"
    },
    {
        "name": "my_var3",
        "ptr": 3 // "Value_my_var3"
    },
    {
        "name": "my_var4",
        "ptr": 4 // "Value_my_var4"
    }
]

Saya ingin menyetel my_var1 = Value_my_var1, my_var2 = Value_my_var2, my_var3 = Value_my_var3

Biasanya saya akan menggunakan:

JsonConvert.DeserializeObject<UserPtr>(strJson);

Namun ketika saya melakukannya, saya mendapatkan pengecualian berikut:

Newtonsoft.Json.JsonSerializationException: Tidak dapat melakukan deserialisasi array JSON saat ini (mis. [1,2,3]) menjadi tipe 'UserPtr' karena tipe tersebut memerlukan objek JSON (mis. {name:value}) untuk melakukan deserialisasi dengan benar. Untuk memperbaiki kesalahan ini, ubah JSON menjadi objek JSON (mis. {name:value}) atau ubah tipe yang dideserialisasi menjadi array atau tipe yang mengimplementasikan antarmuka koleksi (mis. ICollection, IList) seperti Daftar yang dapat dideserialisasi dari a susunan JSON. JsonArrayAttribute juga dapat ditambahkan ke tipe untuk memaksanya melakukan deserialisasi dari array JSON.

Bagaimana saya bisa melakukan deserialisasi array objek yang berisi nama dan nilai properti ke dalam model saya?


person Chakrit    schedule 03.03.2021    source sumber
comment
Tampaknya sedikit mirip dengan Deserialize JSON dari hasil pencarian Sharepoint 2013 ke dalam daftar MyClass, di mana objek c# diserialkan sebagai array objek yang berisi pasangan Nama Properti/Nilai Properti.   -  person dbc    schedule 03.03.2021
comment
Anda tidak dapat mengonversi nilai string seperti "Value_my_var1" menjadi bilangan bulat. Bolehkah saya berasumsi bahwa ini sebenarnya adalah nilai integer yang Anda ganti saat mengetik pertanyaan Anda?   -  person dbc    schedule 04.03.2021
comment
Anda harus memposting JSON Anda yang sebenarnya dan Model kelas yang sebenarnya. Apa yang Anda posting hanyalah sekumpulan properti tanpa wadah asli dan kemungkinan besar JSON palsu.   -  person Jimi    schedule 04.03.2021
comment
Saya membuat asumsi bahwa nilai ptr sebenarnya dari objek my_varX semuanya adalah bilangan bulat yang valid, dan mengedit pertanyaan Anda untuk mencerminkan hal itu. Jika hasil edit saya salah, harap edit ulang pertanyaan Anda untuk menyertakan contoh minimal yang dapat direproduksi.   -  person dbc    schedule 04.03.2021


Jawaban (1)


Anda ingin membuat serialisasi model Anda sebagai array objek yang berisi nama properti dan nilai properti, yang nama dan nilainya berasal dari serialisasi JSON default untuk model Anda. Anda dapat melakukannya dengan generik khusus JsonConverter<T> yang menerjemahkan antara default serialisasi dan serialisasi array.

Secara default, model UserPtr Anda harus diserialkan sebagai berikut:

{
  "my_var1": 1,
  "my_var2": 2,
  "my_var3": 2,
  "my_var4": 4
}

Namun sebaliknya, Anda menerima array objek yang berisi pasangan nama/nilai tunggal seperti yang ditunjukkan dalam pertanyaan Anda, yang namanya sesuai dengan nama properti model Anda. Anda ingin mengikat array ini ke model Anda. Untuk mencapai hal ini, Anda dapat membuat konverter generik yang serupa dengan yang ada dari Deserialisasi JSON dari hasil pencarian Sharepoint 2013 ke dalam daftar Kelas Saya sebagai berikut:

public class NamePtrPropertyArrayConverter<T> : JsonConverter<T> where T : class, new()
{
    struct NamePtrDTO
    {
        public string name;
        public object ptr;
    }
    
    public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer)
    {
        var obj = (JObject)JsonExtensions.DefaultFromObject(serializer, value);
        serializer.Serialize(writer, obj.Properties().Select(p => new NamePtrDTO { name = p.Name, ptr = p.Value }));
    }

    public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
            return null;
        var array = serializer.Deserialize<List<NamePtrDTO>>(reader);
        var obj = new JObject(array.Select(i => new JProperty(i.name, i.ptr)));
        existingValue = existingValue ?? (T)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
        using (var subReader = obj.CreateReader())
            serializer.Populate(subReader, existingValue);
        return existingValue;
    }
}

public static partial class JsonExtensions
{
    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (reader.TokenType == JsonToken.None)       // Skip past beginning of stream.
            reader.ReadAndAssert();
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            reader.ReadAndAssert();
        return reader;
    }

    public static JsonReader ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }

    // DefaultFromObject() taken from this answer https://stackoverflow.com/a/29720068/3744182
    // By https://stackoverflow.com/users/3744182/dbc
    // To https://stackoverflow.com/questions/29719509/json-net-throws-stackoverflowexception-when-using-jsonconvert
    
    public static JToken DefaultFromObject(this JsonSerializer serializer, object value)
    {
        if (value == null)
            return JValue.CreateNull();
        var dto = Activator.CreateInstance(typeof(DefaultSerializationDTO<>).MakeGenericType(value.GetType()), value);
        var root = JObject.FromObject(dto, serializer);
        return root["Value"].RemoveFromLowestPossibleParent() ?? JValue.CreateNull();
    }

    public static JToken RemoveFromLowestPossibleParent(this JToken node)
    {
        if (node == null)
            return null;
        // If the parent is a JProperty, remove that instead of the token itself.
        var contained = node.Parent is JProperty ? node.Parent : node;
        contained.Remove();
        // Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
        if (contained is JProperty)
            ((JProperty)node.Parent).Value = null;
        return node;
    }

    interface IHasValue
    {
        object GetValue();
    }

    [JsonObject(NamingStrategyType = typeof(DefaultNamingStrategy), IsReference = false)]
    class DefaultSerializationDTO<T> : IHasValue
    {
        public DefaultSerializationDTO(T value) => this.Value = value;
        public DefaultSerializationDTO() { }
        [JsonConverter(typeof(NoConverter)), JsonProperty(ReferenceLoopHandling = ReferenceLoopHandling.Serialize)]
        public T Value { get; set; }
        object IHasValue.GetValue() => Value;
    }       
}

public class NoConverter : JsonConverter
{
    // NoConverter taken from this answer https://stackoverflow.com/a/39739105/3744182
    // By https://stackoverflow.com/users/3744182/dbc
    // To https://stackoverflow.com/questions/39738714/selectively-use-default-json-converter
    public override bool CanConvert(Type objectType)  { throw new NotImplementedException(); /* This converter should only be applied via attributes */ }

    public override bool CanRead => false;

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();

    public override bool CanWrite => false;

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}

Kemudian, lakukan deserialisasi dengan menambahkan konverter ke JsonSerializerSettings.Converters:

var settings = new JsonSerializerSettings
{
    Converters = { new NamePtrPropertyArrayConverter<UserPtr>() },
};
var model = JsonConvert.DeserializeObject<UserPtr>(strJson, settings);

Atau terapkan konverter langsung ke model Anda sebagai berikut:

[JsonConverter(typeof(NamePtrPropertyArrayConverter<UserPtr>))]
public class UserPtr
{
    // Contents unchanged
}

Demo biola di sini.

person dbc    schedule 03.03.2021