Java & Groovy & Scala & Kotlin - 26.Xml 与 Json 的处理

Overview

JSON 是一种键值对形式的轻量级的数据交换格式,除了大量用于 Restful 请求外,其二进制形式的 BSON 也被用于作为 NO SQL 的数据存储格式。相比较 XML 而言,JSON 更为简单,灵活。

XML 也可以用于数据交换,最为有名的就是 SOAP 协议。相比较 JSON 而言由于需要有开始和结束标记所以 XML 略显啰嗦。不过相比较 JSON 的无模式形式,XML 可以通过指定 DTD 而为其中定义的数据指定一定的格式,所以可以作为配置文件,在使用 IDE 进行编辑时也能拥有代码提示功能。

尽管现代编程语言都在一定程度上直接支持 JSON 和 XML 的创建和解析,但是很少有人会直接使用语言本身提供的 API 而不是第三方库。所以本章节只是简单的介绍一下基本用法。

Java 篇

JSON

Java 原来并不支持 JSON 的解析,不过在 Oracle 将 JavaFX 的 API 整合到 Java 中,原生的解析也就成了可能。当然更多人还是会选择 Jackson,GSON,Fastjson 等第三方库。

创建 JSON

使用 JavaFX 创建 JSON

JSON 对象

JSONDocument jsonDocument = JSONDocument.createObject();
JSONDocument results = JSONDocument.createObject();
results.setString("result", "x");
results.setString("result", "y");
jsonDocument.set("results", results);
System.out.println(jsonDocument.toJSON());

输出结果

{"results":{"result":"y"}}

JSON 数组

jsonDocument = JSONDocument.createArray();
JSONDocument jsonDocument1 = JSONDocument.createObject();
jsonDocument1.setString("404", "not found");
JSONDocument jsonDocument2 = JSONDocument.createObject();
jsonDocument2.setString("302", "redirect");
jsonDocument.array().add(jsonDocument1);
jsonDocument.array().add(jsonDocument2);
System.out.println(jsonDocument.toJSON());

输出结果

[{"404":"not found"},{"302":"redirect"}]

解析 JSON

String json = "{\"data\":[{\"404\":\"not found\"},{\"302\":\"redirect\"}]}";
JSONReader jsonReader = new JSONStreamReaderImpl(new StringReader(json));
JSONDocument jsonDocument = jsonReader.build();
JSONDocument data = jsonDocument.get("data");
System.out.println(data.get(0).object().keySet().iterator().next());    //  404
System.out.println(data.get(1).object().values().iterator().next());    //  redirect

由于 Java 中使用引号时必须加上转义符,所以 JSON 字符串看起来非常不直观。

XML

创建 XML

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
    String defaultNamespaceUri = "http://myDefaultNamespace";
    String otherNamespaceUri = "http://someOtherNamespace";

    DocumentBuilder builder = factory.newDocumentBuilder();
    Document doc = builder.newDocument();

    //  指定 XSL
    ProcessingInstruction xmlSheet = doc.createProcessingInstruction("xml-sheet", "type='text/xsl' href='myfile.xslt'");
    doc.appendChild(xmlSheet);

    Element langs = doc.createElement("langs");
    langs.setAttribute("type", "current");
    //  指定 Namespace
    langs.setAttribute("xmlns:app", otherNamespaceUri);
    langs.setAttribute("xmlns", defaultNamespaceUri);
    doc.appendChild(langs);

    Element language1 = doc.createElement("language");
    Text text1 = doc.createTextNode("Java");
    language1.appendChild(text1);
    langs.appendChild(language1);

    Element language2 = doc.createElement("language");
    Text text2 = doc.createTextNode("Groovy");
    language2.appendChild(text2);
    langs.appendChild(language2);

    //   指定 CDATA
    CDATASection cdataSection = doc.createCDATASection("<!-- Support Android -->");
    langs.appendChild(cdataSection);

    Element language3 = doc.createElement("language");
    Text text3 = doc.createTextNode("Scala");
    language3.appendChild(text3);
    langs.appendChild(language3);

    Element language4 = doc.createElement("language");
    Text text4 = doc.createTextNode("Kotlin");
    language4.appendChild(text4);
    langs.appendChild(language4);

    // 输出 XML
    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer transformer = tf.newTransformer();
    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
    transformer.setOutputProperty(OutputKeys.VERSION, "1.0");
    transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
    StringWriter writer = new StringWriter();
    StreamResult streamResult = new StreamResult(writer);
    DOMSource source = new DOMSource(doc);
    transformer.transform(source, streamResult);
    String xmlString = writer.toString();
    System.out.println(xmlString);
} catch (ParserConfigurationException | TransformerException e) {
    e.printStackTrace();
}

输出结果

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?xml-sheet type='text/xsl' href='myfile.xslt'?>
<langs xmlns="http://myDefaultNamespace" xmlns:app="http://someOtherNamespace" type="current">
    <language>Java</language>
    <language>Groovy</language><![CDATA[<!-- Support Android -->]]>
    <language>Scala</language>
    <language>Kotlin</language>
</langs>

解析 XML

