paint-brush
JSON in Swift: What You Need to Knowby@ze8c

JSON in Swift: What You Need to Know

by Maksym SytyiMay 7th, 2024
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

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.
featured image - JSON in Swift: What You Need to Know
Maksym Sytyi HackerNoon profile picture

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