Gradle-Groovy 对 XML,JSON 以及文件的操作
# Groovy 文件
- 对 JSON 的操作;
- 对 XML 的操作;
- 对普通文件的操作;
- 总结;
## 对 JSON 的操作
JSON 的操作在 Android 开发中是非常之常用的,客户端请求服务端接口返回的数据类型一般就是 JSON 格式的数据。
下面看看 Groovy 对 JSON 有什么好的扩展。
API | 功能 |
---|---|
JsonSlurper | JSON转化为对象 |
JsonOutput | 对象转化为JSON |
### JSON转化为对象
在 Groovy 中提供 JsonSlurper(JsonParser的API是一样的) 将字符串解析为对象。
- 定义一个 JsonSlurper 对象
def jsonSlurper = new JsonSlurper()
- 解析 json 字符串
def object = jsonSlurper.parseText('''
{
"name":"六号表哥",
"age":26,
"level":null,
"isMale":true
}
''')
- 访问解析后的值
println object.getClass()//class org.apache.groovy.json.internal.LazyMap
println object.name//六号表哥"
println object.age//26
上面的例子中,我们做了一下几件事
- 创建一个 JsonSlurper 对象
- 使用 JsonSlurper 对象来解析 json 字符串
- 通过 key 来访问解析后的值
在 Groovy 中可以解析的数据类型分别有:
- 基本数据类型
- 字符串类型
- map 类型
- list 类型
- null
具体什么意思呢?也就是说,json 的 value 可以是以上这么多种数据类型。
注意:这里有一个很蛋疼的问题,我们在使用 Gson 进行 json 转化为 bean 时,如果 json 字符串中多了一个 bean 没有定义的字段,那么 gson 在解析时就会忽略这个字段,而 JsonSluper 并不会忽略,它会在解析就直接抛出异常,我目前还不知道怎么解决这个问题,如果在解析时,连这个功能都没有,那么我也不会去使用 JsonSlurper 。
下面来演示一下这个问题:
- 定义一个 Person 类,只有两个属性,分别为 name 和 age。
class Person implements Serializable{
String name
int age
String toString() {
"${name} is $age year old"
}
}
- 使用 JsonSlurper 将其转化为 Person 对象
在这里因为没有
isMale
这个属性,因此在运行时就抛出异常了。
Person person = jsonSlurper.parseText('''
{
"name":"六号表哥",
"age":26,
"isMale":true
}
''')
//异常:
org.codehaus.groovy.runtime.metaclass.MissingPropertyExceptionNoStack:
No such property: isMale for class: Person
也许新的语言特性不再需要像 Java 一样定义实体类,通过 JsonSlurper 解析完成之后就是一个 LazyMap 对象,直接调用属性获取数据即可, JS 语言也是这样的。
### 对象转化为JSON
在 Groovy 中提供 JsonOutput 将对象解析为 json 字符串。
Person person = new Person("六号表哥", age: 26)
def json = JsonOutput.toJson(person)
//JsonOutput.prettyPrint 输出带有 json 格式
println JsonOutput.prettyPrint(json)
## 对 XML 的操作
Groovy 支持解析 XML 和生成 XML 的功能。
API | 功能 |
---|---|
groovy.util.XmlParser | 解析 xml |
groovy.util.XmlSlurper | 解析 xml |
groovy.xml.MarkupBuilder | 生成 xml |
### XmlSlurper 解析 XML
- 定一个 xml 字符串
String books = '''
<response version-api="2.0">
<value>
<books>
<book available="20" id="1">
<title>Don Xijote</title>
<author id="1">Manuel De Cervantes</author>
</book>
<book available="14" id="2">
<title>Catcher in the Rye</title>
<author id="2">JD Salinger</author>
</book>
<book available="13" id="3">
<title>Alice in Wonderland</title>
<author id="3">Lewis Carroll</author>
</book>
<book available="5" id="4">
<title>Don Xijote</title>
<author id="4">Manuel De Cervantes</author>
</book>
</books>
</value>
</response>
'''
- XmlSlurper 对象的创建
XmlSlurper xmlSlurper = new XmlSlurper()
- 解析 xml 字符串
def response = xmlSlurper.parseText(books);
### 获取标签内容和属性
#### 获取标签的内容
text()
assert response.value.books.book[0].title.text() == 'Don Xijote'
#### 获取标签的属性
获取标签的属性有以下三种方式:
- a["@href"]
println response.value.books.book[0].author["@id"]
- a.'@href'
println response.value.books.book[0].author."@id"
- a.@href
println response.value.books.book[0].author.@id
### 遍历 XML
- 广度遍历 book 下的 title 子标签的内容
response.value.books.book.each {
book ->
println book.title.text()
}
- 深度遍历 book 下的 title 子标签的内容
//深度遍历
response.depthFirst().findAll {
node ->
return (node.name().equals("book"))
}.collect {
node -> println node.title.text()
}
### 生成 XML
在 Groovy 提供了MarkupBuilder
来动态生成 XML 内容。
以下通过一个实际的例子来生成一段 xml 内容

