I have the following code to extract a JSON contained within a coding key:
let value = try! decoder.decode([String:Applmusic].self, from: $0["applmusic"])
This successfully handles the following JSONs:
{
"applmusic":{
"code":"AAPL",
"quality":"good",
"line":"She told me don't worry",
}
However, fails to extract a JSON with the coding key of applmusic
from the following one:
{
"applmusic":{
"code":"AAPL",
"quality":"good",
"line":"She told me don't worry",
},
"spotify":{
"differentcode":"SPOT",
"music_quality":"good",
"spotify_specific_code":"absent in apple"
},
"amazon":{
"amzncode":"SPOT",
"music_quality":"good",
"stanley":"absent in apple"
}
}
The data models for applmusic
,spotify
and amazon
are different. However, I need only to extract applmusic
and omit other coding keys.
My Swift
data model is the following:
public struct Applmusic: Codable {
public let code: String
public let quality: String
public let line: String
}
The API responds with the full JSON and I cannot ask it to give me only the needed fields.
How to decode only the specific part of the json? It seems, that Decodable
requires me to deserialize the whole json first, so I have to know the full data model for it.
Obviously, one of the solutions would be to create a separate Response
model just to contain the applmusic
parameter, but it looks like a hack:
public struct Response: Codable {
public struct Applmusic: Codable {
public let code: String
public let quality: String
public let line: String
}
// The only parameter is `applmusic`, ignoring the other parts - works fine
public let applmusic: Applmusic
}
Could you propose a better way to deal with such JSON structures?
A little bit more insight
I use it the following technique in the generic extension that automatically decodes the API responses for me. Therefore, I'd prefer to generalize a way for handling such cases, without the need to create a Root
structure. What if the key I need is 3 layers deep in the JSON structure?
Here is the extension that does the decoding for me:
extension Endpoint where Response: Swift.Decodable {
convenience init(method: Method = .get,
path: Path,
codingKey: String? = nil,
parameters: Parameters? = nil) {
self.init(method: method, path: path, parameters: parameters, codingKey: codingKey) {
if let key = codingKey {
guard let value = try decoder.decode([String:Response].self, from: $0)[key] else {
throw RestClientError.valueNotFound(codingKey: key)
}
return value
}
return try decoder.decode(Response.self, from: $0)
}
}
}
The API is defined like this:
extension API {
static func getMusic() -> Endpoint<[Applmusic]> {
return Endpoint(method: .get,
path: "/api/music",
codingKey: "applmusic")
}
}