Groovy 对 XML,JSON 以及文件的操作

# Groovy 文件

  • 对 JSON 的操作;
  • 对 XML 的操作;
  • 对普通文件的操作;
  • ## 对 JSON 的操作

    JSON 的操作在 Android 开发中是非常之常用的,客户端请求服务端接口返回的数据类型一般就是 JSON 格式的数据。

    下面看看 Groovy 对 JSON 有什么好的扩展。

    println object.getClass()//class org.apache.groovy.json.internal.LazyMap
    println object.name//六号表哥"
    println object.age//26
    

    上面的例子中,我们做了一下几件事

  • 创建一个 JsonSlurper 对象
  • 使用 JsonSlurper 对象来解析 json 字符串
  • 通过 key 来访问解析后的值
  • 在 Groovy 中可以解析的数据类型分别有:

  • 基本数据类型
  • 字符串类型
  • map 类型
  • list 类型
  • 具体什么意思呢?也就是说,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 的功能。

    <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 内容

    这里定义一个 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")
     * 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) { //创建一个源文件对应的 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.writeObject(person)
    objFile.withObjectInputStream {
        inputStream ->
            Person p = inputStream.readObject()
            println "name:${p.name},age:${p.age}"//name:六号表哥,age:26