swift-json makes it easy to deal with JSON data in Swift.
To be fast, light, and elegant, swift-json
is based on swift enum
and provides many utility features
swift-json
is not only a traditional json data structure. It's a generic data structure. So in many cases you can use JSON
instead of Any
Swift is very strict about types. But although explicit typing is good for saving us from mistakes, it becomes painful when dealing with JSON and other areas that are, by nature, implicit about types.
The code would look like this:
if let statusesArray = try? JSONSerialization.jsonObject(with: dataFromNetworking, options: .allowFragments) as? [[String: Any]],
let user = statusesArray[0]["user"] as? [String: Any],
let username = user["name"] as? String {
// Finally we got the username
}
It's not good.
Even if we use optional chaining, it would be messy:
if let object = try JSONSerialization.jsonObject(with: dataFromNetworking, options: .allowFragments) as? [[String: Any]],
let username = (object[0]["user"] as? [String: Any])?["name"] as? String {
// There's our username
}
An unreadable mess--for something that should really be simple!
With swift-json
all you have to do is:
let json = JSON(parse: dataFromNetworking)
if let name = json[0]["user"]["name"].string {
//Now you got your value
}
if let name = json[0,"user","name"].string {
//Now you got your value
}
- iOS 8.0+ | macOS 10.10+ | tvOS 9.0+ | watchOS 2.0+
- Xcode 8
You can use The Swift Package Manager to install swift-json
by adding the proper description to your Package.swift
file:
// swift-tools-version:5.7
import PackageDescription
let package = Package(
name: "YOUR_PROJECT_NAME",
dependencies: [
.package(url: "https://github.com/sutext/swift-json.git", from: "1.0.0"),
]
)
Then run swift build
whenever you get prepared.
import JSON
func testUsage() throws{
let jsonString = "{\"b0\":0,\"b1\":true,\"b2\":125,\"b3\":\"true\",\"b4\":1}"
let json = JSON(parse: jsonString)
XCTAssertEqual(json.rawString, jsonString) // parse rawString
let decodeJson = try? JSON.parse(json.rawData!) // parse rawData
XCTAssertEqual(json, decodeJson)
XCTAssertEqual(decodeJson?.rawString, jsonString)
let rawJson = JSON(json.rawValue) // init rawValue
XCTAssertEqual(json, rawJson)
}
func testCollection() throws {
var json = JSON("hello world")
print(json[.none])
print(json[0])
XCTAssertTrue(json.isEmpty) //Single value is an Empty Collection
XCTAssertTrue(json.count==0,"Single Json Collection Error")
json = JSON(["a","b",1,true,1.231231321])
for (key,value) in json{
print(key,":",value)
}
XCTAssertTrue(json.count==5,"Array Json Collection Error")
json = JSON(["name":"jack","age":10,"gender":1,"isok":true])
for (key,value) in json{
print(key,":",value)
}
XCTAssertTrue(json.count==4,"Object Json Collection Error")
}
func testGetters() throws {
var json = JSON(parse:"{\"float\":1.844674407370955e+30}")
json["int8"] = JSON(Int8(1))
json["int16"] = JSON(Int16.max)
json["int32"] = JSON(Int32.max)
json["int_min"] = JSON(Int64.min)
json["int_max"] = JSON(Int64.max)
json["uint_max"] = JSON(UInt64.max)
json["ary"] = [true,Double.pi,Int64.min,Int64.max,UInt64.max]
json["dic"] = ["name":"jackson","age":18,"obj":json]
json["empty"] = [:]
json["null"] = .null
json.test = "test"
XCTAssertEqual(json["int8"].int8, 1)
XCTAssertEqual(json["int8"].bool, true)
XCTAssertEqual(json["int8"].boolValue, true)
XCTAssertEqual(json["int16"].int16, Int16.max)
XCTAssertNil(json["int16"].bool)
XCTAssertEqual(json["int32"].int32, Int32.max)
XCTAssertEqual(json["null"].int16, nil)
XCTAssertEqual(json["uint_max"].uint64, UInt64.max)
XCTAssertEqual(json["uint_max"].uint64Value, UInt64.max)
XCTAssertEqual(json["ary"].array?.count, 5)
XCTAssertEqual(json["ary"].arrayValue.count, 5)
XCTAssertEqual(json["dic"].object?.count, 3)
XCTAssertEqual(json["dic"].objectValue.count, 3)
XCTAssertEqual(json.objectValue.count, json.count)
}
func testRawValue() throws {
let bool:Bool? = true
let int:Int? = nil
var json:JSON = (try? JSON.parse("{\"float\":1.844674407370955e+30}")) ?? .null
json["int16"] = JSON(Int16.max)
json["int32"] = JSON(Int32.max)
json["int_min"] = JSON(Int64.min)
json["int_max"] = JSON(Int64.max)
json["uint_max"] = JSON(UInt64.max)
json["ary"] = [true,Double.pi,Int64.min,Int64.max,UInt64.max,int,[bool],[int]]
json["dic"] = ["name":"jackson","age":18,"obj":json,"int":int]
json["empty"] = [:]
json["null"] = .null
json.test = "test"
let rawValue = json.rawValue
XCTAssertEqual(json, JSON(rawValue))
let rowData = json.rawData!
XCTAssertEqual(json, JSON(parse: rowData))
}
For more info about literal convertibles: Swift Literal Convertibles
func testCodable() throws {
var json:JSON = [:] //ExpressibleByDictionaryLiteral
json["int16"] = JSON(Int16.max)
json["int32"] = JSON(Int32.max)
json["int_min"] = JSON(Int64.min)
json["int_max"] = JSON(Int64.max)
json["uint_max"] = JSON(UInt64.max)
json["float"] = 1.844674407370955e+30
json.str = "ExpressibleByStringInterpolation" // ExpressibleByStringInterpolation,dynamicMemberLookup
json["bool"] = true //ExpressibleByBooleanLiteral
json["int"] = 3123123123//ExpressibleByIntegerLiteral
json["ary"] = [true,Double.pi,Int64.min,Int64.max,UInt64.max,11,[false]]//ExpressibleByArrayLiteral
json["dic"] = ["name":"jackson","age":18,"obj":["key":"value"],"int":9999999]//ExpressibleByDictionaryLiteral
json.int_max.test = 10 // warning happend
json.ary[10] = 1 // nothing happend
XCTAssertEqual(json.dic.count, 4)
XCTAssertEqual(json.ary[10], .null) // Got null rather than crash
XCTAssertEqual(json.ary.count, 7)
json["ary"] = nil // same as json["ary"] = .null; Means delete ary key
XCTAssertEqual(json.ary, .null) // got null
XCTAssertEqual(json.ary.count, 0)
XCTAssertEqual(json.noexist, .null) // got null
XCTAssertEqual(json.test.notexist, .null)
let ajson = try! JSON.parse(json.rawString!)//encode and decode
XCTAssertEqual(ajson, json)
}
func testSubscript() throws{
// With subscript in array
var json: JSON = [1,2,3]
json[0] = 100
json[1] = 200
json[2] = 300
json[99] = 999 // Don't worry, nothing will happen just a warning info
XCTAssertEqual(json[0].int, 100)
XCTAssertEqual(json[99], .null)
// With subscript in Object
json = ["name": "Jack", "age": 25]
json["name"] = "Mike"
json["age"] = "25" // It's OK to set String
json["address"] = "Newyork" // Add the "address": "Newyork" in json
XCTAssertEqual(json, ["name": "Mike", "age": "25","address":"Newyork"])
// Array & Object
var dic:JSON = ["name": "Jack", "age": 25, "list": ["a", "b", "c"]]
dic["list"][2] = "that"
XCTAssertEqual(dic.list[2].string, "that")
// With other JSON objects
let user: JSON = ["username" : "Steve", "password": "supersecurepassword"]
let auth: JSON = [
"user": user, // use user.object instead of just user
"apikey": "supersecretapitoken"
]
XCTAssertEqual(auth.user, user)
}
It is possible to merge one JSON into another JSON. Merging a JSON into another JSON adds all non existing values to the original JSON which are only present in the other
JSON.
If both JSONs contain a value for the same key, mostly this value gets overwritten in the original JSON, but there are two cases where it provides some special treatment:
- In case of both values being a
JSON.Array
the values form the array found in theother
JSON getting appended to the original JSON's array value. - In case of both values being a
JSON.Object
both JSON-values are getting merged the same way the encapsulating JSON is merged.
In a case where two fields in a JSON have different types, the value will get always overwritten.
There are two different fashions for merging: merge
modifies the original JSON, whereas merged
works non-destructively on a copy.
func testMerge() throws{
var base: JSON = [
"firstName": "Michael",
"age": 30,
"skills": ["Coding", "Reading"],
"address": [
"street": "Front St",
"zip": "12345",
]
]
let update: JSON = [
"lastName": "Jackson",
"age": 32,
"skills": ["Singing","Dancing"],
"address": [
"zip": "12342",
"city": "New York City",
"post": "123213"
]
]
let expect = """
{
"address" : {
"city" : "New York City",
"post" : "123213",
"street" : "Front St",
"zip" : "12342"
},
"age" : 32,
"firstName" : "Michael",
"lastName" : "Jackson",
"skills" : [
"Coding",
"Reading",
"Singing",
"Dancing"
]
}
"""
let newone = base.merged(from: update)
base.merge(from:update)
XCTAssertEqual(base,newone)
XCTAssertEqual(base,JSON(parse: expect))
}
}
Work with Alamofire
swift-json nicely wraps the result of the Alamofire JSON response handler:
Alamofire.request(url, method: .get).validate().responseJSON { resp in
switch resp.result {
case .success(let value):
let json = JSON(value)
print("JSON: \(json)")
case .failure(let error):
print(error)
}
}
Work with Moya
swift-json parse data to JSON:
let provider = MoyaProvider<Backend>()
provider.request(.getUserInfo) { result in
switch result {
case let .success(resp):
let json = JSON(parse: resp.data) // convert network data to json
print(json)
case let .failure(error):
print("error: \(error)")
}
}