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 的任何子项。