To convert a JSON into an object that we've defined we can use the Decodable protocol, which allows us to easily use the JSONDecoder object to perform the conversion.
Converting a JSON to an Object
The requirements for our object are:
- The properties must have the same names as the parameters to be converted.
- The property types must match the types of the values to be converted.
- It must implement the Decodable protocol.
We'll define our object by implementing the Decodable protocol, and also a String in JSON format, which we'll use as an example.
struct Person: Decodable {
let name: String
let age: Int
}
let jsonString = """
{
"name": "Pablo",
"age": 19
}
"""
First, we need to convert our String into a Data object. This step is usually unnecessary when the response already comes as Data from the server, but for this example, we will convert it ourselves.
The second step is to use the decode(_:from:)
function from the JSONDecoder object. The decode(_:from:)
function has two parameters: the first specifies the type of object we want to obtain (in this case, Person.self
), and the second is our data
. This function also returns errors, so we'll use try?
as we've seen in previous articles.
if let data = jsonString.data(using: .utf8) {
if let person = try? JSONDecoder().decode(Person.self, from: data) {
print(person.name)
print(person.age)
}
}
Converting Arrays
In the case of having a JSON composed of an Array, we follow the same process, except the type of variable we want to obtain will be [Person].self
.
let jsonArrayString = """
[
{
"name": "Pablo",
"age": 19
},
{
"name": "Andrea",
"age": 23
},
{
"name": "Miguel",
"age": 31
},
]
"""
if let data = jsonArrayString.data(using: .utf8) {
if let persons = try? JSONDecoder().decode([Person].self, from: data) {
for person in persons {
print(person.name)
print(person.age)
}
}
}
// The console outputs:
// Pablo
// 19
// Andrea
// 23
// Miguel
// 31
Nested JSONs and Optionals
We can convert nested JSONs by defining the same structure in our objects. Let's see how we can have Person
objects that, in addition to name and age, can have an object of type Pet
.
We can define some properties as Optional
, if an Optional
parameter does not exist in our JSON, then the property value will be set to nil
. However, if a property is not Optional and it's not found in our JSON, the entire conversion will fail, returning nil
instead of an array.
struct Person: Decodable {
let name: String
let age: Int
let pet: Pet?
}
struct Pet: Decodable {
let name: String
}
let jsonArrayString = """
[
{
"name": "Pablo",
"age": 19,
"pet": {
"name": "Danko"
}
},
{
"name": "Andrea",
"age": 23
}
]
"""
if let data = jsonArrayString.data(using: .utf8) {
if let persons = try? JSONDecoder().decode([Person].self, from: data) {
for person in persons {
print(person.name)
print(person.age)
print(person.pet?.name ?? "No Pet")
}
}
}
// The console outputs:
// Pablo
// 19
// Danko
// Andrea
// 23
// No Pet
Using Different Property Names
In some cases, we may receive JSONs with parameters that have names that we can't or don't want to use. It is possible to use different property names than those in the JSON by adding the following enum.
We'll define a case
for each property we want to decode. The name of the case will be used to find that property in the JSON unless we assign a different one as a String
.
struct Person: Decodable {
let name: String
let age: Int
enum CodingKeys: String, CodingKey {
case name
case age = "years_old"
}
}
In this example, we can have the age
property, which will get its value from the years_old
parameter.
{
"name": "Pablo",
"years_old": 19
}
Be the first to comment