- 定义 Writer
这里定义一个 StringWriter ,它是 Writer 的子类,MarkupBuilder 生成 xml 会写入到 StringWriter 中。
def sw = new StringWriter();
- 闯将 MarkupBuilder
MarkupBuilder builder = new MarkupBuilder(sw);
- 开始写 xml 内容
注意:uses-permission
像这种标签名字就需要使用'uses-permission'
来表示,不然编译是失败的,因为有-
特殊符号,如果就只有一个单词,那么就需要用 ''
来表示。
builder.manifest(package: "com.example.app", xmlns: 'android="http://schemas.android.com/apk/res/android"') {
'uses-permission'('android:name': '"android.permission.INTERNET"')
application('android:name': "com.example.AppApplication") {
activity('android:name': '"com.example.app.activity.SplashActivity"') {
'intent-filter' {
action('android:name': '"android.intent.action.MAIN"')
category('android:name': '"android.intent.category.LAUNCHER"')
}
}
}
}
## 普通文件
- 在 Groovy 中对文件的操作跟 Java 是完全兼容的,并且 Groovy 也提供更加简洁的操作方式。
- 对于文件的操作不外乎就是对文件的读和写。
### 定义一个文件对象
在 Groovy 中定义文件的方式跟 Java 是一致的。下面指定的文件是工程下某一个文件为例。
def file = new File("../GroovyWorkspace.iml")

