如何将包含具有单个 属性 名称和值的对象数组的 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.
我有以下型号:
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.