Json格式转换成XML格式------不使用第三方类库实现

Json格式转换成XML格式

本篇算法的研究完全没有使用第三方类库来实现,完全使用读取字节控制转换的方法。想研究算法的读者们可以阅读此文章。

XML文件与Json文件在线互转:http://www.bejson.com/xml2json

1.Json格式和XML格式对应关系

在研究Json格式数据转换成Xml格式的数据之前,首先,我们需要了解在Json文件中约定的键的格式和XML文件的对应。给下面一个Json文件和一个Xml文件,先观察一下两者的对应关系。

Json文件:

{
  "Schools": {
    "School": [
      {
        "-name": "南京工业大学",
        "-location": "南京",
        "Department": {
          "-name": "测绘科学与技术学院",
          "Profession": [
            "地理信息科学",
            {
              "Pro": [
                "测绘工程",
                "土木工程"
              ],
              "Other": "其他专业"
            },
            "轨道工程"
          ]
        }
      },
      {
        "-name": "复旦大学",
        "-location": "上海",
        "Department": {
          "-name": "计算机科学与技术学院",
          "Profession": [
            "计算机网络",
            {
              "-name": "kdkkds",
              "#text": "计算机科学与技术"
            },
            "计算机软件"
          ]
        }
      },
      {
        "-name": "北京大学",
        "-location": "北京",
        "Department": {
          "-name": "北大软微",
          "Profession": [
            "软件工程",
            "微电子",
          ]
        }
      }
    ]
  }
}

Xml文件:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Schools>
  <School name="南京工业大学" location="南京">
    <Department name="测绘科学与技术学院">
      <Profession>地理信息科学</Profession>
      <Profession>
        <Pro>测绘工程</Pro>
        <Pro>土木工程</Pro>
        <Other>其他专业</Other>
      </Profession>
      <Profession>轨道工程</Profession>
    </Department>
  </School>
  <School name="复旦大学" location="上海">
    <Department name="计算机科学与技术学院">
      <Profession>计算机网络</Profession>
      <Profession name="kdkkds">计算机科学与技术</Profession>
      <Profession>计算机软件</Profession>
    </Department>
  </School>
  <School name="北京大学" location="北京">
    <Department name="北大软微">
      <Profession>软件工程</Profession>
      <Profession>微电子</Profession>
    </Department>
  </School>
</Schools>

我们从上面的Json文件和Xml文件中可以观察到以下约定关系:

  1. 在Json文件中以“-”开头的键,我们约定这样的键对应于XML文件中节点的属性名,如“-location”就是对应XML文件中School节点的location属性;
  2. 对于Json文件中键名为“#text”的,我们约定其对应的值对应于XML文件中的XmlNodeType.Text类型节点,如 “#text”: “计算机科学与技术” 在Xml文件中对应于"<Profession name=“kdkkds”>计算机科学与技术</Profession>";
  3. 另外,还有一种形式对应于Xml中XmlNodeType.Text类型节点。如:
          "Profession": [
            "软件工程",
            "微电子",
          ]

对应于:

      <Profession>软件工程</Profession>
      <Profession>微电子</Profession>

了解了以上的对应关系之后,我们可以进入我们的主题了,正式开始研究Json格式的数据转换成Xml的算法。

2.Json格式转换成Xml格式算法研究

2.1 获取重要信息

首先我们通过不断观察对比Json文件和Xml文件,可以先初步得到以下重要信息:

  1. 在Json文件中,凡是引号中的数据,要么是键要么是值:
  • 如果是键,并且以“-”开头,则该键值对应于Xml文件中的节点的属性民和属性值;
  • 如果是键,不以“-”开头且不为“#text”,则该键的名对应于Xml文件中的节点名;
  • 如果是值,则该引号的前面一定是一个冒号。
  1. 在Json文件中,凡是在“[”和“]”里面的同级对象,对应于Xml文件中多个同级且同名的节点。
    如:
         "Profession": [
            "计算机网络",
            {
              "-name": "kdkkds",
              "#text": "计算机科学与技术"
            },
            "计算机软件"
          ]

对应于:

      <Profession>计算机网络</Profession>
      <Profession name="kdkkds">计算机科学与技术</Profession>
	  <Profession>计算机软件</Profession>

正因如此,我们需要在转换的过程中需要保存这个Json数组的名字“Profession”,以供写入Xml使用。

2.2 算法的具体设计以及详细代码展示

为了方便使用写入过程中的信息,这里面定义两个栈来存储符号和Json数组名信息:

        public static Stack<string> signStack = new Stack<string>();//存放符号栈
        public static Stack<string> jsonArrStack = new Stack<string>();//存放数组对象的名字的栈

