How to Decode JSON Struct to Custom Data Model with Swift

Many types in Swift Standard Library and Foundation Framework such as Int, String, Data, URL, Date, etc. are Codable by default. For any custom type to be Codable automatically, it should conform to Codable Protocol and all of its stored properties should be Codable. For data encoding and decoding tasks, Swift offers Decodable protocols. By confirming these protocols custom models can be decoded from external representation such as JSON. This post leads you to decode a JSON struct to a custom model step by step.

Basic Usage

First of all, define a custom model called GoodsModel.

struct GoodsModel: Codable {
    var classId: Int?
    var type: Int?
    var title: String?
}

Next, convert a JSON object to your custom model.

let jsonDecoder = JSONDecoder()
let jsonData = try? JSONSerialization.data(withJSONObject: jsonDic, options: [])
let model: GoodsModel = try! jsonDecoder.decode(GoodsModel.self, from: jsonData)

In addition, built-in types such as ArrayDictionary, and Optional also conform to Codable whenever they contain codable types. You can use them as native types like String, Int, etc., and the entire structure will still satisfy Codable.

If you want to have a different key name in encoded data than in your custom type or a certain property name is different in your custom type than in JSON, define the raw value type of the CodingKeys enum String and provide the raw value to the case.

struct Movie: Codable {
   var movieId: Int?
   var name: String?
   var movieDetails: MovieDetail?

   enum CodingKeys: String, CodingKey {
      
       case movieId = "id"
       case name 
       case movieDetails    
   }
}

As a result, the simplest way to make a type codable is to declare its properties using types that are already Codable. These types include standard library types like StringInt, and Double; and Foundation types like DateData, and URL. Any type whose properties are codable automatically conforms to Codable just by declaring that conformance.

Never keep a big CodingKeys enumeration with all the keys. Instead, splitting the keys with nested enumerations to preserve the hierarchy is a better choice.

// top-level JSON object keys
private enum CodingKeys : String, CodingKey {

    // using camelCase case names, with snake_case raw values where necessary.
    // the raw values are what's used as the actual keys for the JSON object,
    // and default to the case name unless otherwise specified.
    case id, user, reviewsCount = "reviews_count"

    // "user" JSON object keys
    enum User : String, CodingKey {
        case username = "user_name", realInfo = "real_info"

        // "real_info" JSON object keys
        enum RealInfo : String, CodingKey {
            case fullName = "full_name"
        }
    }

    // nested JSON objects in "reviews" keys
    enum ReviewsCount : String, CodingKey {
        case count
    }
}

As you have seen, this is easier to keep track of the keys at each level in your JSON.

Error Handling

JSONDecoder throws DecodingError which consists of different error cases such as .dataCorrupted, .keyNotFound, .typeMismatch, .valueNotFound.

Conclusion

In conclusion, Decodable is the powerful feature in the Swift standard library to decode JSON easily. Moreover, JSONDecoder provides proper and accurate error handling which many third-party JSON serialization/deserialization libraries lack.

Leave a Reply

Your email address will not be published. Required fields are marked *