JSON in Swift: What You Need to Know

There are three different ways to work with JSON (JavaScript Object Notation) in Swift, including using the built-in JSONSerialization class, the Codable protocol, and third-party libraries like SwiftyJSON, ObjectMapper, CodableAlamofire or something like this. We’ll look at each approach.


For example, make a JSON file

{
“ownerName” : “Anna”,
“posts” : [
{
“title” : “Async requests”,
“isPublished” : false,
“counts” : {
“likes” : 0,
“views” : 0
}
},
{
“title” : “SwiftUI”,
“isPublished” : true,
“counts” : {
“likes” : 20,
“views” : 1500
}
}
]
}


and create models for the view layer converted from JSON

struct Owner {
let name: String
let posts: [Post]
}

struct Post {
let title: String
let isPublished: Bool
let counts: Counts
}

struct Counts {
let likes: Int
let views: Int
}

1. JSONSerialization/Deserialization

JSONSerialization is a built-in class from Apple for parsing JSON in Swift. It’s available in the Foundation framework.

do {
let jsonDict = try JSONSerialization.jsonObject(with: jsonData)
let model = try makeModelFromSerializing(jsonDict as! [String: Any])
let data = try JSONSerialization.data(withJSONObject: makeJSON(fromModel: model))
print(“RESULT Serialization: (model)”)
print(“RESULT Convert: (String(data: data, encoding: .utf8))”)
} catch {
print(“ERROR: (error)”)
}


Where jsonData – it’s just Data from JSON file.


First, serialize the JSON data into any object using JSONSerialization.jsonObject, and if you print that object, you will see that the object is a dictionary [String: Any]. The next step is to convert this dictionary into our model Owner. To do this, we will create a utility method makeModelFromSerializing.

func makeModelFromSerializing(_ dict: [String: Any]) throws -> Owner {
func modelPost(_ dict: [String: Any]) throws -> Post {
guard let title = dict[“title”] as? String,
let isPublished = dict[“isPublished”] as? Bool,
let counts = dict[“counts”] as? [String: Any],
let likes = counts[“likes”] as? Int,
let views = counts[“views”] as? Int
else { throw NSError() }

return Post(
title: title,
isPublished: isPublished,
counts: Counts(likes: likes, views: views)
)
}

guard let ownerName = dict[“ownerName”] as? String,
let posts = dict[“posts”] as? [[String: Any]]
else { throw NSError() }

return Owner(name: ownerName, posts: try posts.map(modelPost(_:)))
}


And finally, convert from our model Owner JSON into Data. For this, convert our Owner model into a JSON dictionary using the makeJSON method, and after that, create the data using JSONSerialization.data.

func makeJSON(fromModel model: Owner) -> [String: Any] {
func makePostJSON(_ model: Post) -> [String: Any] {
[
“title”: model.title,
“isPublished”: model.isPublished,
“counts”: [“likes”: model.counts.likes, “views”: model.counts.views]
]
}

return [“ownerName”: model.name, “posts”: model.posts.map(makePostJSON(_:))]
}


Pros:

easy to use
built-in
You can simply convert JSON into models for the view layer without an additional DTO layer.


Cons:

manual mapping
hard to maintain since you get data by string keys
if you have more complex JSON, it generates more boilerplate code.

2. Codable Protocol

An encoded protocol was introduced in Swift 4. With this one, you can easily convert between JSON and Swift types. You define a struct or class that conforms to Codable and use JSONDecoder or JSONEncoder to encode or decode JSON.


Firstly make DTO (Data Transfer Object)

struct OwnerDTO: Codable {
let ownerName: String
let posts: [PostDTO]
}

extension OwnerDTO {
var convert: Owner {
Owner(name: ownerName, posts: posts.map(.convert))
}

init(model: Owner) {
self.ownerName = model.name
self.posts = model.posts.map(PostDTO.init(model:))
}
}

struct PostDTO: Codable {
let title: String
let isPublished: Bool
let counts: CountsDTO
}

extension PostDTO {
var convert: Post {
Post(title: title, isPublished: isPublished, counts: counts.convert)
}

init(model: Post) {
self.title = model.title
self.isPublished = model.isPublished
self.counts = CountsDTO(model: model.counts)
}
}

struct CountsDTO: Codable {
let likes: Int
let views: Int
}

extension CountsDTO {
var convert: Counts {
Counts(likes: likes, views: views)
}

init(model: Counts) {
self.likes = model.likes
self.views = model.views
}
}


This DTO’s conforms Codable (which is a type alias for the two protocols: Decodeable and Encodable) and creates a computed property convert that transforms our DTO into a Model for the view layer and a custom initialization that helps transform the model from the view layer into a DTO.

do {
let dto = try JSONDecoder().decode(OwnerDTO.self, from: jsonData)
let model = dto.convert
let json = try JSONEncoder().encode(OwnerDTO(model: model))
print(“RESULT Decodable: (dto)”)
print(“RESULT Model: (model)”)
print(“RESULT Encodable: (String(data: json, encoding: .utf8))”)
} catch {
print(“ERROR: (error)”)
}


To convert JSON data to a DTO model, use JSONDecoder().decode, to convert the DTO model to JSON data – JSONEncoder().encode


Pros:

easy to maintain
type-safe
compiler-supported

Cons:

additional layer – DTO
requires fixed data structures

3. Third-Party Libraries

There are several third-party libraries available for JSON parsing in Swift, such as SwiftyJSON, ObjectMapper, or CodableAlamofire. These libraries often provide more flexibility and convenience compared to the built-in solutions.


Let’s look at a SwiftyJSON example:

do {
let json = try JSON(data: jsonData)
let model = makeModel(json: json)
let data = try JSON(makeJSON(fromModel: model)).rawData()
print(“RESULT Third Party Lib: (json)”)
print(“RESULT Third Party Lib Model: (model)”)
print(“RESULT Third Party Lib JSON: (String(data: data, encoding: .utf8))”)
} catch {
print(“ERROR: (error)”)
}


To make model for the view layer using the method makeModel.

func makeModel(json: JSON) -> Owner {
func makePost(json: JSON) -> Post {
Post(
title: json[“title”].stringValue,
isPublished: json[“isPublished”].boolValue,
counts: Counts(likes: json[“counts”][“likes”].intValue, views: json[“counts”][“views”].intValue)
)
}

return Owner(name: json[“ownerName”].stringValue, posts: json[“posts”].arrayValue.map(makePost(json:)))
}


And to convert the model from the view layer into dictionary using the method makeJSON from 1 point


Pros:

flexible
easy to use
you can simply convert JSON into models for the view layer without an additional DTO layer


Cons:

third-party library
runtime errors possible

Conclusion

Each of these methods has its pros and cons. JSONSerialization is lightweight and built-in, Codable is type-safe and easy to use, while third-party libraries have more advanced features and convenience. Which one you choose depends on the specific requirements and preferences of your project.


All examples in this repository: https://github.com/Ze8c/SerializeJSON

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.