I have a JSON which consist of a top object then an array which consist of different JSON Objects. I want to decode this json with minimal struct and without optional variables. If I can achieve, I also want to design a struct which handles all of the array objects via writing only Its relevant struct.
I'll try to simplify the example
As You can see in the image both "Id", "Token", "ServicePublicKey" are different JSON objects. Whole of my backend returns in this architecture of JSON. What I want to achive is that one struct as a wrapper and struct for (Id, ServicePublicKey, Token etc..). At the end when there is a new type coming from JSON, I need to write only relevant struct and add some code inside wrapper.
My Question is that: How can I parse this JSON without any optional variable?
How I try to parse it:
struct Initialization: Decodable {
var error: BunqError? //TODO: Change this type to a right one
var id: Int?
var publicKey: String?
var token: Token?
enum CodingKeys: String, CodingKey {
case error = "Error"
case data = "Response"
case Id = "Id"
case id = "id"
case ServerPublicKey = "ServerPublicKey"
case Token = "Token"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
error = nil
if let errorArray = try container.decodeIfPresent([BunqError].self, forKey: .error) {
if !errorArray.isEmpty {
error = errorArray[0]
}
}
if let unwrappedResponse = try container.decodeIfPresent([Response<Id>].self, forKey: .data) {
print(unwrappedResponse)
}
}
}
struct Response<T: Decodable>: Decodable {
let responseModel: T?
enum CodingKeys: String, CodingKey {
case Id = "Id"
case ServerPublicKey = "ServerPublicKey"
case Token = "Token"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
switch "\(T.self)"
{
case CodingKeys.Id.rawValue:
self.responseModel = try container.decode(T.self, forKey: .Id)
break;
case CodingKeys.ServerPublicKey.rawValue:
self.responseModel = try container.decode(T.self, forKey: .ServerPublicKey)
break;
default:
self.responseModel = nil
break;
}
}
}
struct Id: Decodable {
let id: Int
enum CodingKeys: String, CodingKey {
case id = "id"
}
}
struct ServerPublicKey: Decodable {
let server_public_key: String
}
struct Token: Decodable {
let created: String
let updated: String
let id: Int
let token: String
}
Json Example:
{
"Response" : [
{
"Id" : {
"id" : 123456
}
},
{
"Token" : {
"token" : "myToken",
"updated" : "2020-01-11 13:55:43.397764",
"created" : "2020-01-11 13:55:43.397764",
"id" : 123456
}
},
{
"ServerPublicKey" : {
"server_public_key" : "some key"
}
}
]
}
Question is: How to get the nth element of a JSON Array when Decoding with Codable in Swift?
What I want to achive is that one struct as a wrapper and struct for (Id, ServicePublicKey, Token etc..). At the end when there is a new type coming from JSON, I need to write only relevant struct and add some code inside wrapper. My Question is that: How can I parse this JSON without any optional variable?
First of all I totally agree with your idea. When decoding a JSON we should always aim to
So given this JSON
let data = """
{
"Response": [
{
"Id": {
"id": 123456
}
},
{
"Token": {
"token": "myToken",
"updated": "2020-01-11 13:55:43.397764",
"created": "2020-01-11 13:55:43.397764",
"id": 123456
}
},
{
"ServerPublicKey": {
"server_public_key": "some key"
}
}
]
}
""".data(using: .utf8)!
struct ID: Decodable {
let id: Int
}
struct Token: Decodable {
let token: String
let updated: String
let created: String
let id: Int
}
struct ServerPublicKey: Decodable {
let serverPublicKey: String
enum CodingKeys: String, CodingKey {
case serverPublicKey = "server_public_key"
}
}
struct Result: Decodable {
let response: [Response]
enum CodingKeys: String, CodingKey {
case response = "Response"
}
enum Response: Decodable {
enum DecodingError: Error {
case wrongJSON
}
case id(ID)
case token(Token)
case serverPublicKey(ServerPublicKey)
enum CodingKeys: String, CodingKey {
case id = "Id"
case token = "Token"
case serverPublicKey = "ServerPublicKey"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
switch container.allKeys.first {
case .id:
let value = try container.decode(ID.self, forKey: .id)
self = .id(value)
case .token:
let value = try container.decode(Token.self, forKey: .token)
self = .token(value)
case .serverPublicKey:
let value = try container.decode(ServerPublicKey.self, forKey: .serverPublicKey)
self = .serverPublicKey(value)
case .none:
throw DecodingError.wrongJSON
}
}
}
}
We can finally decode your JSON
do {
let result = try JSONDecoder().decode(Result.self, from: data)
print(result)
} catch {
print(error)
}
And this is the output
Result(response: [
Result.Response.id(
Result.Response.ID(
id: 123456
)
),
Result.Response.token(
Result.Response.Token(
token: "myToken",
updated: "2020-01-11 13:55:43.397764",
created: "2020-01-11 13:55:43.397764",
id: 123456)
),
Result.Response.serverPublicKey(
Result.Response.ServerPublicKey(
serverPublicKey: "some key"
)
)
])
I leave the date decoding to you as homework ;-)
This additional part should answer to your comment
Can we store variables like id, serverPublicKey inside Result struct without Response array. I mean instead of ResponseArray can we just have properties? I think It need a kind of mapping but I can't figure out.
Yes, I think we can.
We need to add one more struct to the ones already described above.
Here it is
struct AccessibleResult {
let id: ID
let token: Token
let serverPublicKey: ServerPublicKey
init?(result: Result) {
typealias ComponentsType = (id: ID?, token: Token?, serverPublicKey: ServerPublicKey?)
let components = result.response.reduce(ComponentsType(nil, nil, nil)) { (res, response) in
var res = res
switch response {
case .id(let id): res.id = id
case .token(let token): res.token = token
case .serverPublicKey(let serverPublicKey): res.serverPublicKey = serverPublicKey
}
return res
}
guard
let id = components.id,
let token = components.token,
let serverPublicKey = components.serverPublicKey
else { return nil }
self.id = id
self.token = token
self.serverPublicKey = serverPublicKey
}
}
This AccessibleResult struct has an initialiser which receives a Result value and tries to populated its 3 properties
let id: ID
let token: Token
let serverPublicKey: ServerPublicKey
If everything goes fine, I mean if the input Result
contains at least an ID
, a Token
and a ServerPublicKey
then the AccessibleResponse
is initialised, otherwise the init fails and nil` is returned.
if
let result = try? JSONDecoder().decode(Result.self, from: data),
let accessibleResult = AccessibleResult(result: result) {
print(accessibleResult)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With