另外需要用到的临时变量:

        bool isKey = true;//用来判断是否是key
        bool isTextNode = false;//用来判断是否是值节点

        bool isAlreadyWriteFirstStart = false; //用来判断是否是值节点数组
        
        string tempAttr = "";//存放临时属性
        string tempElement = "";//存放临时元素名
        bool isTempElementBefore = false;//用来判断前一个引号中的数据是否是Xml文件中的元素名
        
        bool isCommaBefore = false;//用来判断某项操作前的一个符号是否是一个逗号
        List<byte> quoteContentBytes = new List<byte>();//存放引号中的数据

使用FileStream逐步读取Json文件中的字节,使用XmlWriter将读到的信息写入Xml文件:

using (FileStream fsRead = new FileStream(path, FileMode.Open, FileAccess.Read))
 {
      //为XmlReader对象设置settings
      XmlReaderSettings settingsReader = new XmlReaderSettings();
      settingsReader.IgnoreComments = true;
      settingsReader.IgnoreWhitespace = true;

      //为XmlWriter对象设置settings
      XmlWriterSettings settingsWriter = new XmlWriterSettings();
      settingsWriter.Indent = true;//要求缩进
      settingsWriter.Encoding = new UTF8Encoding(true);
      settingsWriter.NewLineChars = Environment.NewLine; //设置换行符

      string filePath = path.Substring(0, path.LastIndexOf("."));
      string writerPath = filePath + ".xml";

      using (XmlWriter xmlWriter = XmlWriter.Create(writerPath, settingsWriter))
      {
          xmlWriter.WriteStartDocument(false);

          int bt = fsRead.ReadByte();
          while (bt != -1)
          {
             ........//主要算法内容
             bt = fsRead.ReadByte();
          }
     }
 }

2.3 主要算法内容(约束较多,内容很多,需耐心思考):

