从 XML 文档创建的 Json 反序列化到 POCO 不适用于数组
Posted
技术标签:
【中文标题】从 XML 文档创建的 Json 反序列化到 POCO 不适用于数组【英文标题】:Deserialization to POCO from Json created of XML document does not work with arrays 【发布时间】:2021-10-15 18:49:42 【问题描述】:我有以下 C# 类
[DataContract(Name = "Person")]
public sealed class Person
[DataMember]
public string Name get; set;
[DataMember]
public List<int> Numbers get; set;
创建的对象
Person person = new Person
Name = "Test",
Numbers = new List<int> 1, 2, 3
;
被序列化为带有DataContractSerializer
的XML文档:
<Person xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Workflows.MassTransit.Hosting.Serialization">
<Name>Test</Name>
<Numbers xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d2p1:int>1</d2p1:int>
<d2p1:int>2</d2p1:int>
<d2p1:int>3</d2p1:int>
</Numbers>
</Person>
JsonConvert.SerializeXNode(xmlDocument)
返回以下 JSON:
"Person":
"@xmlns:i": "http://www.w3.org/2001/XMLSchema-instance",
"@xmlns": "http://schemas.datacontract.org/2004/07/Workflows.MassTransit.Hosting.Serialization",
"Name": "Test",
"Numbers":
"@xmlns:d2p1": "http://schemas.microsoft.com/2003/10/Serialization/Arrays",
"d2p1:int": [
"1",
"2",
"3"
]
当我使用 JsonConvert.DeserializeObject(json, typeof(Person))
将上面的 JSON 反序列化为 POCO 时,两个属性(Name
、Numbers
)都是 NULL
。
我已经尝试移除外部对象:
"@xmlns:i": "http://www.w3.org/2001/XMLSchema-instance",
"@xmlns": "http://schemas.datacontract.org/2004/07/Workflows.MassTransit.Hosting.Serialization",
"Name": "Test",
"Numbers":
"@xmlns:d2p1": "http://schemas.microsoft.com/2003/10/Serialization/Arrays",
"d2p1:int": [
"1",
"2",
"3"
]
然后出现以下异常:
Newtonsoft.Json.JsonSerializationException
HResult=0x80131500
Nachricht = Cannot deserialize the current JSON object (e.g. "name":"value") into type 'System.Collections.Generic.List`1[System.Int32]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
Path 'Numbers.@xmlns:d2p1', line 6, position 18.
Quelle = Newtonsoft.Json
Stapelüberwachung:
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
从 XML 文档转换而来的 JSON 似乎与从 POCO 直接创建的 JSON 不同。
POCO => JSON => POCO 工作正常
POCO => XML => JSON => POCO POCO 获得 NULL 属性和/或异常
是否可以配置 Json.NET 以创建兼容的 JSON 文档?
【问题讨论】:
你在为自己做不必要的工作。只要将Namespace = "http://schemas.datacontract.org/2004/07/Workflows.MassTransit.Hosting.Serialization"
添加到DataContractAttribute
,就可以使用DataContractSerializer
将XML 直接反序列化为Person
,请参阅dotnetfiddle.net/Q46exx。为什么要转码为 JSON,然后使用 Json.NET 反序列化?
【参考方案1】:
对于某些List<T>
,您可以编写一个custom JsonConverter
来反序列化从DataContractSerializer
生成的XML 转码的JSON,但它会非常复杂。考虑到 Newtonsoft 文档Converting between JSON and XML 中指定的“转换规则”:
由于同一级别的多个同名节点被组合成一个数组,因此转换过程可以根据节点的数量产生不同的JSON。
以下四种情况必须处理:
包含多个项目的列表值如下所示:
"@xmlns:d2p1": "http://schemas.microsoft.com/2003/10/Serialization/Arrays",
"d2p1:int": [
"1",
"2",
"3"
]
或(没有命名空间):
"int": [
"1",
"2",
"3"
]
具有单个值的列表如下所示:
"@xmlns:d2p1": "http://schemas.microsoft.com/2003/10/Serialization/Arrays",
"d2p1:int": "1"
或(没有命名空间):
"int": "1"
空列表如下所示:
"@xmlns:d2p1": "http://schemas.microsoft.com/2003/10/Serialization/Arrays"
或(无命名空间):
null
(是的,这是<Numbers />
的正确转码。)
null
列表如下所示:
"@xmlns:d2p1": "http://schemas.microsoft.com/2003/10/Serialization/Arrays",
"@i:nil": "true"
或者
"@i:nil": "true"
对于任何T
本身不是集合的所有List<T>
值,以下转换器应处理所有情况:
public class ListFromDataContractXmlConverter : JsonConverter
readonly IContractResolver resolver;
public ListFromDataContractXmlConverter() : this(null)
public ListFromDataContractXmlConverter(IContractResolver resolver)
// Use the global default resolver if none is passed in.
this.resolver = resolver ?? new JsonSerializer().ContractResolver;
static bool CanConvert(Type objectType, IContractResolver resolver)
Type itemType;
JsonArrayContract contract;
return CanConvert(objectType, resolver, out itemType, out contract);
static bool CanConvert(Type objectType, IContractResolver resolver, out Type itemType, out JsonArrayContract contract)
if ((itemType = objectType.GetListItemType()) == null)
itemType = null;
contract = null;
return false;
// Ensure that [JsonObject] is not applied to the type.
if ((contract = resolver.ResolveContract(objectType) as JsonArrayContract) == null)
return false;
var itemContract = resolver.ResolveContract(itemType);
// Not implemented for jagged arrays.
if (itemContract is JsonArrayContract)
return false;
return true;
public override bool CanConvert(Type objectType) return CanConvert(objectType, resolver);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
Type itemType;
JsonArrayContract contract;
if (!CanConvert(objectType, serializer.ContractResolver, out itemType, out contract))
throw new JsonSerializationException(string.Format("Invalid type for 0: 1", GetType(), objectType));
var list = (IList)(existingValue ?? contract.DefaultCreator());
switch (reader.MoveToContentAndAssert().TokenType)
case JsonToken.Null:
// This is how Json.NET transcodes an empty DataContractSerializer XML list when no namespace is present.
break;
case JsonToken.StartArray:
serializer.Populate(reader, list);
break;
case JsonToken.StartObject:
while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndObject)
var name = (string)reader.AssertTokenType(JsonToken.PropertyName).Value;
reader.ReadToContentAndAssert();
if (name.StartsWith("@"))
// It's an attribute.
if (!name.StartsWith("@xmlns:") && name.EndsWith("nil") && reader.TokenType == JsonToken.String && ((string)reader.Value) == "true")
// It's not a namespace, it's the http://www.w3.org/2001/XMLSchema-instancenil="true" null indicator attribute.
list = null;
else
// It's an element.
if (reader.TokenType == JsonToken.StartArray)
// The list had multiple items
serializer.Populate(reader, list);
else
// The list had a single item. This is it.
list.Add(serializer.Deserialize(reader, itemType));
break;
default:
throw new JsonSerializationException(string.Format("Unexpected token 0", reader.TokenType));
return list;
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
public static partial class JsonExtensions
public static JsonReader AssertTokenType(this JsonReader reader, JsonToken tokenType) =>
reader.TokenType == tokenType ? reader : throw new JsonSerializationException(string.Format("Unexpected token 0, expected 1", reader.TokenType, tokenType));
public static JsonReader ReadToContentAndAssert(this JsonReader reader) =>
reader.ReadAndAssert().MoveToContentAndAssert();
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;
public static Type GetListItemType(this Type type)
// Quick reject for performance
if (type.IsPrimitive || type.IsArray || type == typeof(string))
return null;
while (type != null)
if (type.IsGenericType)
var genType = type.GetGenericTypeDefinition();
if (genType == typeof(List<>))
return type.GetGenericArguments()[0];
type = type.BaseType;
return null;
然后,要转码和反序列化,请执行以下操作:
var xelement = XElement.Parse(xmlString);
var jsonFromXml = JsonConvert.SerializeXNode(xelement, Formatting.None, omitRootObject: true);
var settings = new JsonSerializerSettings
Converters = new ListFromDataContractXmlConverter() ,
;
var deserializedPerson = JsonConvert.DeserializeObject<Person>(jsonFromXml, settings);
注意事项:
转换器不适用于锯齿状列表(即任何List<T>
,其中T
是一些IEnumerable
其他字符串,例如List<List<int>>
或List<int []>
)。
要实现锯齿状列表,有必要将包含一个具有多个值的项目的 JSON 与包含多个项目的列表的 JSON 与每个具有一个值的列表的 JSON 与一个本身具有一个值的列表进行比较具有一个值,并确保无论是否存在命名空间都可以成功区分这些情况。
如果没有omitRootObject: true
,中间 JSON 将有一个额外级别的对象嵌套
"Person": /* The person contents */
您需要向数据模型添加额外级别的包装器对象以反序列化此类 JSON,例如:
public class RootObject public Person Person get; set;
解析为 XElement
而不是 XDocument
会从转码的 JSON 中去除 XML 声明,否则在使用 omitRootObject: true
反序列化时可能会导致问题。
使用 DataContractSerializer
直接从 XML 反序列化 Person
会容易得多。只要您将正确的命名空间添加到数据协定属性中,您就可以执行此操作(仅当它与您的 default namespace 不同时才需要这样做):
[DataContract(Name = "Person", Namespace = "http://schemas.datacontract.org/2004/07/Workflows.MassTransit.Hosting.Serialization")]
public sealed class Person
[DataMember]
public string Name get; set;
[DataMember]
public List<int> Numbers get; set;
请参阅https://dotnetfiddle.net/Q46exx 以获取演示。
这里的演示小提琴:https://dotnetfiddle.net/9B7Sdn。
【讨论】:
以上是关于从 XML 文档创建的 Json 反序列化到 POCO 不适用于数组的主要内容,如果未能解决你的问题,请参考以下文章