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:

Cons:

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:

Cons:

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:

Cons:

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