C# 技巧:使用 JsonSchema 将 XML 转换为 JSON

作者:微信公众号:【架构师老卢】
7-4 17:1
39

概述:JSON,我们知道并喜欢它。我们一直在使用它,我们做一些与网络相关的事情。但是,当您或您的一位客户拥有一些来自只能导出 XML 文件的旧系统的数据时,会发生什么情况?你的第一反应可能是谷歌如何将XML转换为JSON,并找到有人建议你做以下事情:XmlDocument doc = new XmlDocument(); doc.LoadXml(xml); string jsonText = JsonConvert.SerializeXmlNode(doc);这有效,但效果不佳。让我们看一下这个 XML?xml version=””1.0” encoding=””UTF-8”? user

JSON,我们知道并喜欢它。我们一直在使用它,我们做一些与网络相关的事情。
但是,当您或您的一位客户拥有一些来自只能导出 XML 文件的旧系统的数据时,会发生什么情况?
你的第一反应可能是谷歌如何将XML转换为JSON,并找到有人建议你做以下事情:

XmlDocument doc = new XmlDocument();  
doc.LoadXml(xml);  
string jsonText = JsonConvert.SerializeXmlNode(doc);

这有效,但效果不佳。
让我们看一下这个 XML

<?xml version=””1.0"” encoding=””UTF-8"”?> 
   <user> 
     <firstName>jonn</firstName> 
     <lastName>smith</lastName> 
     <age>20</age> 
     <emailList> 
       <email>email1@email.com</email> 
     </emailList> 
 </user>

如果我们使用上面的代码将其转换为 JSON,我们将得到以下 JSON

{  
    "?xml": {  
        "@version": "1.0",  
        "@encoding": "UTF-8"  
    },  
    "user": {  
        "firstName": "jonn",  
        "lastName": "smith",  
        "age": "20",  
        "emailList": {  
            "email": "email1@email.com"  
        }  
    }  
}

您可能已经注意到有 2 个问题:
1.年龄现在是一个字符串,而不是一个数字

2. emailList 是一个对象而不是一个数组(在 XML 中,无法判断某物是对象还是具有单个项目的数组)

因此,在这一点上,我们意识到我们需要更复杂的东西来帮助我们进行转换。幸运的是,我们可以使用 JsonSchema。

什么是 JsonSchema?

JsonSchema 是一种描述 JSON 结构的方法,您可以使用它让其他人知道您希望如何接收数据并验证传入的 JSON 以确保它们符合架构。
您可以转到 https://jsonschema.net/ 查看它是如何工作的,您可以输入一个 JSON,站点将生成其 JsonSchema。

如果我们看一下他们的例子,对于 JSON

{  
    "checked": false,  
    "dimensions": {  
        "width": 5,  
        "height": 10  
    },  
    "id": 1,  
    "name": "A green door",  
    "price": 12.5,  
    "tags": [  
        "home",  
        "green"  
    ]  
}

生成以下 JsonSchema:

{  
    "$schema": "https://json-schema.org/draft/2019-09/schema",  
    "$id": "http://example.com/example.json",  
    "type": "object",  
    "default": {},  
    "required": [  
        "checked",  
        "dimensions",  
        "id",  
        "name",  
        "price",  
        "tags"  
    ],  
    "properties": {  
        "checked": {  
            "type": "boolean",  
            "default": false,  
            "title": "The checked Schema",  
        },  
        "dimensions": {  
            "type": "object",  
            "default": {},  
            "title": "The dimensions Schema",  
            "required": [  
                "width",  
                "height"  
            ],  
            "properties": {  
                "width": {  
                    "type": "integer",  
                    "default": 0,  
                    "title": "The width Schema",  
                },  
                "height": {  
                    "type": "integer",  
                    "default": 0,  
                    "title": "The height Schema",  
                }  
            }  
        },  
        "id": {  
            "type": "integer",  
            "default": 0,  
            "title": "The id Schema",  
        },  
        "name": {  
            "type": "string",  
            "default": "",  
            "title": "The name Schema",  
        },  
        "price": {  
            "type": "number",  
            "default": 0,  
            "title": "The price Schema"  
        },  
        "tags": {  
            "type": "array",  
            "default": [],  
            "title": "The tags Schema",  
            "items": {  
                "type": "string"  
            }  
        }  
    }  
}

