.Net中XML,JSON的几种处理方式
一、xml:
1、基本了解:
xml,extensible markup language可扩展标记语言,用于数据的传输或保存,特点,格式非常整齐数据清晰明了,并且任何语言都内置了xml分析引擎,
不需要再单独写引擎,但是相较于json解析较慢,现在很多项目任然有广泛的应用。
2、几种解析和生成xml的方式:这里,为了最后我们比较几种xml的解析以及生成速度,我们使用一个同一的xml进行生成以及解析:
<?xml version="1.0" encoding="utf-8" standalone="yes"?> <total> <comm> <version>版本</version> <flag>入院标志</flag> </comm> <detail> <abc123>医院编码</abc123> <def456>住院流水号</def456> <jklmnopq> <xyz> <rst789>待遇类别</rst789> <uvw123>人员类别</uvw123> </xyz> </jklmnopq> </detail> </total>
这是一个简单自己加的医院用的部分数据,可以使用自己觉的好用的xml,这里生成全部,获取数据我们全部取待遇类别这一项。
a、xmlreader和xmlwriter类:
public static string createxml() { //indent属性设置或获取是否缩进的值 var xws = new xmlwritersettings() {indent=true,encoding = encoding.utf8 }; stringbuilder sb = new stringbuilder(); //调用xmlwriter的静态生成方法create,需要2个参数或者一个参数,一个作为输出 //另一个设置xml的格式 using (xmlwriter xw = xmlwriter.create(sb,xws)) { //这里括号里的属性用来设置standalone的值 xw.writestartdocument(true); xw.writestartelement("total"); xw.writestartelement("comm"); xw.writeelementstring("version", "版本"); xw.writeelementstring("flag", "入院标志"); xw.writeendelement(); xw.writestartelement("detail"); xw.writeelementstring("abc123", "医院编码"); xw.writeelementstring("def456", "住院流水号"); xw.writestartelement("jklmnopq"); xw.writestartelement("xyz"); xw.writeelementstring("rst789", "待遇类别"); xw.writeelementstring("uvw123", "人员类别"); xw.writeendelement(); xw.writeendelement(); xw.writeendelement(); xw.writeenddocument(); } //这里因为字符串的格式是utf-16,通过最简单的方式调用replace方法即可。如果是stringwriter,则使用派生类重写encoding方法即可。 return sb.tostring().replace("utf-16","utf-8"); }
这里我们先来简单解释一下standalone的意思,这个表示你所写的xml是否是独立的,如果为yes则表示它是独立的不能引用任何的dtd文件,
如果是no则表示它不是独立的,可以用外部的dtd文件,dtd文件:document type definition文档类型定义,它是一套语法规则,用来检验你所写的
标准通用标记语言或者可扩展标记语言是否符合规则。
这里可以看到这个创建还是很简单的,writestartdocument创建文档,writeenddocument结束文档,writestartelement创建标签,writeendelement
创建结束标签,如果有具体值的使用writeelementstring(),接受2个参数:标签名和具体的值,同样的如果此标签有属性,使用writeattributestring()方法
即可。上面是书写xml的过程,现在开始获取xml的值:
public static string getxmlinnervalue() { string result = string.empty; //仍然调用xmlreader的create静态方法生成 using (xmlreader reader = xmlreader.create("hospital.xml")) { while (reader.read()) { try { //read()方法会连xml的空白一起读,所以要加上xmlnodetype.element来读元素 if (reader.movetocontent() == xmlnodetype.element && reader.name == "rst789") { //这里微软建议尽量使用readelementcontentasstring()方法,速度更快 //因为某些元素会比较复杂,所以使用readasstring方法要捕获异常,在异常中继续调用read()方法就行了 result = reader.readelementcontentasstring(); } else { reader.read(); } } catch (exception ex) { reader.read(); } } } return result; }
这里也比较简单,通过调用read()方法,在通过name属性值,就可以确定我们要找的值。如果是要找属性值,调用
reader的getattribute()方法即可,参数是属性的名称。
b、xmlserializer类,通过序列化的手段操作xml,当然,用了序列化,那么相对的时间也会多一点,后面来证明,先建对象:
public class xyz { [xmlelement("rst789")] public string treatment { get; set; } [xmlelement("uvw123")] public string personnel{ get; set; } }
public class jklmnopq { [xmlelement] public xyz xyz { get; set; } }
public class detail { [xmlelement("abc123")] public string hospitalid { get; set; } [xmlelement("def456")] public string residentid { get; set; } [xmlelement] public jklmnopq jklmnopq { get; set; } }
public class comm { [xmlelement] public string version { get; set; } [xmlelement] public string flag { get; set; } }
[xmlroot] public class total { [xmlelement] public comm comm { get; set; } [xmlelement] public detail detail { get; set; } }
这里我在项目里新建了个model文件夹,然后添加了这5个类,可以看到这些类或者属性上面都有特性,特性是个常用也比较好用的东西,
可以暂时理解为注释这样一种东西对它所描述的东西的一个注解,后面详细会说,xmlroot代表是xml中的根元素,xmlelement代表元素,
xmlattribute代表属性,xmlignore则表示不序列化,后面如果有参数,则参数值表示在xml中真实的值,比如类xyz中,treatment待遇类别,
但是xml中当然不能用treatment显示,要用rst789显示,放在特性中就可以了,现在生成xml:
public static total gettotal() => new total { comm = new comm { version = "版本", flag = "入院标志" }, detail = new detail { hospitalid = "医院编码", residentid = "住院流水号", jklmnopq = new jklmnopq { xyz = new xyz { treatment = "待遇类别", personnel = "人员类别" } } } }; public static string createxml() { string xml = string.empty; using (stringwriter sw = new stringwriter()) { xmlserializernamespaces xsn = new xmlserializernamespaces(); xsn.add(string.empty,string.empty); xmlserializer serializer = new xmlserializer(typeof(total)); serializer.serialize(sw,gettotal(),xsn); xml = sw.tostring().replace("utf-16","utf-8"); } return xml; }
先说明下第一个方法,这里用了c#6.0的新特性,如果表达式体方法只有一个,可以通过lambda语法来书写。
第二个方法里面直接调用xmlserializer的serialize方法进行序列化就可以了,那么反序列化用deserialize方法就可以了,这里的serialize方法
有多钟参数,笔者可根据需要做相应调整,这里xml声明中的standalone不能做对应的书写,查了半天没有查到,如果再用xmlwriter或者dom
做的话就用到了那这种方法的结合,如果有更好的方法可以留言,感激感激。
public static string getxmlinnervalue() { total total = null; using (filestream fs = new filestream("hospital.xml",filemode.open)) { xmlserializer serializer = new xmlserializer(typeof(total)); total = serializer.deserialize(fs) as total; } return total.detail.jklmnopq.xyz.treatment; }
这里我尽量不适用xmlreader之类的转换xml方法,反序列化将xml转换为类,然后通过属性取到所需要的值。
c、dom:现在说明很常见的一种解析xml方式dom ,document obejct model文档对象模型。说明一下dom的工作方式,先将xml文档装入到内存,
然后根据xml中的元素属性创建一个树形结构,也就是文档对象模型,将文档对象化。那么优势显而易见,我么可以直接更新内存中的树形结构,因此
对于xml的插入修改删除效率更高。先看xml的书写:
public static string createxml() { xmldocument doc = new xmldocument(); xmlelement total = doc.createelement("total"); xmlelement comm = doc.createelement("comm"); total.appendchild(comm); xmlelement version = doc.createelement("version"); version.innerxml = "版本"; comm.appendchild(version); xmlelement flag = doc.createelement("flag"); flag.innerxml = "入院标志"; comm.appendchild(flag); xmlelement detail = doc.createelement("detail"); total.appendchild(detail); xmlelement abc123 = doc.createelement("abc123"); abc123.innerxml = "医院编码"; detail.appendchild(abc123); xmlelement def456 = doc.createelement("def456"); def456.innerxml = "住院流水号"; detail.appendchild(def456); xmlelement jklmnopq = doc.createelement("jklmnopq"); detail.appendchild(jklmnopq); xmlelement xyz = doc.createelement("xyz"); jklmnopq.appendchild(xyz); xmlelement rst789 = doc.createelement("rst789"); rst789.innerxml = "待遇类别"; xmlelement uvw123 = doc.createelement("uvw123"); uvw123.innerxml = "人员类别"; xyz.appendchild(rst789); xyz.appendchild(uvw123); xmldeclaration declaration = doc.createxmldeclaration("1.0","utf-8","yes"); doc.appendchild(declaration); doc.appendchild(total); //xmldocument的innerxml属性只有元素内的内容,outerxml则包含整个元素 return doc.outerxml; }
这里的创建xml还是比较简单的,document create一个xmlelement,append一下,最后全部append到document上就可以了。
说明一下innerxml和innertext,这个学过html的人应该都懂,innertext和innerhtml区别一样,如果是纯字符,则两者通用,如果是
带有标签这种的<>,这样innertext则会将其翻译为<和>这样,而innerxml则不会翻译。另外如果是获取xml的内容,innertext
会获取所有的值,而innerxml则会将节点一起返回,现在获取xml的值:
public static string getxmlinnervalue() { string result = string.empty; xmldocument doc = new xmldocument(); doc.load("hospital.xml"); xmlnode xn = doc.selectsinglenode("/total/detail/jklmnopq/xyz/rst789"); result = xn.innerxml; return result; }
这里经常用的就是xmlnode类,所有节点的抽象基类,提供了对节点的操作方法和属性,xmlnodelist类xmlnode的一组,如果有重复的节点
则使用xmlnodelist通过doc的getelementsbytagname()获取,然后通过index获取需要的具体节点。还有这里selectsinglenode()方法的参数
是这个节点的具体位置,而非节点名称,不然会报nullreferenceexception。再说一下常用的属性,有haschildnodes是否有子节点,childnodes
所有子节点,firstchild第一个子节点,parentchild父节点,nextsibling下一个兄弟节点,previoussibling 上一个兄弟节点。
d、使用linq解析:简单说下linq是什么,语言集成查询c#里面linq提供了不同数据的抽象层,所以可以使用相同的语法访问不同的数据源,简单来说
就是可以查询出一系列的数据不仅仅是从数据库查询,后面的篇章会进行详细说明。
public static string createxml() { string xml = string.empty; xdocument doc = new xdocument( new xdeclaration("1.0","utf-8","yes"), new xelement("total", new xelement("comm", new xelement("version","版本"), new xelement("flag","入院标志")), new xelement("detail", new xelement("abc123","医院编码"), new xelement("def456","住院流水号"), new xelement("jklmnopq", new xelement("xyz", new xelement("rst789","待遇类别"), new xelement("uvw123","人员类别")))))); stringbuilder sb = new stringbuilder(); using (xmlwriter xw = xmlwriter.create(sb)) { doc.writeto(xw); } xml = sb.tostring().replace("utf-16","utf-8"); return xml; }
这里使用了xdocument这个类创建xml也是很简单的,主要注意一下括号别多别少就行了,同样需要编码转换不然仍然出来的是utf-16。
public static string getxmlinnervalue() { string result = string.empty; xdocument doc = xdocument.load("hospital.xml"); var query = from r in doc.descendants("rst789") select r; foreach (var item in query) { result = item.value; } return result; }
这里linq同样也是使用以操作内存的方式操作xml,和dom的方式比较类似,也是比较快速的方法。
e、xpathnavigator类:system.xml.xpath下的一个抽象类,它根据实现ixpathnavigable的接口的类创建,如xpathdocument,xpathnavigator。由xpathdocument创建的xpathnavigator是只读对象,对于.net framework由xmldocument创建的xpathnavigator可以进行修改,它的canedit属性为true,必须要在4.6版本以上,可以使用#if进行判断。对于.net core没有提供createnavigator这个方法所以它始终是可读的。这个类可以理解为一个光标搜索,一个navigator对数据进行导航,它不是一个流模型,对于xml只分析和读取一次。
public static string getxmlinnervalue() { string str = string.empty; xpathdocument doc = new xpathdocument("hospital.xml"); xpathnavigator navigator = doc.createnavigator(); xpathnodeiterator iterator = navigator.select("/total/detail/jklmnopq/xyz/rst789"); while (iterator.movenext()) { if (iterator.current.name == "rst789") { str = iterator.current.value; } } return str; }
public static string getvalue() { string result = string.empty; xpathdocument doc = new xpathdocument("hospital.xml"); xpathnavigator navigator = doc.createnavigator(); xpathnodeiterator iterator = navigator.select("/total"); while (iterator.movenext()) { xpathnodeiterator iteratornew = iterator.current.selectdescendants(xpathnodetype.element, false); while (iteratornew.movenext()) { if (iteratornew.current.name== "rst789") { result = iteratornew.current.value; } } } return result; }
如果没有重复节点的情况下直接用第一种写好路径就行了,如果有重复节点就要用第二种进行多次循环了。现在我们用stopwatch进行时间监测。
static void main(string[] args) { console.writeline("生成xml的时间:"); stopwatch sw = new stopwatch(); sw.start(); xmlreaderandxmlwriter.createxml(); sw.stop(); console.writeline($"xmlreaderandxmlwriter生成时间:{sw.elapsed}"); sw.reset(); sw.restart(); xmlserializersample.createxml(); sw.stop(); console.writeline($"xmlserializersample生成时间:{sw.elapsed}"); sw.reset(); sw.restart(); domsample.createxml(); sw.stop(); console.writeline($"domsample生成时间:{sw.elapsed}"); sw.reset(); sw.restart(); linqsample.createxml(); sw.stop(); console.writeline($"linqsample生成时间:{sw.elapsed}"); sw.reset(); console.writeline("====================================================="); console.writeline("查询xml的时间:"); sw.restart(); xmlreaderandxmlwriter.getxmlinnervalue(); sw.stop(); console.writeline($"xmlreaderandxmlwriter查询时间:{sw.elapsed}"); sw.reset(); sw.restart(); xmlserializersample.getxmlinnervalue(); sw.stop(); console.writeline($"xmlserializersample查询时间:{sw.elapsed}"); sw.reset(); sw.restart(); domsample.getxmlinnervalue(); sw.stop(); console.writeline($"domsample查询时间:{sw.elapsed}"); sw.reset(); sw.restart(); linqsample.getxmlinnervalue(); sw.stop(); console.writeline($"linqsample查询时间:{sw.elapsed}"); sw.reset(); sw.restart(); xpathnavigatorsample.getxmlinnervalue(); sw.stop(); console.writeline($"xpathnavigatorsample查询时间:{sw.elapsed}"); }
这里我们可以看到无论是生成还是查询都是dom的操作更为快速一点,序列化相比就慢很多了,另外比较新的技术xpathnavigator也很快了,
再综合代码的精简程度,选择合适的方法进行传输数据就可以了。现在开始总结json的2种常用用法。
二、json:
1、简介:javascript object notation是一种轻量级的数据交互格式,采用完全独立于语言的格式传输和存储数据,语法简介,相较于xml解析速度更快。
2、介绍2种常用的解析方式。
先设定一个json做比较对象:
{ "code": "001", "feedbackmsg": "输出结算错误原因", "messages": [ { "uuid": "550e8400-e29b-41d4-a716-446655440000", "ruleid": "a0001", "triggerlevel": "1", "involvedcost": 6, "content": "病人住院his金额与社保金额不相等", "comments": "", "feedbackmsg": "his结算金额应与社保金额相等", "encounterresults": [ { "encounterid": "1", "orderids": [] } ] }, { "uuid": "550e8400-e19b-41d4-a716-446655440000", "ruleid": "b0001", "triggerlevel": "2", "involvedcost": 9, "content": "患者处方中存在配伍禁忌的中药【乌头】、【贝母】。", "comments": "", "feedbackmsg": "该病人这两种药品非同时使用", "encounterresults": [ { "encounterid": "1", "orderids": [ "cf01", "cf02" ] } ] } ] }
然后用2种方式分别生成:
第一种:
public static string createjson1() { string json = string.empty; dictionary<string, object> data = new dictionary<string, object> { {"code" ,"001" }, {"feedbackmsg" ,"输出结算错误原因" }, {"messages" ,new list<object>{ new dictionary<string,object> { {"uuid" ,"550e8400-e29b-41d4-a716-446655440000"}, {"ruleid" ,"a0001"}, {"triggerlevel" ,"1"}, {"involvedcost" ,6}, {"content" ,"病人住院his金额与社保金额不相等"}, {"comments","" }, {"feedbackmsg" ,"his结算金额应与社保金额相等"}, {"encounterresults" ,new list<object> { new dictionary<string,object>{ {"encounterid","1" }, {"orderids" ,new list<object> { } } } } } }, new dictionary<string, object> { {"uuid" ,"550e8400-e19b-41d4-a716-446655440000"}, {"ruleid" ,"b0001"}, {"triggerlevel" ,"2"}, {"involvedcost" ,9}, {"content" ,"患者处方中存在配伍禁忌的中药【乌头】、【贝母】。"}, {"comments" ,""}, {"feedbackmsg" ,"该病人这两种药品非同时使用"}, {"encounterresults" ,new list<object> { new dictionary<string,object> { {"encounterid","1" }, {"orderids" ,new list<object> { "cf01","cf02" } } } } } } } } }; json = jsonconvert.serializeobject(data,formatting.indented); return json; }
第二种:
public static string createjson2() { var root = new jobject { {"code" ,"001"} ,{ "feedbackmsg", "输出结算错误原因" } ,{"messages" , new jarray { new jobject { { "uuid", "550e8400-e29b-41d4-a716-446655440000" }, {"ruleid" ,"a0001" } ,{"triggerlevel" ,"1"} ,{"involvedcost" ,6} ,{"content" ,"病人住院his金额与社保金额不相等"} , {"comments" ,""} ,{"feedbackmsg" ,"his结算金额应与社保金额相等"} ,{"encounterresults" ,new jarray{ new jobject { { "encounterid" ,"1"} ,{ "orderids" ,new jarray { } } } } } } , new jobject{ {"uuid" , "550e8400-e19b-41d4-a716-446655440000" } ,{ "ruleid" ,"b0001"} ,{ "triggerlevel", "2"} , {"involvedcost" ,9 } ,{"content" ,"患者处方中存在配伍禁忌的中药【乌头】、【贝母】。" } ,{"comments" ,""} , {"feedbackmsg" ,"该病人这两种药品非同时使用"} ,{"encounterresults" ,new jarray{ new jobject{ { "encounterid", "1"} , {"orderids" ,new jarray{"cf01" ,"cf02"} } } } } } } } }; return root.tostring(); }
这里这2种方法都引用了这个第三方文件,可以在nuget包中直接找到,然后添加引用即可,第一种采用序列化
dictionary,第二种用jobject和jarray生成,都很简单,而且可以处理很复杂的json字符串,比较麻烦的就是写这个的时候必须非常认真,稍一溜号
可能大括号就再也对不上了。。。这里讲2种比较通用的方法就可以了,当然还可以直接把json用字符串直接拼起来,本来还想说一下比较近的litjson,
但是这个第三方程序有个不足,它的内部定义编码是unicode,对于中文会进行转换,可以从git上下载源码然后改动以后就对中文没有影响了,也可以
使用正则表达式的unescape方法转换。
public static string createjson() { jsonwriter writer = new jsonwriter(); writer.writeobjectstart(); writer.writepropertyname("code"); writer.write("001"); writer.writepropertyname("feedbackmsg"); writer.write("输出结算错误原因"); writer.writepropertyname("messages"); writer.writearraystart(); writer.writeobjectstart(); writer.writepropertyname("uuid"); writer.write("550e8400-e29b-41d4-a716-446655440000"); writer.writepropertyname("ruleid"); writer.write("a0001"); writer.writepropertyname("triggerlevel"); writer.write("1"); writer.writepropertyname("involvedcost"); writer.write(6); writer.writepropertyname("content"); writer.write("病人住院his金额与社保金额不相等"); writer.writepropertyname("feedbackmsg"); writer.write("该病人这两种药品非同时使用"); writer.writepropertyname("comments"); writer.write(""); writer.writearraystart(); writer.writeobjectstart(); writer.writepropertyname("encounterid"); writer.write("1"); writer.writepropertyname("orderids"); writer.writearraystart(); writer.writearrayend(); writer.writeobjectend(); writer.writearrayend(); writer.writeobjectend(); writer.writearrayend(); writer.writeobjectend(); return system.text.regularexpressions.regex.unescape(writer.tostring()); }
这里看到中文编码合适,现在开始解析json串中的值。
这里我们取orderid中的第二个值,第一种用反序列化的方法:先构造实体类。
public class rootobject { public string code { get; set; } public string feedbackmsg { get; set; } public list<messages> messages { get; set; } } public class messages { public string uuid { get; set; } public string ruleid { get; set; } public string triggerlevel { get; set; } public int involvedcost { get; set; } public string content { get; set; } public string comments { get; set; } public string feedbackmsg { get; set; } public list<encounterresults> encounterresults { get; set; } } public class encounterresults { public string encounterid { get; set; } public list<string> orderids { get; set; } }
public static string getjsoninnervalue2(string json) { var newtype = jsonconvert.deserializeobject<rootobject>(json); return newtype.messages[1].encounterresults[0].orderids[1]; }
这里只要盯好每个元素是什么类型的就可以了,代码还是很简单的。第二种方法我们选择使用litjson,这里只取值所以不需要考虑一下
中文乱码的情况,并且使用litjson不需要构造实体类,非常方便。
public static string getjsoninnervalue3(string json) { jsondata data = jsonmapper.toobject(json); return data["feedbackmsg"].tostring(); } public static string getjsoninnervalue4(string json) { jsondata data = jsonmapper.toobject(json); return data["messages"][1]["encounterresults"][0]["orderids"][1].tostring(); }
当然这个东西还是需要先在nuget包中下载引用,很简单方便。
三、xml和json之间的相互转换:
1、先看xml转换为json,仍然选取前面的xml进行转换:
public static string xmltojsontest() { string json = string.empty; xmldocument doc = new xmldocument(); doc.load("hospital.xml"); xmlnode node = doc.selectsinglenode("/total"); json = jsonconvert.serializexmlnode(node); return json; }
因为xml里面还有xmldeclaration,所以通过jsonconvert的serializexmlnode通过节点的方式转换,如果想去掉根节点,调用
serializerxmlnode的重载方法即可。
2,现在用json转换为xml:
public static string jsontoxmltest() { string xml = string.empty; string json = "{\"code\":\"001\",\"feedbackmsg\":\"输出结算错误原因\",\"messages\":[{\"uuid\":\"550e8400-e29b-41d4-a716-446655440000\",\"ruleid\":\"a0001\",\"triggerlevel\":\"1\",\"involvedcost\":6,\"content\":\"病人住院his金额与社保金额不相等\",\"comments\":\"\",\"feedbackmsg\":\"his结算金额应与社保金额相等\",\"encounterresults\":[{\"encounterid\":\"1\",\"orderids\":[]}]},{\"uuid\":\"550e8400-e19b-41d4-a716-446655440000\",\"ruleid\":\"b0001\",\"triggerlevel\":\"2\",\"involvedcost\":9,\"content\":\"患者处方中存在配伍禁忌的中药【乌头】、【贝母】。\",\"comments\":\"\",\"feedbackmsg\":\"该病人这两种药品非同时使用\",\"encounterresults\":[{\"encounterid\":\"1\",\"orderids\":[\"cf01\",\"cf02\"]}]}]}"; xmldocument doc = jsonconvert.deserializexmlnode(json,"root"); xml = doc.outerxml; return xml; }
这里因为json中没有根节点,所以使用deserializexmlnode的重载方法,加一个root根节点即可。
这样最后xml和json之间的相互转换也完成了,数据传输的总结到此结束了,还有不明之处,请多多指教。
2019-03-01 12:03:56
推荐阅读