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文件中可以观察到以下约定关系:
- 在Json文件中以“-”开头的键,我们约定这样的键对应于XML文件中节点的属性名,如“-location”就是对应XML文件中School节点的location属性;
- 对于Json文件中键名为“#text”的,我们约定其对应的值对应于XML文件中的XmlNodeType.Text类型节点,如 “#text”: “计算机科学与技术” 在Xml文件中对应于"<Profession name=“kdkkds”>计算机科学与技术</Profession>";
- 另外,还有一种形式对应于Xml中XmlNodeType.Text类型节点。如:
"Profession": [
"软件工程",
"微电子",
]
对应于:
<Profession>软件工程</Profession>
<Profession>微电子</Profession>
了解了以上的对应关系之后,我们可以进入我们的主题了,正式开始研究Json格式的数据转换成Xml的算法。
2.Json格式转换成Xml格式算法研究
2.1 获取重要信息
首先我们通过不断观察对比Json文件和Xml文件,可以先初步得到以下重要信息:
- 在Json文件中,凡是引号中的数据,要么是键要么是值:
- 如果是键,并且以“-”开头,则该键值对应于Xml文件中的节点的属性民和属性值;
- 如果是键,不以“-”开头且不为“#text”,则该键的名对应于Xml文件中的节点名;
- 如果是值,则该引号的前面一定是一个冒号。
- 在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 主要算法内容(约束较多,内容很多,需耐心思考):
算法核心逻辑如下:
- 遇到“{”和“[”则将其放入signStack栈中;若遇到“}”,则signStack弹栈并调用WriteEndElement方法写入结束元素,若遇到“]”则仅仅需signStack弹栈即可;
- 如果读到“,”,则置isCommaBefore为true;如果遇到引号则置isCommaBefore为false;
- 如果引号之前的符号为“:”,那么该引号中数据则对应于属性值或者是XmlNodeType.Text节点,置isKey为false;如果读到“{”或者“,”,则需要置isKey为true;
- 如果当前引号中的数据是属性名,则用tempAttr记录下来,等待获取下个引号中的数据,然后调用XmlWriter.WriteAttributeString方法把属性键值对写入当前节点,并置isTempElementBefore 为false;
- 如果当前引号中的数据是元素名,则直接调用XmlWriter.WriteStartElement方法写入开始元素,置isTempElementBefore 为true,并用tempElement记录下来,若接下来遇到符号“[”时,则需要把tempElement放入jsonArrStack栈中;
- .如果当前的引号中的数据为“#text”,则需要置isTextNode为true,表明接下来的引号中的数据是XmlNodeType.Text节点,需要调用XmlWriter.WriteString来写入Xml文件,并且每一次调用该方法后需要重新置isTextNode为false;
- 若当前引号中的数据不为键,即isKey=false,并且此时isTextNode或者isTempElementBefore 为true时,则表明该数据对应XmlNodeType.Text类型节点,需调用XmlWriter.WriteString方法写入,否则就是属性的值,调用XmlWriter.WriteAttributeString方法即可。
- ."[ ]"中的每一个对象,要么是以引号开头,要么以“{”开头,请仔细思考下面两种情况:
- 当以引号开头时,则表明当前引号中的数据是对应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