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 encounteredfIs read five characters from the current position, and the string of five characters read must befalseOtherwise, an error is thrown; If the first character is encounteredtIs four characters from the current position, and the string of four characters read must betrueOtherwise, an error is thrown.
  • Resolve NULL when the first character is encounteredn, 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 existsuIf 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\ror\nAn 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, callreadElementFunction.
  • 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