共通篇
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