简体   繁体   English

如何将包含具有单个属性名称和值的对象数组的 JSON 反序列化为 c# model?

[英]How to deserialize JSON containing an array of objects with a single property name and value each into a c# model?

I have the following model:我有以下 model:

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

And some API response JSON which is:还有一些 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"
    }
]

I want to set my_var1 = Value_my_var1, my_var2 = Value_my_var2, my_var3 = Value_my_var3我想设置my_var1 = Value_my_var1, my_var2 = Value_my_var2, my_var3 = Value_my_var3

Normally I would use:通常我会使用:

JsonConvert.DeserializeObject<UserPtr>(strJson);

But when I do, I get the following exception:但是当我这样做时,我得到以下异常:

Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON array (eg [1,2,3]) into type 'UserPtr' because the type requires a JSON object (eg {"name":"value"}) to deserialize correctly. Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON array (eg [1,2,3]) into type 'UserPtr' because the type requires a JSON object (eg {"name":"value"}) to deserialize correctly. To fix this error either change the JSON to a JSON object (eg {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (eg ICollection, IList) like List that can be deserialized from a JSON array.要修复此错误,请将 JSON 更改为 JSON object (例如 {"name":"value"})或将实现的反序列化类型接口更改为数组或集合从 JSON 阵列反序列化。 JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array. JsonArrayAttribute 也可以添加到类型中以强制它从 JSON 数组反序列化。

How can I deserialize this array of objects containing property names and values into my model?如何将这个包含属性名称和值的对象数组反序列化到我的 model 中?

You would like to serialize your model as an array of objects containing property names and property values, where the names and values come from the "default" JSON serialization for your model.您想将 model 序列化为包含属性名称和属性值的对象数组,其中名称和值来自 Z20F35E630DAF44DBFA4C3F68F5399DC8 的“默认”JSON 序列化。 You can do this with a custom generic JsonConverter<T> that translates between the default serialization and the array serialization.您可以使用在默认序列化和数组序列化之间转换的自定义通用JsonConverter<T>来执行此操作。

By default, your UserPtr model should be serialized as follows:默认情况下,您的UserPtr model 应序列化如下:

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

But instead, you are receiving an array of objects containing single name/value pairs as shown in your question, where the names correspond to your model's property names.但相反,您收到的对象数组包含单个名称/值对,如您的问题所示,其中名称对应于模型的属性名称。 You would like to bind this array to your model.您想将此数组绑定到您的 model。 To accomplish this, you can create a generic converter similar to the one from Deserialize JSON from a Sharepoint 2013 search result into a list of MyClass as follows:为此,您可以创建一个通用转换器,类似于Deserialize 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();
}

Then, either deserialize by adding the converter to JsonSerializerSettings.Converters :然后,通过将转换器添加到JsonSerializerSettings.Converters来反序列化:

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

Or apply the converter directly to your model as follows:或者将转换器直接应用于您的 model,如下所示:

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

Demo fiddle here .演示小提琴在这里

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM