Swift JSON与XML处理
JSON处理
JSON 基础介绍
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。它基于 JavaScript 的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。JSON 数据格式主要有两种结构:对象(object)和数组(array)。
对象是一个无序的键值对集合,用花括号 {}
包围,每个键值对之间用逗号 ,
分隔,键和值之间用冒号 :
分隔。例如:
{
"name": "John",
"age": 30,
"city": "New York"
}
数组是一个有序的值列表,用方括号 []
包围,值之间用逗号 ,
分隔。例如:
[10, 20, 30]
JSON 支持的数据类型包括字符串(string)、数字(number)、布尔值(boolean)、对象(object)、数组(array)以及 null
。
在 Swift 中处理 JSON
Swift 提供了多种方式来处理 JSON 数据,主要涉及到 Foundation 框架中的 JSONSerialization
类以及 Codable
协议。
使用 JSONSerialization 进行手动解析
JSONSerialization
类提供了方法来将 JSON 数据转换为 Foundation 对象(如 NSDictionary
、NSArray
、NSString
、NSNumber
和 NSNull
),以及将这些 Foundation 对象转换回 JSON 数据。
- 解析 JSON 数据 假设我们有如下的 JSON 数据字符串:
let jsonString = "{\"name\":\"John\",\"age\":30,\"city\":\"New York\"}"
if let data = jsonString.data(using:.utf8) {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
if let dict = json as? [String: Any] {
if let name = dict["name"] as? String {
print("Name: \(name)")
}
if let age = dict["age"] as? Int {
print("Age: \(age)")
}
if let city = dict["city"] as? String {
print("City: \(city)")
}
}
} catch {
print("Error: \(error)")
}
}
在上述代码中,首先将 JSON 字符串转换为 Data
类型。然后使用 JSONSerialization.jsonObject(with:options:)
方法将 Data
解析为 Foundation 对象。这里 options
参数如果传入 .mutableContainers
,则会返回可变的 NSMutableDictionary
和 NSMutableArray
。接着通过类型转换,获取字典中的具体值。
- 创建 JSON 数据
let jsonDict: [String: Any] = [
"name": "Jane",
"age": 25,
"city": "Los Angeles"
]
do {
let jsonData = try JSONSerialization.data(withJSONObject: jsonDict, options: [.prettyPrinted])
if let jsonString = String(data: jsonData, encoding:.utf8) {
print(jsonString)
}
} catch {
print("Error: \(error)")
}
在这段代码中,通过 JSONSerialization.data(withJSONObject:options:)
方法将字典转换为 JSON 数据。options
参数中的 .prettyPrinted
会让生成的 JSON 数据格式更易读,每个层级之间有缩进。
使用 Codable 协议进行自动解析
Swift 4.0 引入的 Codable
协议使得 JSON 解析变得更加简单和类型安全。Codable
是 Encodable
和 Decodable
协议的组合。
- 定义 Codable 结构体
struct Person: Codable {
let name: String
let age: Int
let city: String
}
这里定义了一个 Person
结构体,它遵循 Codable
协议。由于结构体中的所有属性都是 Codable
类型,Swift 编译器会自动为该结构体合成 Encodable
和 Decodable
的实现。
- 解析 JSON 数据
let jsonString = "{\"name\":\"Bob\",\"age\":28,\"city\":\"Chicago\"}"
if let data = jsonString.data(using:.utf8) {
do {
let decoder = JSONDecoder()
let person = try decoder.decode(Person.self, from: data)
print("Name: \(person.name), Age: \(person.age), City: \(person.city)")
} catch {
print("Error: \(error)")
}
}
上述代码中,首先将 JSON 字符串转换为 Data
类型。然后创建一个 JSONDecoder
实例,使用 decode(_:from:)
方法将 Data
解析为 Person
结构体实例。
- 创建 JSON 数据
let person = Person(name: "Alice", age: 22, city: "Boston")
do {
let encoder = JSONEncoder()
encoder.outputFormatting =.prettyPrinted
let jsonData = try encoder.encode(person)
if let jsonString = String(data: jsonData, encoding:.utf8) {
print(jsonString)
}
} catch {
print("Error: \(error)")
}
在这段代码中,创建一个 JSONEncoder
实例,设置 outputFormatting
为 .prettyPrinted
以获得易读的 JSON 格式。然后使用 encode(_:)
方法将 Person
结构体实例编码为 JSON 数据。
JSON 解析中的常见问题及解决方法
- 数据类型不匹配
在手动解析 JSON 数据时,如果 JSON 数据中的值类型与代码中期望的类型不匹配,就会导致类型转换失败。例如,将一个 JSON 中的字符串值尝试转换为整数。
解决方法:在类型转换前,使用
is
关键字进行类型检查。例如:
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
if let ageObject = json["age"] {
if let age = ageObject as? Int {
print("Age: \(age)")
} else if let ageString = ageObject as? String {
if let ageInt = Int(ageString) {
print("Age (converted from string): \(ageInt)")
}
}
}
}
-
JSON 格式错误 如果 JSON 数据格式不正确,例如缺少引号、括号不匹配等,解析会失败并抛出错误。 解决方法:在获取 JSON 数据后,首先使用在线 JSON 校验工具(如 JSONLint)验证 JSON 格式的正确性。在代码中,使用
do - catch
块捕获解析错误,并进行相应处理。 -
嵌套 JSON 结构 当 JSON 数据包含嵌套的对象或数组时,解析会变得复杂。例如:
{
"name": "Tom",
"age": 32,
"hobbies": ["reading", "swimming"],
"address": {
"street": "123 Main St",
"city": "Denver",
"zip": "80201"
}
}
解决方法:定义嵌套的 Codable
结构体。例如:
struct Address: Codable {
let street: String
let city: String
let zip: String
}
struct Person: Codable {
let name: String
let age: Int
let hobbies: [String]
let address: Address
}
然后按照正常的 Codable
解析流程进行解析。
XML处理
XML 基础介绍
XML(eXtensible Markup Language)是一种标记语言,用于存储和传输数据。与 JSON 不同,XML 具有更严格的语法规则,并且可以定义自己的标签。XML 文档由一个根元素和零个或多个子元素组成。
例如:
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book category="fiction">
<title lang="en">The Great Gatsby</title>
<author>F. Scott Fitzgerald</author>
<price>19.99</price>
</book>
<book category="non-fiction">
<title lang="en">Sapiens: A Brief History of Humankind</title>
<author>Yuval Noah Harari</author>
<price>24.99</price>
</book>
</bookstore>
在上述 XML 示例中,bookstore
是根元素,每个 book
是子元素。元素可以有属性(如 book
元素的 category
属性),子元素可以有文本内容(如 title
元素的文本 “The Great Gatsby”)。
在 Swift 中处理 XML
在 Swift 中处理 XML 数据,主要有两种常用的方式:使用 NSXMLParser
进行手动解析,以及使用第三方库如 AEXML
进行更方便的处理。
使用 NSXMLParser 进行手动解析
NSXMLParser
是 Foundation 框架提供的用于解析 XML 数据的类。它采用基于事件的解析方式,通过委托方法通知解析过程中的各种事件。
- 创建解析器并设置委托
let xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><bookstore><book category=\"fiction\"><title lang=\"en\">The Great Gatsby</title><author>F. Scott Fitzgerald</author><price>19.99</price></book><book category=\"non-fiction\"><title lang=\"en\">Sapiens: A Brief History of Humankind</title><author>Yuval Noah Harari</author><price>24.99</price></book></bookstore>"
if let data = xmlString.data(using:.utf8) {
let parser = XMLParser(data: data)
let delegate = BookstoreParser()
parser.delegate = delegate
parser.parse()
}
这里创建了一个 XMLParser
实例,并为其设置了一个自定义的委托 BookstoreParser
。
- 实现委托方法
class BookstoreParser: NSObject, XMLParserDelegate {
var currentElement: String?
var currentBook: Book?
var books: [Book] = []
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
currentElement = elementName
if elementName == "book" {
currentBook = Book()
if let category = attributeDict["category"] {
currentBook?.category = category
}
}
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
guard let currentElement = currentElement, let currentBook = currentBook else { return }
let trimmedString = string.trimmingCharacters(in:.whitespacesAndNewlines)
if trimmedString.isEmpty { return }
switch currentElement {
case "title":
currentBook.title = trimmedString
case "author":
currentBook.author = trimmedString
case "price":
if let price = Double(trimmedString) {
currentBook.price = price
}
default:
break
}
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if elementName == "book" {
if let book = currentBook {
books.append(book)
}
currentBook = nil
}
currentElement = nil
}
}
struct Book {
var category: String?
var title: String?
var author: String?
var price: Double?
}
在上述代码中,BookstoreParser
类实现了 XMLParserDelegate
协议的三个主要方法:
parser(_:didStartElement:namespaceURI:qualifiedName:attributes:)
:当解析器遇到一个开始标签时调用,这里我们记录当前元素名,并在遇到book
标签时初始化一个Book
实例,并处理book
标签的属性。parser(_:foundCharacters:)
:当解析器找到字符数据时调用,我们在这里根据当前元素名,将字符数据赋值给Book
实例的相应属性。parser(_:didEndElement:namespaceURI:qualifiedName:)
:当解析器遇到一个结束标签时调用,这里我们在遇到book
结束标签时,将Book
实例添加到books
数组中,并重置currentBook
和currentElement
。
使用 AEXML 进行便捷处理
AEXML
是一个第三方库,它提供了更简洁和直观的方式来处理 XML 数据。首先需要将 AEXML
库添加到项目中,可以通过 CocoaPods 或 Swift Package Manager 进行添加。
- 解析 XML 数据
import AEXML
let xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><bookstore><book category=\"fiction\"><title lang=\"en\">The Great Gatsby</title><author>F. Scott Fitzgerald</author><price>19.99</price></book><book category=\"non-fiction\"><title lang=\"en\">Sapiens: A Brief History of Humankind</title><author>Yuval Noah Harari</author><price>24.99</price></book></bookstore>"
if let data = xmlString.data(using:.utf8) {
do {
let xmlDocument = try AEXMLDocument(xmlData: data)
for book in xmlDocument.root?.children.filter({ $0.name == "book" })?? [] {
if let category = book.attributes["category"], let title = book["title"].value, let author = book["author"].value, let priceString = book["price"].value, let price = Double(priceString) {
print("Category: \(category), Title: \(title), Author: \(author), Price: \(price)")
}
}
} catch {
print("Error: \(error)")
}
}
在上述代码中,首先将 XML 字符串转换为 Data
类型,然后使用 AEXMLDocument(xmlData:)
方法创建一个 AEXMLDocument
实例。通过访问 root
属性获取根元素,然后过滤出所有的 book
子元素,并获取其属性和子元素的值。
- 创建 XML 数据
let bookstore = AEXMLElement(name: "bookstore")
let book1 = AEXMLElement(name: "book", attributes: ["category": "fiction"])
book1.addChild(AEXMLElement(name: "title", value: "The Great Gatsby", attributes: ["lang": "en"]))
book1.addChild(AEXMLElement(name: "author", value: "F. Scott Fitzgerald"))
book1.addChild(AEXMLElement(name: "price", value: "19.99"))
bookstore.addChild(book1)
let book2 = AEXMLElement(name: "book", attributes: ["category": "non-fiction"])
book2.addChild(AEXMLElement(name: "title", value: "Sapiens: A Brief History of Humankind", attributes: ["lang": "en"]))
book2.addChild(AEXMLElement(name: "author", value: "Yuval Noah Harari"))
book2.addChild(AEXMLElement(name: "price", value: "24.99"))
bookstore.addChild(book2)
let xmlDocument = AEXMLDocument(root: bookstore)
if let xmlData = xmlDocument.xmlData(options:.prettyPrinted), let xmlString = String(data: xmlData, encoding:.utf8) {
print(xmlString)
}
在这段代码中,通过创建 AEXMLElement
实例来构建 XML 结构,然后将这些元素组合成一个 AEXMLDocument
实例。最后使用 xmlData(options:)
方法将文档转换为 XML 数据,并设置 options
为 .prettyPrinted
以获得易读的 XML 格式。
XML 解析中的常见问题及解决方法
- 命名空间处理 XML 支持命名空间,这可能会使解析变得复杂。例如:
<ns1:bookstore xmlns:ns1="http://example.com/books">
<ns1:book>
<ns1:title>The Great Gatsby</ns1:title>
<ns1:author>F. Scott Fitzgerald</ns1:author>
<ns1:price>19.99</ns1:price>
</ns1:book>
</ns1:bookstore>
解决方法:在使用 NSXMLParser
时,parser(_:didStartElement:namespaceURI:qualifiedName:attributes:)
方法的 namespaceURI
参数会提供命名空间的 URI。可以根据命名空间 URI 来处理元素。在 AEXML
中,AEXMLElement
的 namespace
属性可以用于获取和设置命名空间相关信息。
-
XML 格式错误 与 JSON 类似,XML 也需要严格的格式,如果标签不匹配、属性格式错误等都会导致解析失败。 解决方法:使用在线 XML 校验工具(如 XML Validator)验证 XML 格式的正确性。在代码中,使用
do - catch
块捕获解析错误,并进行相应处理。 -
性能问题 在处理大型 XML 文件时,基于事件的解析(如
NSXMLParser
)通常比基于树的解析(如AEXML
,它会将整个 XML 构建成内存中的树结构)更高效。 解决方法:如果处理的 XML 数据量较大,优先选择NSXMLParser
进行解析。同时,可以通过合理设置缓冲区大小等方式优化性能。
JSON 与 XML 的比较
- 数据格式
- JSON:格式简洁,易于阅读和编写,采用键值对和数组的形式表示数据,更适合现代编程语言的数据交互。
- XML:格式较为复杂,有严格的标签定义和语法规则,支持自定义标签和命名空间,更适合需要高度结构化和扩展性的数据表示。
- 解析性能
- JSON:解析速度通常较快,因为其格式简单,在现代移动设备和 Web 应用中被广泛使用。
- XML:解析相对较慢,特别是在处理大型文件时,因为其语法复杂性和需要构建树状结构(在一些解析方式下)。
- 数据传输
- JSON:由于格式简洁,数据量相对较小,适合在网络传输中使用,特别是在移动应用和 Web API 中。
- XML:数据量相对较大,因为需要包含标签等元信息,在网络传输时可能需要更多的带宽。
- 应用场景
- JSON:常用于 Web 应用的前后端数据交互、移动应用的数据传输、配置文件等场景。
- XML:在企业级应用、数据交换标准(如 SOAP)、文档存储(如 eBook 格式)等场景中仍有广泛应用。
在实际项目中,应根据具体的需求和场景选择合适的数据格式和处理方式。如果注重简洁性和性能,JSON 可能是更好的选择;如果需要高度结构化和扩展性,XML 可能更合适。