JSON与XML处理

JSON与XML处理 #

共通篇 #

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

总结 #

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

项目源码见 JGSK/_26_xml_json

沪ICP备17055033号-2