### 读取文件
Groovy 提供了快速读取文件内容的 api
,分别如下所示:
- getText() 一次性读取所有的内容到一个字符串中
- readLines() 一次性读取所有的内容到集合中
- eachLine() 逐行读取内容
一次性读取文件到内存中,
getText()
返回一个字符串,该字符串就表示文件的内容。
/**
* Read the content of the File and returns it as a String.
*
* @param file the file whose content we want to read
* @return a String containing the content of the file
* @throws IOException if an IOException occurs.
* @since 1.0
*/
public static String getText(File file) throws IOException {
return IOGroovyMethods.getText(newReader(file));
}
实际调用很简单:
def result = file.getText()
println result
readLines()
返回一个集合,该集合的元素对应于文件中每一个行内容。
/**
* Reads the file into a list of Strings, with one item for each line.
*
* @param file a File
* @return a List of lines
* @throws IOException if an IOException occurs.
* @see IOGroovyMethods#readLines(java.io.Reader)
* @since 1.0
*/
public static List<String> readLines(File file) throws IOException {
return IOGroovyMethods.readLines(newReader(file));
}
实际调用很简单:
//先获取集合,然后遍历集合内容
file.readLines().each{
line->
println line
}
eachLine
方法进行逐行获取,传入的闭包的参数1表示遍历当前行的内容,参数2是可选参数,表示当前行的行号。
/**
* Iterates through this file line by line. Each line is passed to the
* given 1 or 2 arg closure. The file is read using a reader which
* is closed before this method returns.
*
* @param self a File
* @param closure a closure (arg 1 is line, optional arg 2 is line number starting at line 1)
* @return the last value returned by the closure
* @throws IOException if an IOException occurs.
* @see #eachLine(java.io.File, int, groovy.lang.Closure)
* @since 1.5.5
*/
public static <T> T eachLine(File self, @ClosureParams(value=FromString.class,options={"String","String,Integer"}) Closure<T> closure) throws IOException {
return eachLine(self, 1, closure);
}
实际调用很简单:
//一个参数的闭包,表示当前行的内容
file.eachLine {
line->
println line
}
//两个参数的闭包,参数1表示当前行的内容,参数2表示当前行的行号
file.eachLine {
line,lineNum->
println "lineNum:${lineNum} : ${line}"
}
在 Groovy
中可以通过 file.withReader
的方式快速的拿到 file
对应的 Reader
对象,然后进行读操作。这个操作就只有一行代码,我们来对比一下在 java
中,获取一个 file
对应的 Reader
对象的获取。
- groovy 版本
file.withReader {
//这个 reader 就是 Java 中的 LineNumberReader 对象
reader ->
reader.readLines().each {
line ->
println line
}
}
- Java 版
File javaFile = new File("../GroovyWorkspace.iml")
LineNumberReader lineNumberReader = new LineNumberReader(new FileReader(javaFile))
从上面的操作可以看出, Groovy 是一行代码就可以实现跟 Java 一样的功能,并且通过 withReader 的方式,Groovy 还帮你对流进行 close 操作,这在 java 是需要手动调用的,因此 Groovy 还是很方便的。下面的代码就是截取 withReader 的源码,在注释中已经说的很清楚,使用该方法能保证流被关闭。
/**
* Create a new BufferedReader for this file and then
* passes it into the closure, ensuring the reader is closed after the
* closure returns.
*
* @param file a file object
* @param closure a closure
* @return the value returned by the closure
* @throws IOException if an IOException occurs.
* @since 1.5.2
*/
public static <T> T withReader(File file, @ClosureParams(value=SimpleType.class, options="java.io.BufferedReader") Closure<T> closure) throws IOException {
return IOGroovyMethods.withReader(newReader(file), closure);
}
### 写入文件
- writeText(String) 写入一个字符串到文件中
- withWriter(closure)获取一个 Writer
- withWriterAppend(closure)获取一个 Writer,与 withWriter 不同之处,它会以追加的方式将内容添加到文件中
def writeFile = new File("Test.txt");
if(!writeFile.exists()){
writeFile.createNewFile()
}
//一句话就可以实现写入操作,不需要关心流,内部会自动关闭
writeFile.write("hello Groovy\n");
//会覆盖原有的内容
writeFile.withWriter {
writer->
writer.write("hello Java\n")
}
//以追加的方式将内容添加到文件中
writeFile.withWriterAppend { writer->
writer.write("hello Gradle\n")
}
### 实战:文件拷贝
上面列举了文件的读和写的方式,下面结合读写相关 api 来实现一个文件拷贝的功能。
/**
* src 表示源文件路径
* dest 表示目标文件路径
*/
boolean copy(String src, String dest) {
try{
//创建一个源文件对应的 File 对象
File srcFile = new File(src)
//源文件不存在
if(!srcFile.exists()){
return false
}
//创建一个目标文件对应的 File 对象
File destFile = new File(dest)
//检测目标文件是否存在
if (!destFile.exists()) {
destFile.createNewFile()
}
//获取源文件的内容
String srcText = srcFile.getText()
//获取目标文件对应的 Writer 对象,将 srcText 写入。
destFile.withWriter {
writer ->
writer.write(srcText)
}
return true;
}catch(Exception e){
return false
}
}
### 实战:对象的序列化与反序化
我们在实际开发中,经常要将一个对象序列化到本地,在 Java 中一般使用ObjectOutputStream
来实现。接下来看一下 Groovy 怎么实现这功能的。
Person person = new Person(name: "六号表哥", age: 26)
File objFile = new File("../person")
if (!objFile.exists()) {
objFile.createNewFile()
}
//写入操作
//通过 withObjectOutputStream 就可以快速的获取一个 ObjectOutputStream 实例对象
objFile.withObjectOutputStream {
out->
out.writeObject(person)
}
//读取
objFile.withObjectInputStream {
inputStream ->
Person p = inputStream.readObject()
println "name:${p.name},age:${p.age}"//name:六号表哥,age:26
}
## 总结
以上总结的 Groovy 中 json ,xml 以及文件的相关操作,本文只是简单的总结了其比较常用的 api 示例,更多内容,你可以通过查阅官网来学习更多关于 groovy 中关于这三者的语法。本文中肯定有很多不足之处,有待慢慢完善,谢谢阅读。
多思考,多总结,多实践。
「记录于2018-07-08晚」