We will develop a small but complete Swift library for processing and serializing JSON data.
Project source: github.com/swiftdo/jso…
In Swift’s JSON parser (1), we constructed the JSON data type and embellished its output. In this article, the second in the Swift Code A JSON Parser series, we will parse JSON strings into JSON data.
Analytical analysis
Let’s review the definition of JSON:
public enum JSON {
case object([String: JSON])
case array([JSON])
case string(String)
case double(Double)
case int(Int)
case bool(Bool)
case null
}
Copy the code
We also know the data type of JSON:
"a string"
12345
12.3
true
null
[1.2.3]
{"a": 1."b": 2}
Copy the code
To parse json to JSON, we need to do the following parsing:
- Parsing string when we encounter the first character is a double quote
"
, you need to start from the current position to the next"
To parse out a string. - The value type is parsed to determine whether a character is a number and, if so, reads the character until it is not. Resolves whether this string array is a number. Pass
.
To distinguish between an int and a double. - Parse a Boolean type if the first character is encountered
f
Is read five characters from the current position, and the string of five characters read must befalse
Otherwise, an error is thrown; If the first character is encounteredt
Is four characters from the current position, and the string of four characters read must betrue
Otherwise, an error is thrown. - Resolve NULL when the first character is encountered
n
, four characters are read from the current position, and the four characters read constitute NULL. Otherwise, an error is thrown. - Parsing object object structure is
{"key": JSON }
We can first parse to the key string, and then recursively parse to the value because the value may be string, value type, Boolean, object, array, null. - Parsing an array The structure of an array is
[JSON, JSON]
Because the value can be a string, value type, Boolean, object, array, NULL, each element can be resolved recursively.
The whole parsing process:
Null, false, true
All three are parsed in the same way: judge the first character and then match subsequent characters.
func readJsonNull(str: String.index: Int) throws- > (JSON.Int) {
return try readJsonCharacters(str: str, index: index, characters: ["u"."l"."l"], error: ParserError(msg: Error reading null value), json: .null)
}
func readJsonFalse(str: String.index: Int) throws- > (JSON.Int) {
return try readJsonCharacters(str: str,index: index, characters: ["a"."l"."s"."e"], error: ParserError(msg: "Error reading false"), json: .bool(false))}func readJsonTrue(str: String.index: Int) throws- > (JSON.Int) {
return try readJsonCharacters(str: str,index: index, characters: ["r"."u"."e"], error: ParserError(msg: "Error reading true value"), json: .bool(true))}/// tools
func readJsonCharacters(str: String.index: Int.characters: [Character].error: ParserError.json: JSON) throws- > (JSON.Int) {
var ind = index
var result = true
for i in 0 ..< characters.count {
if str.count < = ind || str[ind] ! = characters[i] {
result = false
break
}
ind + = 1
}
// Subsequent characters match exactly
if result {
return (json, ind)
}
throw error
}
Copy the code
string
- The initial element is an array value of Character
- Encountered characters are escape characters
\
, the value added\
If the next character exists, check whether it existsu
If so, loop the next four characters to form Unicode. Or an error - If you encounter
"
, breaks out of the loop, converts the read value to a string, and returns the composed string and the subscript of the next character currently read - If the character read is
\r
or\n
An error, - Other values are directly added to value.
/// Read the string,index starts after"
func readString(str: String.index: Int) throws- > (String.Int) {
var ind = index
var value: [Character] = []
while ind < str.count {
var c = str[ind]
ind + = 1
if c = = "\ \" { // Check if it is an escape character
value.append("\ \")
if ind > = str.count {
throw ParserError(msg: "Unknown ending")
}
c = str[ind]
ind + = 1
value.append(c)
if c = = "u" {
try (0 ..< 4).forEach { _ in
c = str[ind]
ind + = 1
if isHex(c: c) {
value.append(c)
} else {
throw ParserError(msg: "Not a valid Unicode character")}}}}else if c = = "\"" {
break
} else if c = = "\r" || c = = "\n" {
throw ParserError(msg: "Incoming JSON string contents are not allowed to have newlines")}else {
value.append(c)
}
}
return (String(value), ind)
}
Copy the code
number
Read numeric characters up to the position of non-numeric characters and determine whether these characters contain. If so, convert to double data, otherwise to int data
/// read the number, index is the sequence number of the next character
func readJsonNumber(str: String.index: Int) throws- > (JSON.Int) {
var ind = index - 1 // A numeric character at the beginning
var value: [Character] = []
// Is a numeric character
while ind < str.count && isNumber(c: str[ind]) {
value.append(str[ind])
ind + = 1
}
/// If the decimal point is included, convert to double
if value.contains(".") {
if let v = Double(String(value)) {
return (.double(v), ind)
}
} else {
if let v = Int(String(value)) {
return (.int(v), ind)
}
}
throw ParserError(msg: "Unrecognized numeric type\(ind)")}Copy the code
object
- read
{
After, need to first{
Delete the possible Spaces. After removing the Spaces, check whether it is"
If not, an error is reported - Continue to read
"
The following characters until the next one"
Position, using these characters as keys. - Remove the quotes
"
After the whitespace character, determine whether the next character is:
If yes, read:
Is not a space, callreadElement
Function. - After reading, subsequent non-null characters are read, if yes
}
, the data is read successfully. If it is.
, cyclic read operation
/// Parse the JSON string as an object structure, with index representing the index of the next character
func parseObject(str: String.index: Int) throws- > (JSON.Int) {
var ind = index
var obj: [String: JSON] = [:]
repeat {
/// non-null characters are read
ind = readToNonBlankIndex(str: str, index: ind)
if str[ind] ! = "\"" {
throw ParserError(msg: "Unrecognized character"\(str[ind])"")
}
ind + = 1
// Read the string
let (name, ids) = try readString(str: str, index: ind)
ind = ids
if obj.keys.contains(name) {
throw ParserError(msg: "Key already exists:\(name)")
}
ind = readToNonBlankIndex(str: str, index: ind)
if str[ind] ! = ":" {
throw ParserError(msg: "Unrecognized characters:\(str[ind])")
}
ind + = 1
ind = readToNonBlankIndex(str: str, index: ind)
// read the next element
let next = try readElement(str: str, index: ind)
ind = next.1
obj[name] = next.0
/// non-whitespace characters are read
ind = readToNonBlankIndex(str: str, index: ind)
let ch = str[ind]
ind + = 1
if ch = = "}" { break }
if ch ! = "," {
throw ParserError(msg: "Unrecognized character")}}while true
return (.object(obj), ind)
}
Copy the code
array
Array parsing is similar to object parsing, with whitespace removed to separate each element. If the character] is encountered, the array string is parsed.
/// Parse the JSON string into an array structure
func parseArray(str: String.index: Int) throws- > (JSON.Int) {
var arr: [JSON] = []
var ind = index
repeat {
ind = readToNonBlankIndex(str: str, index: ind)
// read the next element
let ele = try readElement(str: str, index: ind)
ind = ele.1
arr.append(ele.0)
/// Read non-whitespace characters
ind = readToNonBlankIndex(str: str, index: ind)
let ch = str[ind]
ind + = 1
if ch = = "]" { break }
if ch ! = "," {
throw ParserError(msg: "Unrecognized character")}}while true
return (.array(arr), ind)
}
Copy the code
readElement
Call the respective parsing function with the first letter read:
func readElement(str: String.index: Int) throws- > (JSON.Int) {
var ind = index
let c = str[ind]
ind + = 1
switch c {
case "[":
return try parseArray(str: str, index: ind)
case "{":
return try parseObject(str: str, index: ind)
case "\"":
let (str, ids) = try readString(str: str, index: ind)
return (.string(str), ids)
case "t":
return try readJsonTrue(str: str, index: ind)
case "f":
return try readJsonFalse(str: str, index: ind)
case "n":
return try readJsonNull(str: str, index: ind)
case _ where isNumber(c: c):
return try readJsonNumber(str: str, index: ind)
default:
throw ParserError(msg: "The unknown element.\(c)")}}Copy the code
test
let str = "{ \"a\": [8, 9, 10].\"c\": {\"temp\":true,\"say\":\"hello\".\"name\":\"world\"}, \"b\": 10.2}"
print("Json string ::\n\(str) \n")
do {
// Parse the JSON string
let result = try parseJson(str: str)
print("\nReturn result ::")
// Formats a JSON string
print(prettyJson(json: result))
} catch {
print(error)
}
Copy the code
The results show:
Json string :: {"a": [8.-9.+10]."c": {"temp":true."say":"hello"."name":"world"}, "b":10.2} returns the result :: {"b":10.2."a": [8.-9.10]."c": {"name":"world"."say":"hello"."temp":true}}Copy the code
conclusion
Read from the beginning to the end of a character, read the first character, and parse the corresponding format according to json data format rules. Recursion is used for object and array parsing. The difficulty of parsing lies in the clarity of the JSON data rules and the subscript movement of the string.
Json parsing of the first version has been completed. If you have any questions, or want to join Swift wechat group, please follow our wechat official account: OldBirds