Swift
JSON
Object Conversion
Swift Programming
iOS Development

Simple and clean way to convert JSON string to Object in Swift

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

In modern Swift, the clean answer to "convert a JSON string to an object" is usually Decodable plus JSONDecoder. That approach is concise, type-safe, and easier to maintain than building objects manually from dictionaries returned by JSONSerialization.

Decode into a typed model with Codable

The usual pattern is:

  1. turn the JSON string into Data
  2. define a model that matches the JSON structure
  3. decode with JSONDecoder

Here is a complete example.

swift
1import Foundation
2
3struct User: Decodable {
4    let id: Int
5    let name: String
6    let isAdmin: Bool
7}
8
9let json = """
10{
11  "id": 42,
12  "name": "Ava",
13  "isAdmin": true
14}
15"""
16
17guard let data = json.data(using: .utf8) else {
18    fatalError("Invalid UTF-8 string")
19}
20
21do {
22    let user = try JSONDecoder().decode(User.self, from: data)
23    print(user.name)
24} catch {
25    print("Decode failed:", error)
26}

This is the simplest and cleanest solution for most app code. The result is a real Swift type, so the compiler can help you catch mistakes early.

Nested JSON maps naturally to nested Swift types

If the JSON contains objects inside objects, mirror that structure in your model definitions.

swift
1import Foundation
2
3struct Address: Decodable {
4    let city: String
5    let zipCode: String
6}
7
8struct Customer: Decodable {
9    let name: String
10    let address: Address
11}
12
13let json = """
14{
15  "name": "Lena",
16  "address": {
17    "city": "Toronto",
18    "zipCode": "M5V"
19  }
20}
21"""
22
23let data = Data(json.utf8)
24let customer = try JSONDecoder().decode(Customer.self, from: data)
25print(customer.address.city)

That is another reason Decodable is preferred. The model definitions stay close to the shape of the incoming data instead of forcing you to navigate loosely typed dictionaries everywhere.

Use CodingKeys when JSON names do not match Swift names

Real payloads often use snake case or field names that do not match your preferred Swift naming. You can map them explicitly with CodingKeys.

swift
1import Foundation
2
3struct Account: Decodable {
4    let userID: Int
5    let displayName: String
6
7    enum CodingKeys: String, CodingKey {
8        case userID = "user_id"
9        case displayName = "display_name"
10    }
11}
12
13let json = """
14{
15  "user_id": 7,
16  "display_name": "Mark"
17}
18"""
19
20let account = try JSONDecoder().decode(Account.self, from: Data(json.utf8))
21print(account)

If your entire payload consistently uses snake case, JSONDecoder can also convert keys automatically.

swift
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

That removes a lot of boilerplate when the naming mismatch is systematic.

Build a reusable helper when you decode often

If the app decodes many JSON strings, wrap the pattern in a generic helper. This keeps calling code small without hiding the important failure points.

swift
1import Foundation
2
3func decodeJSON<T: Decodable>(_ json: String, as type: T.Type) throws -> T {
4    guard let data = json.data(using: .utf8) else {
5        throw NSError(domain: "DecodeJSON", code: 1, userInfo: [
6            NSLocalizedDescriptionKey: "Input was not valid UTF-8"
7        ])
8    }
9
10    let decoder = JSONDecoder()
11    return try decoder.decode(T.self, from: data)
12}
13
14let user: User = try decodeJSON("""
15{
16  "id": 1,
17  "name": "Ava",
18  "isAdmin": false
19}
20""", as: User.self)
21
22print(user)

A helper like this is fine as long as it stays thin. The actual decoding should still be done by JSONDecoder, not by custom reflection or manual key walking.

When JSONSerialization still makes sense

If the JSON shape is truly dynamic and cannot be represented cleanly as models, JSONSerialization may still be appropriate.

swift
1import Foundation
2
3let json = """{ "featureFlag": true, "threshold": 0.75 }"""
4let data = Data(json.utf8)
5let object = try JSONSerialization.jsonObject(with: data)
6print(object)

That returns Any, usually backed by Dictionary<String, Any> or Array<Any>. It is flexible, but you lose much of Swift's type safety. For application models, Decodable is usually the better default.

Common Pitfalls

A common mistake is trying to decode a JSON string directly without converting it to Data. JSONDecoder works with bytes, not with String.

Another mistake is defining model property types that do not match the payload. If the JSON field can be missing or null, the corresponding Swift property may need to be optional.

A third mistake is reaching for JSONSerialization first. That usually leads to long chains of casts and runtime errors that Decodable would have avoided.

Summary

  • In Swift, the cleanest path from JSON string to object is usually Decodable with JSONDecoder.
  • Convert the string to Data, then decode into a typed model.
  • Use nested models and CodingKeys to match the payload cleanly.
  • Use keyDecodingStrategy when the API consistently uses snake case.
  • Keep JSONSerialization for truly dynamic JSON, not as the default for app models.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.