关于 JsonSchema 需要注意的几件事:

  1. 它是递归的:JSON 中的嵌套对象将具有嵌套的 JsonSchema。
  2. 我们可以根据需要标记属性。
  3. 我们可以将默认值设置为要使用的属性,如果我们得到的 JSON 不包含它们的值。
  4. 对于数组(请参阅“tags”部分),我们有一个属性类型和一个“items”部分,它告诉我们数组中对象的类型

有关数组项和 C#
的注意事项从技术上讲,我们可以创建具有包含不同类型的数组的 JSON

{  
    "random": ["a", 1,{}]  
}

对于此数组,_项_将具有多种类型。如果 JSON 数组具有相同类型的项,则架构中的 items 部分将只有 1 种类型。

为了增加复杂性,表示 JsonSchema 的 C# Nugets 可能会以不同的方式处理此问题。有些将仅使用“items”属性,有些将使用“item”和“items”属性(“item”表示所有对象类型相同的普通情况,“items”表示它们不同时)。我在下面的示例代码中使用的 Nuget 使用“item”和“items”。

那么,如何转换呢?
在我们开始之前,先简单说明一下:JSON可以无限复杂,一些开发人员和公司正在导出JSON,这会让你质疑自己的理智。其中一些 JSON 甚至无法转换为 C# 对象。我在这里忽略了这些边缘情况,而只是处理属性可以

  1. 原始类型
  2. 对象数组(相同类型的对象)
  3. 基元类型数组(相同类型)
  4. 一个对象

现在我们已经解决了这个问题,让我们看一下代码:

   public class JsonConverter
    {
        public static JObject Convert(JsonSchema schema, string document) =>
                                    Convert(schema, XDocument.Parse(document));
        public static JObject Convert(JsonSchema schema, XDocument document)
        {
            return Parse(schema, document.Descendants().ToList());
        }

        private static JObject Parse(JsonSchema schema, List<XElement> elements)
        {
            var result = new JObject();

            foreach (var property in schema.Properties)
            {
                var element = elements.FirstOrDefault(e => e.Name == property.Key);
                var xmlValue = element?.Value;

                //if the XML value is NULL check the schema to see if there are a default value
                xmlValue ??= property.Value.Default?.ToString();

                //if the value is NULL and the property is required throw an Exception
                if (xmlValue == null && 
                    schema.RequiredProperties.Contains(property.Key))
                {
                    throw new Exception($"Required property [{property.Key}] is missing from XML");
                }

                if (xmlValue == null)
                {
                    break;
                }

                switch (property.Value.ActualTypeSchema.Type)
                {
                    case JsonObjectType.Boolean:
                    case JsonObjectType.String:
                    case JsonObjectType.Integer:
                    case JsonObjectType.Number:
                        result[property.Key] = ParsePlain(xmlValue, property.Value.Type);
                        break;

                    case JsonObjectType.Array:
                        if (property.Value.Item.Reference?.Type == JsonObjectType.Object)
                        {   //array of objects 
                            var array = element.Elements()
                                .Select(e => Parse(property.Value.Item.Reference,
                                                   e.Elements().ToList()));
                            
                            result[property.Key] = new JArray(array);
                        }
                        else
                        {   //array of plain items
                            var array = element.Elements()
                                .Select(c => ParsePlain(c.Value,
                                                        property.Value.Item.Type));
                           
                            result[property.Key] = new JArray(array);
                        }

                        break;

                    case JsonObjectType.Object:
                        result[property.Key] = Parse(property.Value.ActualTypeSchema,
                                                     element.Descendants().ToList());

                        break;
                }
            }

            return result;
        }

        private static JToken ParsePlain(string xmlValue, JsonObjectType type)
        {
            return type switch
            {
                JsonObjectType.String => xmlValue,
                JsonObjectType.Boolean => bool.Parse(xmlValue),
                JsonObjectType.Integer => int.Parse(xmlValue),
                JsonObjectType.Number => double.Parse(xmlValue)
            };
        }
    }

