วิธีดีซีเรียลไลซ์ JSON ที่มีอาร์เรย์ของอ็อบเจ็กต์ที่มีชื่อคุณสมบัติเดียวและให้ค่าแต่ละรายการเป็นรูปแบบ c #

ฉันมีรุ่นต่อไปนี้:

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

และการตอบสนอง API บางส่วน JSON ซึ่งก็คือ:

[ 
    {
        "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"
    }
]

ฉันต้องการตั้งค่า my_var1 = Value_my_var1, my_var2 = Value_my_var2, my_var3 = Value_my_var3

โดยปกติฉันจะใช้:

JsonConvert.DeserializeObject<UserPtr>(strJson);

แต่เมื่อฉันทำ ฉันจะได้รับข้อยกเว้นต่อไปนี้:

Newtonsoft.Json.JsonSerializationException: ไม่สามารถทำการดีซีเรียลไลซ์อาร์เรย์ JSON ปัจจุบัน (เช่น [1,2,3]) ให้เป็นประเภท 'UserPtr' ได้ เนื่องจากประเภทนั้นต้องใช้วัตถุ JSON (เช่น {name:value}) เพื่อทำการดีซีเรียลไลซ์อย่างถูกต้อง หากต้องการแก้ไขข้อผิดพลาดนี้ ให้เปลี่ยน JSON เป็นออบเจ็กต์ JSON (เช่น {name:value}) หรือเปลี่ยนประเภทดีซีเรียลไลซ์เป็นอาร์เรย์หรือประเภทที่ใช้อินเทอร์เฟซการรวบรวม (เช่น ICollection, IList) เช่น List ที่สามารถดีซีเรียลไลซ์ได้จาก อาร์เรย์ JSON JsonArrayAttribute สามารถเพิ่มลงในประเภทเพื่อบังคับให้ทำการดีซีเรียลไลซ์จากอาร์เรย์ JSON ได้

ฉันจะดีซีเรียลไลซ์อาร์เรย์ของอ็อบเจ็กต์ที่มีชื่อคุณสมบัติและค่าลงในโมเดลของฉันได้อย่างไร


person Chakrit    schedule 03.03.2021    source แหล่งที่มา
comment
ดูเหมือนจะคล้ายกับ ดีซีเรียลไลซ์ JSON จากผลการค้นหา Sharepoint 2013 ลงในรายการ MyClass ซึ่งวัตถุ c# จะถูกทำให้เป็นอนุกรม เป็นอาร์เรย์ของอ็อบเจ็กต์ที่มีคู่ชื่อคุณสมบัติ / ค่าคุณสมบัติ   -  person dbc    schedule 03.03.2021
comment
คุณไม่สามารถแปลงค่าสตริง เช่น "Value_my_var1" เป็นจำนวนเต็มได้ ฉันขอสรุปได้ไหมว่าสิ่งเหล่านี้เป็นค่าจำนวนเต็มที่มีรูปแบบที่ถูกต้องซึ่งคุณแทนที่เมื่อพิมพ์คำถามของคุณ   -  person dbc    schedule 04.03.2021
comment
คุณควรโพสต์ JSON จริงของคุณและโมเดลคลาสจริง สิ่งที่คุณโพสต์เป็นเพียงคุณสมบัติจำนวนมากที่ไม่มี คอนเทนเนอร์ จริง และส่วนใหญ่อาจเป็น JSON ปลอม   -  person Jimi    schedule 04.03.2021
comment
ฉันตั้งสมมติฐานว่าค่า ptr ที่แท้จริงของวัตถุ my_varX เป็นจำนวนเต็มที่ถูกต้องทั้งหมด และแก้ไขคำถามของคุณเพื่อสะท้อนถึงสิ่งนั้น หากการแก้ไขของฉันไม่ถูกต้อง โปรดแก้ไขคำถามของคุณอีกครั้งเพื่อรวมตัวอย่างที่ทำซ้ำได้น้อยที่สุด   -  person dbc    schedule 04.03.2021


คำตอบ (1)


คุณต้องการทำให้โมเดลของคุณเป็นอนุกรมเป็นอาร์เรย์ของออบเจ็กต์ที่มีชื่อคุณสมบัติและค่าคุณสมบัติ โดยที่ชื่อและค่ามาจากการทำให้เป็นอนุกรม JSON เริ่มต้นสำหรับโมเดลของคุณ คุณสามารถทำได้ด้วย ทั่วไปที่กำหนดเอง JsonConverter<T> ที่แปลระหว่างค่าเริ่มต้น การทำให้เป็นอนุกรมและการทำให้เป็นอนุกรมของอาร์เรย์

ตามค่าเริ่มต้น โมเดล UserPtr ของคุณควรเป็นแบบอนุกรมดังนี้:

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

แต่คุณได้รับอาร์เรย์ของออบเจ็กต์ที่มีคู่ชื่อ/ค่าเดียวดังที่แสดงในคำถามของคุณ โดยที่ชื่อนั้นสอดคล้องกับชื่อคุณสมบัติของโมเดลของคุณ คุณต้องการเชื่อมโยงอาร์เรย์นี้กับโมเดลของคุณ เพื่อให้บรรลุเป้าหมายนี้ คุณสามารถสร้างตัวแปลงทั่วไปที่คล้ายกับตัวแปลงจาก ยกเลิกการซีเรียลไลซ์ JSON จากผลการค้นหา Sharepoint 2013 ลงในรายการ MyClass ดังต่อไปนี้:

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

จากนั้น ให้ทำการดีซีเรียลไลซ์โดยการเพิ่มตัวแปลงลงใน JsonSerializerSettings.Converters:

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

หรือใช้ตัวแปลงโดยตรงกับโมเดลของคุณดังนี้:

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

สาธิตซอที่นี่

person dbc    schedule 03.03.2021