XML 有多种解析方式,但是最基本的就两种 DOM 和 SAX。DOM 需要将文档都读到内存中,所以可以实现随机读取。SAX 则是基于事件一步一步进行解析,解比 DOM 的实现方式要复杂。以下例子为 DOM 解析。

String xml = "<langs type='current' count='4' mainstream='true'>\n" +
        "  <language flavor='static' version='1.8.0_25'>Java</language>\n" +
        "  <language flavor='dynamic' version='2.4.4'>Groovy</language>\n" +
        "  <language flavor='static' version='2.11.5'>Scala</language>\n" +
        "  <language flavor='static' version='0.12.613'>Kotlin</language>\n" +
        "</langs>";

byte[] xmlBytes = xml.getBytes();
InputStream is = new ByteArrayInputStream(xmlBytes);

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
    DocumentBuilder db = dbf.newDocumentBuilder();
    Document doc = db.parse(is);

    Element langs = doc.getDocumentElement();
    System.out.println("count = " + langs.getAttribute("count"));   //  count=4

    NodeList list = langs.getElementsByTagName("language");
    for (int i = 0; i < list.getLength(); i++) {
        Element language = (Element) list.item(i);
        System.out.println(language.getTextContent());
    }
} catch (ParserConfigurationException | SAXException | IOException e) {
    e.printStackTrace();
}

Groovy 篇

JSON

Groovy 中创建和解析 JSON 格式比起 Java 来说要简单不少。

创建 JSON

JSON 对象

def json = new JsonBuilder()
json.call {
    results {
        result("x")
        result("y")
    }
}
println(json.toPrettyString())

输出结果

{
    "results": {
        "result": "y"
    }
}

JSON 数组

def list = [
        [code: "404", value: "not found"],
        [code: "302", value: "redirect"]
]
def builder = new JsonBuilder(list)
println builder.toPrettyString()

输出结果

[
    {
        "code": "404",
        "value": "not found"
    },
    {
        "code": "302",
        "value": "redirect"
    }
]

包含 JSON 数组的 JSON 对象

def root = new JsonBuilder()
root {
    data(
            list.collect {
                [
                        code : it.code,
                        value: it.value
                ]
            }
    )
}
println root.toPrettyString()

输出结果

{
    "data": [
        {
            "code": "404",
            "value": "not found"
        },
        {
            "code": "302",
            "value": "redirect"
        }
    ]
}

解析 JSON

def json = """
{
    "data": [
        {
            "code": "404",
            "value": "not found"
        },
        {
            "code": "302",
            "value": "redirect"
        }
    ]
}
"""
def result = new JsonSlurper().parseText(json)
println result.data[0].code     //  404
println result.data[1].value    //  redirect

Groovy 中由于支持原样输出,所以无需使用转义符。

XML

创建 XML

基于 MarkupBuilder

def sw = new StringWriter()
def xml = new MarkupBuilder(sw)
xml.langs(type: "current") {
    language("Java")
    language("Groovy")
    language("Scala")
    language("Kotlin")
}
println(sw)

输出结果

<langs type='current'>
  <language>Java</language>
  <language>Groovy</language>
  <language>Scala</language>
  <language>Kotlin</language>
</langs>

基于 StreamingMarkupBuilder

相比较 MarkupBuilder 可以实现更多的功能

def comment = "<![CDATA[<!-- address is new to this release -->]]>"
def builder = new StreamingMarkupBuilder()
builder.encoding = "UTF-8"
xml = {
    mkp.xmlDeclaration()
    mkp.pi("xml-stylesheet": "type='text/xsl' href='myfile.xslt'")
    mkp.declareNamespace('': 'http://myDefaultNamespace')
    mkp.declareNamespace('app': 'http://someOtherNamespace')
    langs(type: "current") {
        language("Java", flavor: 'static', "app:version": '1.8.0_25')
        language("Groovy", flavor: 'dynamic', "app:version": '2.4.4')
        language("Scala", flavor: 'static', "app:version": '2.11.5')
        language("Kotlin", flavor: 'static', "app:version": '0.12.613')
    }
}
def writer = new StringWriter()
writer << builder.bind(xml)
println(writer)

解析 XML

Groovy 中主要使用 XmlSlurper 或者 XmlParser 进行 XML 解析,两种都属于 SAX 解析方式,API 基本一样。

使用 XmlParser

获得节点的属性时可以使用 attribute() 方法或者 XPATH 形式的 @属性名

def xml = """
<langs type='current' count='4' mainstream='true'>
<language flavor='static' version='1.8.0_25'>Java</language>
<language flavor='dynamic' version='2.4.4'>Groovy</language>
<language flavor='static' version='2.11.5'>Scala</language>
<language flavor='static' version='0.12.613'>Kotlin</language>
</langs>
"""
def langs = new XmlParser().parseText(xml)
println "count = ${langs.attribute("count")}" //  count=4
println "count = ${langs.@count}"
langs.language.each {
    println it.text()
}

XmlSlurper

def langs2 = new XmlSlurper().parseText(xml)
println "count = ${langs2.@count}" //  count=4
langs2.language.each {
    println it.text()
}
println langs2.language[1].@flavor   //  dynamic