让我们来分解一下:

Parse 方法完成所有工作。

它遍历架构属性,对于每个属性,我们执行以下操作:

  1. 尝试获取该属性的 XML 值
foreach (var property in schema.Properties)   
{   
    var element = elements.FirstOrDefault(e => e.Name == property.Key);  
   var xmlValue = element?.Value;

2.如果XML值为NULL,则尝试从架构中设置默认值(如果定义了一个值)

xmlValue ??= property.Value.Default?.ToString();

3. 如果 XML 值为 NULL 并且该属性是必需的,则引发异常。如果不需要,请转到下一个属性

if (xmlValue == null && schema.RequiredProperties.Contains(property.Key))   
{   
  throw new Exception($”Required property \[{property.Key}\] is missing from XML”);  
}   
  
if (xmlValue == null) { break; }

4. 将 XML 值解析为 JSON 值

switch (property.Value.ActualTypeSchema.Type)
{
    case JsonObjectType.Boolean:
    case JsonObjectType.String:
    case JsonObjectType.Integer:
    case JsonObjectType.Number:
        result[property.Key] = ParsePlain(xmlValue, property.Value.Type);
        break;

    case JsonObjectType.Array:
        if (property.Value.Item.Reference?.Type == JsonObjectType.Object)
        {   //array of objects 
            var array = element.Elements()
                .Select(e => Parse(property.Value.Item.Reference,
                                   e.Elements().ToList()));
            
            result[property.Key] = new JArray(array);
        }
        else
        {   //array of plain items
            var array = element.Elements()
                .Select(c => ParsePlain(c.Value,
                                        property.Value.Item.Type));
           
            result[property.Key] = new JArray(array);
        }

        break;

    case JsonObjectType.Object:
        result[property.Key] = Parse(property.Value.ActualTypeSchema,
                                     element.Descendants().ToList());

        break;
}

 private static JToken ParsePlain(string xmlValue, JsonObjectType type)
  {
      return type switch
      {
          JsonObjectType.String => xmlValue,
          JsonObjectType.Boolean => bool.Parse(xmlValue),
          JsonObjectType.Integer => int.Parse(xmlValue),
          JsonObjectType.Number => double.Parse(xmlValue)
      };
  }

这里有 4 种情况:

  1. 原始类型 - 我们只需将 XML 值解析为正确的类型(使用 ParsePlain 方法)。
  2. 对象数组 - 我们遍历元素子元素,并对每个元素子元素使用对 Parse 方法的递归调用。
  3. 基元类型的数组 - 我们遍历元素子元素,并在每个子元素上调用 ParsePlain 方法。
  4. 一个对象 - 我们对 XML 元素和嵌套架构使用对 Parse 方法的递归调用来创建嵌套的 JSON 对象。

一旦我们遍历了所有架构属性,我们只需返回我们创建的 JSON 对象。

此代码涵盖了最常见的用例,您可能需要对其进行扩展以涵盖对象或数组可以使用多种不同类型的情况。

//if the value of an array can change from:
{
    "array": [ "a","b"]
}
// to:
{
    "array": [  {  "name": "a"},{  "name": "b"} ]
}

源代码获取:公众号回复消息【code:32007

相关代码下载地址
重要提示!:取消关注公众号后将无法再启用回复功能,不支持解封!
第一步:微信扫码关键公众号“架构师老卢”
第二步:在公众号聊天框发送code:32007,如:code:32007 获取下载地址
第三步:恭喜你,快去下载你想要的资源吧
阅读排行