文章

Android解析XML与JSON

一、解析XML

约定一个XML文件:

<apps>
    <app>
        <id>1</id>
        <name>Google Maps</name>
        <version>1.0</version>
    </app>
    <app>
        <id>2</id>
        <name>Chrome</name>
        <version>2.1</version>
    </app>
    <app>
        <id>3</id>
        <name>Google Play</name>
        <version>2.3</version>
    </app>
</apps>

1.Pull解析方式

private fun parseXMLWithPull(xmlData: String) {
    try {
        // 获取XmlPullParserFactory实例
        val factory = XmlPullParserFactory.newInstance()
        // 得到XmlPullParser对象
        val xmlPullParser = factory.newPullParser()
        // 设置XML数据
        xmlPullParser.setInput(StringReader(xmlData))
        // 获取当前解析事件
        var eventType = xmlPullParser.eventType
        var id = ""
        var name = ""
        var version = ""
        while (eventType != XmlPullParser.END_DOCUMENT) {
            // 获取当前节点名(id、name、version)
            val nodeName = xmlPullParser.name
            when (eventType) {
                // 开始解析某个节点
                XmlPullParser.START_TAG -> {
                    when (nodeName) {
                        // 获取节点具体内容
                        "id" -> id = xmlPullParser.nextText()
                        "name" -> name = xmlPullParser.nextText()
                        "version" -> version = xmlPullParser.nextText()
                    }
                }
                // 完成解析某个节点
                XmlPullParser.ENT_TAG -> {
                    if ("app" == nodeName) {
                        Log.d("MainActivity", "id is $id")
                        Log.d("MainActivity", "name is $name")
                        Log.d("MainActivity", "version is $version")
                    }
                }
            }
            // 获取下一个解析事件
            eventType = xmlPullParser.next()
        }
    } catch(e: Exception) {
        e.printStackTrace()
    }
}

2.SAX解析方式

SAX语法比Pull复杂一点,但是语义方面会更加清楚。

SAX解析通常会新建一个类继承自DefaultHandler,并重写父类5个方法。

class ContentHandler : DefaultHandler() {
    private var nodeName = ""
    private lateinit var id: StringBuilder
    private lateinit var name: StringBuilder
    private lateinit var version: StringBuilder
    
    // 开始XML解析时调用
    override fun startDocument() {
        id = StringBuilder()
        name = StringBuilder()
        version = StringBuilder()
    }
    
    // 开始解析某个节点时调用
    override fun startElement(uri: String, localName: String,
            qName: String, attributes: Attributes) {
        // 记录当前节点名
        nodeName = localName
        Log.d("ContentHandler", "uri is $uri")
        Log.d("ContentHandler", "localName is $localName")
        Log.d("ContentHandler", "qName is $qName")
        Log.d("ContentHandler", "attributes is $attributes")
    }
    
    // 获取节点内容时调用
    // 获取节点内容时可能会被调用多次
    // 一些换行符也会当内容解析,需要载代码中做好控制
    override fun characters(ch: CharArray, start: Int, length: Int) {
        // 根据当前节点名判断将内容添加到哪一个StringBuilder对象中
        when (nodeName) {
            "id" -> id.append(ch, start, length)
            "name" -> id.append(ch, start, length)
            "version" -> id.append(ch, start, length)
        }
    }
    
    // 完成某个节点的解析时调用
    override fun endElement(uri: String, localName: String, qName: String) {
        if ("app" == localName) {
            // 此时id、name、version中都可能包括回车或换行符
            Log.d("ContentHandler", "id is ${id.toString.trim()}")
            Log.d("ContentHandler", "name is ${name.toString.trim()}")
            Log.d("ContentHandler", "version is ${version.toString.trim()}")
            // 最后要将StringBuilder清空,不然会影响下一次读取
            id.setLength(0)
            name.setLength(0)
            version.setLength(0)
        }
    }
    
    // 完成整个XML解析时调用
    override fun endDocument(){}
}

在MainActivity中启用SAX解析:

private fun parseXMLWithSAX(xmlData: String) {
    try {
        val factory = SAXParserFactory.newInstance()
        val xmlReader = factory.newSAXParser().getXMLReader()
        val handler = ContentHandler()
        // 将ContentHandler的实例设置到XMLReader中
        xmlReader.contentHandler = handler
        // 开始执行解析
        xmlReader.parse(InputSource(StringReader(xmlData)))
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

二、解析JSON

约定一个JSON文件:

[{"id" : "5", "version" : "5.5", "name" : "Clash of Clans"},
{"id" : "6", "version" : "7.0", "name" : "Boom Beach"},
{"id" : "7", "version" : "3.5", "name" : "Clash Royale"}]

类似地,解析JSON也有很多种方法,可以使用官方提供的JSONObject,也可以使用Google的开源库GSON。另外,一些第三方的开源库如JacksonFastJSON等也非常不错。

1.使用JSONObject

private fun parseJSONWithJSONObject(jsonData: String) {
    try {
        val jsonArray = JSONArray(jsonData)
        for (i in 0 until jsonArray.length()) {
            val jsonObject = jsonArray.getJSONObject(i)
            val id = jsonObject.getString("id")
            val name = jsonObject.getString("name")
            val version = jsonObject.getString("version")
            Log.d("MainActivity", "id is $id")
            Log.d("MainActivity", "name is $name")
            Log.d("MainActivity", "version is $version")
        }
    } catch(e: Exception) {
        e.printStackTrace()
    }
}

2.使用GSON

添加依赖,编辑app/build.gradle:

dependencies {
    implementation "com.google.code.gson:gson:2.8.6"
}

GSON可以将一段JSON格式的字符串映射成一个对象,从而不需要手动编写代码进行解析了。

2.1.解析一段JSON

比如说一段JSON格式的数据如下:

{"name" : "Tom", "age" : 20}

那我们就可以定义一个Person类,并加入name和age这两个字段,然后只需要简单地调用如下代码就可以将JSON数据自动解析成一个Person对象:

val gson = Gson()
val person = gson.fromJson(jsonData, Person::class.java)

2.2.解析一段JSON数组

比如说一段JSON格式的数组数据如下:

[{"name" : "Tom", "age" : 20},
{"name" : "Jack", "age" : 25},

这时我们需要借助TypeToken将期望解析成的数据传入fromJson()方法中,如下:

val typeOf = object : TypeToken<List<Person>>() {}.type
val people = gson.fromJson<List<Person>>(jsonData, typeOf)

2.3.实际使用

首先新增一个App类:

class App(val id: String, val name: String, val version: String)

方法:

private fun parseJSONWithGSON(jsonData: String) {
    val gson = Gson()
    val typeOf = object : TypeToken<List<App>>() {}.type
    val appList = gson.fromJson<List<App>>(jsonData, typeOf)
    for (app in appList) {
        Log.d("MainActivity", "id is ${app.id}")
        Log.d("MainActivity", "name is ${app.name}")
        Log.d("MainActivity", "version is ${app.version}")
    }
}
License:  CC BY 4.0