算法核心逻辑如下:

  1. 遇到“{”和“[”则将其放入signStack栈中;若遇到“}”,则signStack弹栈并调用WriteEndElement方法写入结束元素,若遇到“]”则仅仅需signStack弹栈即可;
  2. 如果读到“,”,则置isCommaBefore为true;如果遇到引号则置isCommaBefore为false;
  3. 如果引号之前的符号为“:”,那么该引号中数据则对应于属性值或者是XmlNodeType.Text节点,置isKey为false;如果读到“{”或者“,”,则需要置isKey为true;
  4. 如果当前引号中的数据是属性名,则用tempAttr记录下来,等待获取下个引号中的数据,然后调用XmlWriter.WriteAttributeString方法把属性键值对写入当前节点,并置isTempElementBefore 为false;
  5. 如果当前引号中的数据是元素名,则直接调用XmlWriter.WriteStartElement方法写入开始元素,置isTempElementBefore 为true,并用tempElement记录下来,若接下来遇到符号“[”时,则需要把tempElement放入jsonArrStack栈中;
  6. .如果当前的引号中的数据为“#text”,则需要置isTextNode为true,表明接下来的引号中的数据是XmlNodeType.Text节点,需要调用XmlWriter.WriteString来写入Xml文件,并且每一次调用该方法后需要重新置isTextNode为false;
  7. 若当前引号中的数据不为键,即isKey=false,并且此时isTextNode或者isTempElementBefore 为true时,则表明该数据对应XmlNodeType.Text类型节点,需调用XmlWriter.WriteString方法写入,否则就是属性的值,调用XmlWriter.WriteAttributeString方法即可。
  8. ."[ ]"中的每一个对象,要么是以引号开头,要么以“{”开头,请仔细思考下面两种情况
  • 当以引号开头时,则表明当前引号中的数据是对应XmlNodeType.Text节点,该[ ]中的所有对象,只有第一个对象只需要调用WriteEndElement,而其他对象则既需要调用WriteStartElement,也需要调用WriteEndElement,这里面设置isAlreadyWriteFirstStart ,在每一个“[“后将isAlreadyWriteFirstStart 置为true来表示已经写过第一个对象,在第一个对象读完后将isAlreadyWriteFirstStart 置为false,便是接下来[ ]中的对象需要调用WriteStartElement。在“}”和“]”后,将会根据signStack的栈顶元素是否为“[”来设置isAlreadyWriteFirstStart 的值。
  • 当以“{”开头时,同样,因为在“[”前已经写过一次且仅写了一次StartElement,所以对[ ]中的除了第一对象的其他对象需要我们手动写一个StartElement,而这个元素名也是我们已经存放在jsonArrStack中的栈顶值。那么如何判断是否是除了第一个的其他对象呢?仔细观察一下,不难发现凡是在"{"前有一个逗号的,则该{ }中的数据一定是数组中的除了第一个的其他对象,我们就可根据此信息手动调用一下XmlWriter.StartElement(jsonArrStack.Peek())方法。

代码如下:

        //为冒号“:”
        if (bt == 58)
            isKey = false;

        if (bt == 34)//为引号
        {
            int b = fsRead.ReadByte();

            //读取引号中的内容
            while (b != 34)
            {
                quoteContentBytes.Add(Convert.ToByte(b));
                b = fsRead.ReadByte();
            }
            string result = System.Text.Encoding.UTF8.GetString(quoteContentBytes.ToArray());
            if (signStack.Peek() == "[")
            {
                if (!isAlreadyWriteFirstStart)
                    xmlWriter.WriteStartElement(jsonArrStack.Peek());
                isAlreadyWriteFirstStart = false;
                xmlWriter.WriteString(result);
                xmlWriter.WriteEndElement();
            }
            else
            {
                if (isKey)//为key
                {
                    //是属性Attribute
                    if (result[0].ToString() == "-")
                    {
                        string attr = Regex.Replace(result, "-", "");
                        tempAttr = attr;

                        isTempElementBefore = false;
                    }
                    else if (result != "#text")//是子节点
                    {
                        tempElement = result;
                        xmlWriter.WriteStartElement(tempElement);

                        isTempElementBefore = true;
                    }
                    else//是包含Value的节点
                        isTextNode = true;
                }
                else//为值
                {
                    if (isTextNode || isTempElementBefore)
                    {
                        xmlWriter.WriteString(result);
                        isTextNode = false;
                        if (isTempElementBefore)
                            xmlWriter.WriteEndElement();
                    }
                    else
                        xmlWriter.WriteAttributeString(tempAttr, result);
                }
            }

            quoteContentBytes.Clear();//清空
            isCommaBefore = false;
        }
        else if (bt == 123)//为“{”
        {
            //若是逗号,则表示是一个[]对象中的除了第一个的其他对象。
            if (isCommaBefore)
                xmlWriter.WriteStartElement(jsonArrStack.Peek());

            signStack.Push("{");//符号进栈
            isKey = true;

            isAlreadyWriteFirstStart = false;
            isTempElementBefore = false;
        }
        else if (bt == 91)
        {
            signStack.Push("[");//符号进栈
            jsonArrStack.Push(tempElement);//此处tempElement存放便是“[”之前的一个引号中的数据

            isAlreadyWriteFirstStart = true;

            isTempElementBefore = false;

        }
        else if (bt == 125)//为“}”时,jsonArrStack退栈,并写入WriteEndElement
        {
            string t = signStack.Pop();//符号出栈
            //自检,用于检测是否有括号不匹配现象
            if ((bt == 125 && t != "{") || (bt == 93 && t != "["))
                Console.WriteLine(tempElement);

            //如果是最后一个“{”,对应于第一个进栈的,也不写关闭元素;
            if (signStack.Count > 1)
            {
                xmlWriter.WriteEndElement();
                isAlreadyWriteFirstStart = signStack.Peek() != "[";
            }

        }
        else if (bt == 93) //为“]”时,jsonArrStack退栈
        {
            string t = signStack.Pop();//符号出栈
            //自检,用于检测是否有括号不匹配现象
            if ((bt == 125 && t != "{") || (bt == 93 && t != "["))
                Console.WriteLine(tempElement);

            //若为“]”,则arrStack出栈
            jsonArrStack.Pop();

            isAlreadyWriteFirstStart = signStack.Peek() != "[";
        }

        //逗号
        if (bt == 44)
        {
            isKey = true;
            isCommaBefore = true;//记录下一次读取数据之前的符号为逗号
        }

2.4 总体代码

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;

namespace JSONDataToXML
{
    class Program
    {
        public static Stack<string> signStack = new Stack<string>();//存放符号栈
        public static Stack<string> jsonArrStack = new Stack<string>();//存放数组对象的名字的栈

        public static List<byte> quoteContentBytes = new List<byte>();//存放引号中的数据

        bool isKey = true;//用来判断是否是key
        bool isTextNode = false;//用来判断是否是值节点

        bool isAlreadyWriteFirstStart = false; //用来判断是否是值节点数组
        int valueArrDeepth = 0;

        string tempAttr = "";//存放临时属性
        string tempElement = "";//存放临时元素名
        bool isTempElementBefore = false;
        bool isCommaBefore = false;//用来判断某项操作前的一个符号是否是一个逗号
        
        static void Main(string[] args)
        {
            Console.WriteLine("请输入Json文件路径:");
            string path = Console.ReadLine();

            Stopwatch sw = new Stopwatch();
            sw.Start();

            //解析Json
            JsonDataToXml(path);

            sw.Stop();
            Console.WriteLine("用时:" + sw.Elapsed.ToString());

            Console.WriteLine("解析成功!");
            Console.ReadKey();
        }

        public static void JsonDataToXml(string path)
        {
                using (XmlWriter xmlWriter = XmlWriter.Create(writerPath, settingsWriter))
                {
                    xmlWriter.WriteStartDocument(false);

                    int bt = fsRead.ReadByte();
                    bool isKey = true;//用来判断是否是key
                    bool isTextNode = false;//用来判断是否是值节点

                    bool isAlreadyWriteFirstStart = false; //用来判断是否是值节点数组

                    string tempAttr = "";//存放临时属性
                    string tempElement = "";//存放临时元素名
                    bool isTempElementBefore = false;
                    bool isCommaBefore = false;//用来判断某项操作前的一个符号是否是一个逗号
                    while (bt != -1)
                    {
                        //为冒号“:”
                        if (bt == 58)
                            isKey = false;

                        if (bt == 34)//为引号
                        {
                            int b = fsRead.ReadByte();

                            //读取引号中的内容
                            while (b != 34)
                            {
                                quoteContentBytes.Add(Convert.ToByte(b));
                                b = fsRead.ReadByte();
                            }
                            string result = System.Text.Encoding.UTF8.GetString(quoteContentBytes.ToArray());
                            if (signStack.Peek() == "[")
                            {
                                if (!isAlreadyWriteFirstStart)
                                    xmlWriter.WriteStartElement(jsonArrStack.Peek());
                                isAlreadyWriteFirstStart = false;
                                xmlWriter.WriteString(result);
                                xmlWriter.WriteEndElement();
                            }
                            else
                            {
                                if (isKey)//为key
                                {
                                    //是属性Attribute
                                    if (result[0].ToString() == "-")
                                    {
                                        string attr = Regex.Replace(result, "-", "");
                                        tempAttr = attr;

                                        isTempElementBefore = false;
                                    }
                                    else if (result != "#text")//是子节点
                                    {
                                        tempElement = result;
                                        xmlWriter.WriteStartElement(tempElement);

                                        isTempElementBefore = true;
                                    }
                                    else//是包含Value的节点
                                        isTextNode = true;
                                }
                                else//为值
                                {
                                    if (isTextNode || isTempElementBefore)
                                    {
                                        xmlWriter.WriteString(result);
                                        isTextNode = false;
                                        if (isTempElementBefore)
                                            xmlWriter.WriteEndElement();
                                    }
                                    else
                                        xmlWriter.WriteAttributeString(tempAttr, result);
                                }
                            }

                            quoteContentBytes.Clear();//清空
                            isCommaBefore = false;
                        }
                        else if (bt == 123)//为“{”
                        {
                            //若是逗号,则表示是一个[]对象中的除了第一个的其他对象。
                            if (isCommaBefore)
                                xmlWriter.WriteStartElement(jsonArrStack.Peek());

                            signStack.Push("{");//符号进栈
                            isKey = true;

                            isAlreadyWriteFirstStart = false;
                            isTempElementBefore = false;
                        }
                        else if (bt == 91)
                        {
                            signStack.Push("[");//符号进栈
                            jsonArrStack.Push(tempElement);//此处tempElement存放便是“[”之前的一个引号中的数据

                            isAlreadyWriteFirstStart = true;

                            isTempElementBefore = false;

                        }
                        else if (bt == 125)//为“}”时,jsonArrStack退栈,并写入WriteEndElement
                        {
                            string t = signStack.Pop();//符号出栈
                            //自检,用于检测是否有括号不匹配现象
                            if ((bt == 125 && t != "{") || (bt == 93 && t != "["))
                                Console.WriteLine(tempElement);

                            //如果是最后一个“{”,对应于第一个进栈的,也不写关闭元素;
                            if (signStack.Count > 1)
                            {
                                xmlWriter.WriteEndElement();
                                isAlreadyWriteFirstStart = signStack.Peek() != "[";
                            }

                        }
                        else if (bt == 93) //为“]”时,jsonArrStack退栈
                        {
                            string t = signStack.Pop();//符号出栈
                            //自检,用于检测是否有括号不匹配现象
                            if ((bt == 125 && t != "{") || (bt == 93 && t != "["))
                                Console.WriteLine(tempElement);

                            //若为“]”,则arrStack出栈
                            jsonArrStack.Pop();

                            isAlreadyWriteFirstStart = signStack.Peek() != "[";
                        }

                        //逗号
                        if (bt == 44)
                        {
                            isKey = true;
                            isCommaBefore = true;//记录下一次读取数据之前的符号为逗号
                        }
                        bt = fsRead.ReadByte();
                    }
                }
            }
        }
    }
}

结语:本人在研究Json格式转换成Xml格式的时候仅使用自己能想到的Json格式,在此基础上去设计算法,可能会有一些我没有考虑到的情况,若读者发现有代码或逻辑控制有缺陷,可反馈在评论,谢谢。
相应github地址:https://github.com/LuQiJun/Json2Xml

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值