Newtonsoft.Json 使用数组检测将 xml 文档转换为 json
Newtonsoft.Json convertion xml document to json with array detection
我想做的是将复杂的 xml 文档转换为 json 格式,我正在使用 Newtonsoft.Json 来实现我的目标。我遇到了小问题 - 大问题。例如
我的模型看起来像:
public class assets
{
public UInt32 id {get; set;}
public String providerName {get; set;}
public String provider {get; set;}
public String realm {get; set;}
public ICollection<unit> unit {get; set;}
}
我的意图是用户将 xml 内容流式传输到将 xml 更改为 json 的方法,我将 post 更改为 API .
为了简化用户正在粘贴简单的 xml(普通的 xml 要复杂得多,但基本上它看起来像下面的许多级别的示例)
<assets xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="assets.xsd" providerName="myProviderName" provider="myProvider" realm="myCatalog">
<unit idKey="newGeo_63119679"></unit>
<unit idKey="newGeo_63119179"></unit>
</assets>
Json 结果如下:
{"@providerName":"myProviderName","@provider":"myProvider","@realm":"myCatalog","unit":[{"@idKey":"newGeo_63119679"},{"@idKey":"newGeo_63119577"}]}
如此神奇的服务看起来像:
public async ValueTask<string> AddAsset(string body)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(body);
string json = JsonConvert.SerializeXmlNode(doc, Formatting.None, true);
HttpResponseMessage response = await this._httpClient.PostAsJsonAsync("/Asset/create_asset", json);
string responseJson = await response.Content.ReadAsStringAsync();
return responseJson;
}
好吧,这部分工作正常,但是当我从 xml 中删除一个单元节点时(因此只剩下一个单元节点),我的结果是:
{"@providerName":"myProviderName","@provider":"myProvider","@realm":"myCatalog","unit":{"@idKey":"newGeo_63119679"}}
现在将其反序列化为模型所需要的数组已经消失了。我知道我可以操纵 xml 属性来添加 json:Array='true'.
但我想知道是否有更复杂的解决方案,例如 JsonConverter 可以在给定类型中搜索 属性 并检查它是否为 collection,如果是则分配它作为 json collection。怎么咬这个问题?
而且我检查过 SerializeXmlNode 没有转换器参数。
好吧,我通过混合来自@dbc 的信息和我的快乐创新找到了答案:
public class Converter<TEntity> : JsonConverter<TEntity> where TEntity : class
{
private readonly IEnumerable<Type> _entityTypes =
Assembly.GetExecutingAssembly().GetReferencedAssemblies()
.SelectMany(assembly => Assembly.Load(assembly).GetTypes().Where(t => t.Namespace == MigrationService.EntitiesNameSpace));
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, TEntity value, JsonSerializer serialize) =>
throw new NotImplementedException($"Write option is turned off for {nameof(CollectionConverter)} custom json converter");
public override TEntity ReadJson(JsonReader reader, Type objectType, TEntity existingValue, bool hasExistingValue, JsonSerializer serializer)
{
void SetSimpleTypes(JToken token, TEntity instance, PropertyInfo property)
{
var value = token[property.Name];
if(value == null) return;
var cast = Convert.ChangeType(value, property.PropertyType);
property.SetValue(instance, cast);
}
void SetClassTypes(JToken token, TEntity instance, PropertyInfo property)
{
var constructedConverterType = typeof(CollectionConverter<>).MakeGenericType(property.PropertyType);
if (Activator.CreateInstance(constructedConverterType) is not JsonConverter converter) return;
var propertyInstance = JsonConvert.DeserializeObject(token[property.Name].ToString(), property.PropertyType, converter);
property.SetValue(instance, propertyInstance);
}
void SetCollectionTypes(JToken token, TEntity instance, PropertyInfo property)
{
var constructedCollectionType = typeof(List<>).MakeGenericType(property.PropertyType.GetGenericArguments().Single());
var collectionInstance = Activator.CreateInstance(constructedCollectionType) as IList;
var value = token[property.Name];
void HandleSingleCollectionType(string body)
{
var propertyCollectionType = property.PropertyType.GetGenericArguments().Single();
var constructedConverterType = typeof(CollectionConverter<>).MakeGenericType(propertyCollectionType);
if (Activator.CreateInstance(constructedConverterType) is not JsonConverter converter) return;
var convertedInstance = JsonConvert.DeserializeObject(body, propertyCollectionType, converter);
collectionInstance?.Add(convertedInstance);
}
switch (value)
{
case JArray array:
foreach (var body in array)
{
HandleSingleCollectionType(body.ToString());
}
break;
case JObject:
HandleSingleCollectionType(value.ToString());
break;
default:
Debug.WriteLine($"Unknown or empty json token value for property {property.Name}");
break;
}
property.SetValue(instance, collectionInstance);
}
JToken token = JToken.Load(reader);
var instance = (TEntity)Activator.CreateInstance(typeof(TEntity));
var properties = instance?.GetType().GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance);
if (properties == null) return default;
foreach (PropertyInfo property in properties)
{
try
{
if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>))
{
SetCollectionTypes(token, instance, property);
}
else if (this._entityTypes.Any(type => type == property.PropertyType) && property.PropertyType.IsClass)
{
SetClassTypes(token, instance, property);
}
else
{
SetSimpleTypes(token, instance, property);
}
}
catch (NullReferenceException ex)
{
Debug.WriteLine($"Null value for entity class property {property.Name}, exception: {ex}");
}
catch (FormatException ex)
{
Debug.WriteLine($"Can not convert value property {property.Name} in {instance.GetType().Name}, exception: {ex}");
}
catch (Exception ex)
{
Debug.WriteLine($"Undefined exception for {property.Name} exception: {ex}");
}
}
return instance;
}
}
它根据 class 结构转换非常复杂的 xml 结构,所以如果 class 结构说某物是集合,它将构建集合,如果它是一个对象(我的意思是 class) 它将创建 class 的实例,依此类推。此外,它使用自身来转换 xml 的任何子项。
我想做的是将复杂的 xml 文档转换为 json 格式,我正在使用 Newtonsoft.Json 来实现我的目标。我遇到了小问题 - 大问题。例如
我的模型看起来像:
public class assets
{
public UInt32 id {get; set;}
public String providerName {get; set;}
public String provider {get; set;}
public String realm {get; set;}
public ICollection<unit> unit {get; set;}
}
我的意图是用户将 xml 内容流式传输到将 xml 更改为 json 的方法,我将 post 更改为 API .
为了简化用户正在粘贴简单的 xml(普通的 xml 要复杂得多,但基本上它看起来像下面的许多级别的示例)
<assets xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="assets.xsd" providerName="myProviderName" provider="myProvider" realm="myCatalog">
<unit idKey="newGeo_63119679"></unit>
<unit idKey="newGeo_63119179"></unit>
</assets>
Json 结果如下:
{"@providerName":"myProviderName","@provider":"myProvider","@realm":"myCatalog","unit":[{"@idKey":"newGeo_63119679"},{"@idKey":"newGeo_63119577"}]}
如此神奇的服务看起来像:
public async ValueTask<string> AddAsset(string body)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(body);
string json = JsonConvert.SerializeXmlNode(doc, Formatting.None, true);
HttpResponseMessage response = await this._httpClient.PostAsJsonAsync("/Asset/create_asset", json);
string responseJson = await response.Content.ReadAsStringAsync();
return responseJson;
}
好吧,这部分工作正常,但是当我从 xml 中删除一个单元节点时(因此只剩下一个单元节点),我的结果是:
{"@providerName":"myProviderName","@provider":"myProvider","@realm":"myCatalog","unit":{"@idKey":"newGeo_63119679"}}
现在将其反序列化为模型所需要的数组已经消失了。我知道我可以操纵 xml 属性来添加 json:Array='true'.
但我想知道是否有更复杂的解决方案,例如 JsonConverter 可以在给定类型中搜索 属性 并检查它是否为 collection,如果是则分配它作为 json collection。怎么咬这个问题?
而且我检查过 SerializeXmlNode 没有转换器参数。
好吧,我通过混合来自@dbc 的信息和我的快乐创新找到了答案:
public class Converter<TEntity> : JsonConverter<TEntity> where TEntity : class
{
private readonly IEnumerable<Type> _entityTypes =
Assembly.GetExecutingAssembly().GetReferencedAssemblies()
.SelectMany(assembly => Assembly.Load(assembly).GetTypes().Where(t => t.Namespace == MigrationService.EntitiesNameSpace));
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, TEntity value, JsonSerializer serialize) =>
throw new NotImplementedException($"Write option is turned off for {nameof(CollectionConverter)} custom json converter");
public override TEntity ReadJson(JsonReader reader, Type objectType, TEntity existingValue, bool hasExistingValue, JsonSerializer serializer)
{
void SetSimpleTypes(JToken token, TEntity instance, PropertyInfo property)
{
var value = token[property.Name];
if(value == null) return;
var cast = Convert.ChangeType(value, property.PropertyType);
property.SetValue(instance, cast);
}
void SetClassTypes(JToken token, TEntity instance, PropertyInfo property)
{
var constructedConverterType = typeof(CollectionConverter<>).MakeGenericType(property.PropertyType);
if (Activator.CreateInstance(constructedConverterType) is not JsonConverter converter) return;
var propertyInstance = JsonConvert.DeserializeObject(token[property.Name].ToString(), property.PropertyType, converter);
property.SetValue(instance, propertyInstance);
}
void SetCollectionTypes(JToken token, TEntity instance, PropertyInfo property)
{
var constructedCollectionType = typeof(List<>).MakeGenericType(property.PropertyType.GetGenericArguments().Single());
var collectionInstance = Activator.CreateInstance(constructedCollectionType) as IList;
var value = token[property.Name];
void HandleSingleCollectionType(string body)
{
var propertyCollectionType = property.PropertyType.GetGenericArguments().Single();
var constructedConverterType = typeof(CollectionConverter<>).MakeGenericType(propertyCollectionType);
if (Activator.CreateInstance(constructedConverterType) is not JsonConverter converter) return;
var convertedInstance = JsonConvert.DeserializeObject(body, propertyCollectionType, converter);
collectionInstance?.Add(convertedInstance);
}
switch (value)
{
case JArray array:
foreach (var body in array)
{
HandleSingleCollectionType(body.ToString());
}
break;
case JObject:
HandleSingleCollectionType(value.ToString());
break;
default:
Debug.WriteLine($"Unknown or empty json token value for property {property.Name}");
break;
}
property.SetValue(instance, collectionInstance);
}
JToken token = JToken.Load(reader);
var instance = (TEntity)Activator.CreateInstance(typeof(TEntity));
var properties = instance?.GetType().GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance);
if (properties == null) return default;
foreach (PropertyInfo property in properties)
{
try
{
if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>))
{
SetCollectionTypes(token, instance, property);
}
else if (this._entityTypes.Any(type => type == property.PropertyType) && property.PropertyType.IsClass)
{
SetClassTypes(token, instance, property);
}
else
{
SetSimpleTypes(token, instance, property);
}
}
catch (NullReferenceException ex)
{
Debug.WriteLine($"Null value for entity class property {property.Name}, exception: {ex}");
}
catch (FormatException ex)
{
Debug.WriteLine($"Can not convert value property {property.Name} in {instance.GetType().Name}, exception: {ex}");
}
catch (Exception ex)
{
Debug.WriteLine($"Undefined exception for {property.Name} exception: {ex}");
}
}
return instance;
}
}
它根据 class 结构转换非常复杂的 xml 结构,所以如果 class 结构说某物是集合,它将构建集合,如果它是一个对象(我的意思是 class) 它将创建 class 的实例,依此类推。此外,它使用自身来转换 xml 的任何子项。