如何将包含具有单个 属性 名称和值的对象数组的 JSON 反序列化到 C# 模型中?

How to deserialize JSON containing an array of objects with a single property name and value each into a c# 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; }
}

还有一些 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: Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'UserPtr' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly. To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.

如何将这个包含 属性 个名称和值的对象数组反序列化到我的模型中?

您想将模型序列化为包含 属性 名称和 属性 值的对象数组,其中名称和值来自“默认”JSON 序列化你的模型。您可以使用 custom generic JsonConverter<T> 在默认序列化和数组序列化之间进行转换。

默认情况下,您的 UserPtr 模型应按如下方式序列化:

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

但是,您收到的对象数组包含单个 name/value 对,如您的问题所示,其中名称对应于模型的 属性 名称。您希望将此数组绑定到您的模型。为此,您可以创建一个类似于 中的通用转换器,如下所示:

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 
    // By https://whosebug.com/users/3744182/dbc
    // To 
    
    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 
    // By https://whosebug.com/users/3744182/dbc
    // To 
    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
}

演示 fiddle here.