XmlSlurper 与 XmlParser 非常相似,最大的区别就是前者是懒加载方式,只有使用时才会真正进行解析。有关两种方式应该如何选择可以参考 Stackoverflow 上的讨论

Scala 篇

JSON

Scala 原来的 JSON API 目前已经不推荐使用了,如果希望进行 JSON 解析,通常需要使用 Playframeworks 之类的第三方框架或库,所以这里就不介绍了。

XML

与其它三种语言不同,在 Scala 中 XML 是一等公民,这意味着可以直接定义 XML 而不是其它语言中的 XML 字符串,所以在 Scala 中使用 XML 非常简单。

创建 XML

XML 无需使用 "" 定义,在 XML 中可以使用 {} 来编写代码,就像在 JSP 中编写代码一样。

val langType = "current"
val langs =
  <langs type={langType}>
    <language>Java</language>
    <language>Groovy</language>{scala.xml.PCData("<!-- Support Android-->")}<language>Scala</language>
    <language>Kotlin</language>
  </langs>
val printer = new PrettyPrinter(80, 4)
println(printer.format(langs))
println(scala.xml.Utility.trim(langs))

输出结果

<langs type="current">
    <language>Java</language>
    <language>Groovy</language>
    <![CDATA[<!-- Support Android-->]]>
    <language>Scala</language>
    <language>Kotlin</language>
</langs>

以上 XML 可以通过使用 List 循环进一步简化

val langLst = List("Java", "Groovy", "Scala", "kotlin")
val xml =
  <langs type={langType}>
    {langLst.map(l =>
    <language>
      {l}
    </language>
  )}
  </langs>

如果 XML 很长也可以通过 NodeBuffer 先构建不同部分再组装到一起。其中, += 用于添加 XML 到当前 NodeBuffer,&+ 用于构建新的 NodeBuffer。

var nb = new NodeBuffer
nb += <language>Java</language>
nb += <language>Groovy</language>
nb = nb &+ <language>Scala</language> &+ <language>Kotlin</language>
val langs3 =
  <langs>
    {nb}
  </langs>
println(printer.format(xml))

在生成 XML 时也可以通过重写来修改原来的 XML 的内容

val rewrite = new RuleTransformer(new RewriteRule {
  override def transform(n: Node): Node =
    n match {
      case <language>{l}</language> =>
        <language>{l}!!!</language>
      case other => other
    }
}
)
val result = rewrite.transform(langs)
println(printer.format(result.head))

输出 XML 到文件的时候也可以指定 DocType

val doctype = DocType("html",
  PublicID("-//W3C//DTD XHTML 1.0 Strict//EN",
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"),
  Nil)
XML.save("files/langs.xml", langs,
  "utf-8", xmlDecl = true, doctype)

以上将在 XML 头部追加如下信息

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

解析 XML

Scala 解析 XML 方式类似 XPATH,可以使用 \ 获得某一节点的直接子节点,使用 \\ 获得子节点,使用 @ 获得属性值。

val langs =
  <langs type='current' count='4' mainstream='true'>
    <language flavor='static' version='1.8.0_25'>
      <title>Java</title>
    </language>
    <language flavor='dynamic' version='2.4.4'>
      <title>Groovy</title>
    </language>
    <language flavor='static' version='2.11.5'>
      <title>Scala</title>
    </language>
    <language flavor='static' version='0.12.613'>
      <title>Kotlin</title>
    </language>
  </langs>

val java = (langs \ "language")(0)
val javaVersion = java \ "@version"
val javaTitle = (langs \\ "title")(0)
(langs \ "language" \ "title").foreach(n =>
  println(n.text)
)
println(javaVersion)    //  1.8.0_25
println(javaTitle.text) //  Java

Kotlin 篇

JSON

Kotlin 目前没有什么通用的支持 JSON 的 API,需要自己定义 DSL 来解析。

XML

创建 XML

val xml = """
<langs type='current' count='4' mainstream='true'>
<language flavor='static' version='1.8.0_25'>Java</language>
<language flavor='dynamic' version='2.4.4'>Groovy</language>
<language flavor='static' version='2.11.5'>Scala</language>
<language flavor='static' version='0.12.613'>Kotlin</language>
</langs>
"""

val builder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
val doc = builder.parse(ByteArrayInputStream(xml.toByteArray()))
println(doc.toXmlString())

解析 XML

val langs = doc.getElementsByTagName("langs").item(0)
println(langs.getAttributes().getNamedItem("count").getNodeValue()) //  4

Summary

  • 四种语言都原生提供 XML Api,但是只有 Scala 视 XML 为一等公民。
  • 目前 Java 和 Groovy 都支持 JSON Api。
  • 使用 JSON 和 XML 更多时候还是依赖第三方库而非原生 API。

文章源码见 https://github.com/SidneyXu/JGSK 仓库的 _26_xml_json 小节

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,076评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,658评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,732评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,493评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,591评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,598评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,601评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,348评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,797评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,114评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,278评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,953评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,585评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,202评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,180评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,139评论 2 352

推荐阅读更多精彩内容