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 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 } } ] } { "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 } 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 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)") } 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. Data 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. JSONSerialization.jsonObject [String: Any] Owner 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(_:))) } 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 . Owner Data Owner makeJSON 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(_:))] } 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: Pros: easy to use built-in You can simply convert JSON into models for the view layer without an additional DTO layer. easy to use built-in You can simply convert JSON into models for the view layer without an additional DTO layer. Cons: Cons: manual mapping hard to maintain since you get data by string keys if you have more complex JSON, it generates more boilerplate code. 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 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 } } 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. Codable 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)") } 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 JSONDecoder().decode JSONEncoder().encode Pros: Pros: easy to maintain type-safe compiler-supported easy to maintain type-safe compiler-supported Cons: Cons: additional layer – DTO requires fixed data structures additional layer – DTO requires fixed data structures 3. Third-Party Libraries 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)") } 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. 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:))) } 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 makeJSON Pros: Pros: flexible easy to use you can simply convert JSON into models for the view layer without an additional DTO layer flexible easy to use you can simply convert JSON into models for the view layer without an additional DTO layer Cons: Cons: third-party library runtime errors possible third-party library runtime errors possible Conclusion 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 https://github.com/Ze8c/SerializeJSON