Decoding JSON structures with minor differences into just one format - json

I have three API endpoints that result in the same final structure, however the full JSON structure received from the API is a bit different in each of them.
First JSON result
{
"tracks": [{
"name": "Never Gonna Give You Up"
}]
}
Second JSON result
{
"items": [{
"name": "Never Gonna Give You Up"
}]
}
Third JSON result
{
"items": [{
"track": {
"name": "Never Gonna Give You Up"
}
}]
}
I want all of them to look like this
{
"tracks": [{
"name": "Never Gonna Give You Up"
}]
}
For that I'm using three different structures:
First:
struct TopHitsTrackResponse: Decodable {
var tracks: [Track]
}
Second:
struct FavoritesTrackResponse: Decodable {
var tracks: [Track]
enum CodingKeys: String, CodingKey {
case tracks = "items"
}
}
And the third one is the code below.
What I've tried
I have successfully made the first and second JSON results look exactly equal to the wanted result. However, the third one is a bit more complicated for me. Here's what I've tried without success.
struct NestedTrackResponse: Decodable {
let tracks: [Track]
enum CodingKeys: String, CodingKey {
case tracks = "items"
}
enum TrackKeys: String, CodingKey {
case track
}
init(from decoder: Decoder) throws {
let outerContainer = try decoder.container(keyedBy: CodingKeys.self)
let trackContainer = try outerContainer.nestedContainer(keyedBy: TrackKeys.self,
forKey: .tracks)
self.tracks = try trackContainer.decode([Track].self, forKey: .track)
}
struct Track: Decodable {
var name: String
}
}
I'm calling the API with this function
AF.request(urlRequest)
.validate()
.responseDecodable(of: NestedTrackResponse.self) { response in
// It's always resulting in fatal error
guard let data = response.value else {
fatalError("Error receiving tracks from API.")
}
}
// - `AF` is Alamofire, but I've already tried not using it,
// and the error persists
// - `urlRequest` is just the URL of the API and the API key,
// doesn't really matter for this problem
And getting this error
Expected to decode Dictionary<String, Any> but found an array instead.

You have to write a custom initializer with if let expressions to distinguish the cases.
struct Response : Decodable {
let tracks : [Track]
private enum CodingKeys : String, CodingKey { case items, tracks }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let tracks = try? container.decode([Track].self, forKey: .items) {
self.tracks = tracks
} else if let tracks = try? container.decode([Track].self, forKey: .tracks) {
self.tracks = tracks
} else if let items = try? container.decode([Item].self, forKey: .items) {
self.tracks = items.map(\.track)
} else {
throw DecodingError.dataCorruptedError(forKey: .items, in: container, debugDescription: "Unsupported JSON structure")
}
}
}
struct Item : Decodable {
let track : Track
}
struct Track : Decodable {
let name : String
}

As I said in the other answer, recommending to stick to the JSON structure that you receive from the API, this will prevent many headaches in future, as well as having a well-defined networking layer.
struct Track: Decodable {
var name: String
}
struct TopHitsTrackResponse: Decodable {
var tracks: [Track]
}
struct FavoritesTrackResponse: Decodable {
var items: [Track]
}
// though you should name this by the API name,
// like with the other two
struct NestedTrackResponse: Decodable {
var items: [Item]
struct Item: Decodable {
var track: Track
}
}
Basically the above is your networking layer. Now, comes the business layer, which will try to extract the tracks from each kind of response.
You could implement it with protocols, but functions work as well (if not even better):
func tracks(from response: TopHitsTrackResponse) -> [Track] {
response.tracks
}
func tracks(from response: FavoritesTrackResponse) -> [Track] {
response.items
}
func tracks(from response: NestedTrackResponse) -> [Track] {
response.items.map(\.track)
}
Then, in your API callbacks, you simply call tracks(from:), in order to extract the most wanted results.

Related

How to Parse Nested part of a JSON based on a condition in Swift

Other suggested solutions handle type of structure they are part of, While I am not able to figure out how to parse nested part of that same structure based on the value within outer structure.
I have a JSON response whose structure change based on one of the values within outer part of JSON.
For example:
{
"reports": [
{
"reportType": "advance",
"reportData": {
"value1": "true"
}
},
{
"reportType": "simple",
"reportData": {
"value3": "false",
"value": "sample"
}
}
]
}
Using Codable with string as Type for 'report' key fails to parse this json.
I want this report value to be either parsed later and store it as it is or atleast parse it based on the reportType value as this have different structure for each value of reportType.
I have written code based on the the suggested solutions.
enum ReportTypes: String {
case simple, advance
}
struct Reports: Codable {
let reportArray = [Report]
}
struct Report: Decodable {
let reportType: String
let reportData: ReportTypes
enum CodingKeys: String, CodingKey {
case reportType, reportData
}
init(from decoder: Decoder) {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.reportType = try container.decode(String.self, forKey: .reportType)
switch ReportTypes(rawValue: self.reportType) {
case .simple:
???
case .advance:
???
}
}
}
Please look at the switch cases and i'm not sure what to do. I need a solution similar to do this.
Workaround:
The workaround is that to mode that reportType inside the report {} structure and then follow this question How can i parse an Json array of a list of different object using Codable?
New Structure
{
"reports": [
{
"reportType": "advance",
"reportData": {
"reportType": "advance",
"value1": "true"
}
},
{
"reportType": "simple",
"reportData": {
"reportType": "simple",
"value3": "false",
"value": "sample"
}
}
]
}
So it worked out for me this way.
But if changing the structure is not what you can afford then this will not work.
Other possible solution I see and later Question: How to Access value of Codable Parent struct in a nested Codable struct is storing reportType in variable currentReportType from init(from decoder: Decoder) and then write another decoder for struct reportData that will handle decoding based on the value stored in var currentReportType. Write it by following the first link shared.
A reasonable way to decode this JSON is an enum with associated values because the type is known.
Create two structs for the objects representing the data in reportData
struct AdvancedReport: Decodable {
let value1: String
}
struct SimpleReport: Decodable {
let value3, value: String
}
and adopt Decodable in ReportType
enum ReportType: String, Decodable {
case simple, advance
}
The two main structs are the struct Response which is the root object (I renamed it because there are too many occurrences of the term Report) and the mentioned enum with associated values. The key reportType in the child dictionary is not being decoded because it's redundant.
struct Response: Decodable {
let reports : [Report]
}
enum Report: Decodable {
case simple(SimpleReport)
case advance(AdvancedReport)
private enum CodingKeys: String, CodingKey {
case reportType, reportData
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let reportType = try container.decode(ReportType.self, forKey: .reportType)
switch reportType {
case .simple: self = .simple(try container.decode(SimpleReport.self, forKey: .reportData))
case .advance: self = .advance(try container.decode(AdvancedReport.self, forKey: .reportData))
}
}
}
After decoding the JSON you can switch on reports
for report in response.reports {
switch report {
case .simple(let simpleReport): print(simpleReport)
case .advance(let advancedReport): print(advancedReport)
}
}

Swift & Codable: how to "bypass" some JSON levels?

I would like to use this Pokémon API to fetch some data and convert it into a Swift Pokemon struct.
Here is an extract of the response I get when fetching Pokemon #142:
{
"id": 142,
"name": "aerodactyl",
"types": [{
"type": {
"name": "rock",
"url": "https://pokeapi.co/api/v2/type/6/"
},
"slot": 1
},
{
"type": {
"name": "flying",
"url": "https://pokeapi.co/api/v2/type/3/"
},
"slot": 2
}
]
}
Here is the struct I wrote to convert this JSON into a Swift type:
struct Pokemon: Codable {
var id: Int
let name: String
var types: [PokemonType]?
}
struct PokemonType: Codable {
var type: PokemonTypeContent
}
struct PokemonTypeContent: Codable {
var name: PokemonTypeNameContent
}
enum PokemonTypeNameContent: String, Codable {
case flying = "flying"
case rock = "rock"
// ...
}
Now here is my problem: when I want to get the Pokemon types, I need to dig into this:
pokemon.types.first?.type.name
I would like to know if I have instead a way of getting the PokemonTypeNameContent array in the Pokemon struct, to do something like this:
struct Pokemon {
var types: [PokemonTypeNameContent]?
}
(I am not interested in getting the slot values).
Thank you for your help!
You can do custom encoding for PokemonTypeNameContent, and traverse through the levels of JSON using nestedContainer
enum PokemonTypeNameContent: String, Decodable {
case flying = "flying"
case rock = "rock"
// ...
enum OuterCodingKeys: CodingKey { case type }
enum InnerCodingKeys: CodingKey { case name }
init(from decoder: Decoder) throws {
// this is the container for each JSON object in the "types" array
let container = try decoder.container(keyedBy: OuterCodingKeys.self)
// this finds the nested container (i.e. JSON object) associated with the key "type"
let innerContainer = try container.nestedContainer(keyedBy: InnerCodingKeys.self, forKey: .type)
// now we can decode "name" as a string
let name = try innerContainer.decode(String.self, forKey: .name)
if let pokemonType = Self.init(rawValue: name) {
self = pokemonType
} else {
throw DecodingError.typeMismatch(
PokemonTypeNameContent.self,
.init(codingPath: innerContainer.codingPath + [InnerCodingKeys.name],
debugDescription: "Unknown pokemon type '\(name)'",
underlyingError: nil
)
)
}
}
}
// Pokemon can then be declared like this:
struct Pokemon: Decodable {
let id: Int
let name: String
let types: [PokemonTypeNameContent]
}
Do note that this means that you lose the option of decoding PokemonTypeNameContent as a regular enum. If you do want to do that, put the custom decoding code into a property wrapper. Note that we would be decoding the entire JSON array, instead of each JSON object.
#propertyWrapper
struct DecodePokemonTypes: Decodable {
var wrappedValue: [PokemonTypeNameContent]
init(wrappedValue: [PokemonTypeNameContent]) {
self.wrappedValue = wrappedValue
}
enum OuterCodingKeys: CodingKey { case type }
enum InnerCodingKeys: CodingKey { case name }
init(from decoder: Decoder) throws {
// container for the "types" JSON array
var unkeyedContainer = try decoder.unkeyedContainer()
wrappedValue = []
// while we are not at the end of the JSON array
while !unkeyedContainer.isAtEnd {
// similar to the first code snippet
let container = try unkeyedContainer.nestedContainer(keyedBy: OuterCodingKeys.self)
let innerContainer = try container.nestedContainer(keyedBy: InnerCodingKeys.self, forKey: .type)
let name = try innerContainer.decode(String.self, forKey: .name)
if let pokemonType = PokemonTypeNameContent(rawValue: name) {
wrappedValue.append(pokemonType)
} else {
throw DecodingError.typeMismatch(
PokemonTypeNameContent.self,
.init(codingPath: innerContainer.codingPath + [InnerCodingKeys.name],
debugDescription: "Unknown pokemon type '\(name)'",
underlyingError: nil
)
)
}
}
}
}
// You would write this in Pokemon
#DecodePokemonTypes
var types: [PokemonTypeNameContent]

Decode a nested object in Swift 5 with custom initializer

I have an API which returns a payload like this (just one item is included in the example).
{
"length": 1,
"maxPageLimit": 2500,
"totalRecords": 1,
"data": [
{
"date": "2021-05-28",
"peopleCount": 412
}
]
}
I know I can actually create a struct like
struct Root: Decodable {
let data: [DailyCount]
}
struct DailyCount: Decodable {
let date: String
let peopleCount: Int
}
For different calls, the same API returns the same format for the root, but the data is then different. Moreover, I do not need the root info (length, totalRecords, maxPageLimit).
So, I am considering to create a custom init in struct DailyCount so that I can use it in my URL session
let reports = try! JSONDecoder().decode([DailyCount].self, from: data!)
Using Swift 5 I tried this:
struct DailyCount: Decodable {
let date: String
let peopleCount: Int
}
extension DailyCount {
enum CodingKeys: String, CodingKey {
case data
enum DailyCountCodingKeys: String, CodingKey {
case date
case peopleCount
}
}
init(from decoder: Decoder) throws {
// This should let me access the `data` container
let container = try decoder.container(keyedBy: CodingKeys.self
peopleCount = try container.decode(Int.self, forKey: . peopleCount)
date = try container.decode(String.self, forKey: .date)
}
}
Unfortunately, it does not work. I get two problems:
The struct seems not to conform anymore to the Decodable protocol
The CodingKeys does not contain the peopleCount (therefore returns an error)
This can’t work for multiple reasons. You are trying to decode an array, so your custom decoding implementation from DailyCount won’t be called at all (if it were to compile) since at the top level your JSON contains an object, not an array.
But there is a much simpler solution which doesn’t even require implementing Decodable yourself.
You can create a generic wrapper struct for your outer object and use that with whatever payload type you need:
struct Wrapper<Payload: Decodable>: Decodable {
var data: Payload
}
You then can use this to decode your array of DailyCount structs:
let reports = try JSONDecoder().decode(Wrapper<[DailyCount]>.self, from: data).data
This can be made even more transparent by creating an extension on JSONDecoder:
extension JSONDecoder {
func decode<T: Decodable>(payload: T.Type, from data: Data) throws -> T {
try decode(Wrapper<T>.self, from: data).data
}
}
Sven's answer is pure and elegant, but I would be remiss if I didn't point out that there is also a stupid but easy way: dumpster-dive into the "data" without using Codable at all. Example:
// preconditions
let json = """
{
"length": 1,
"maxPageLimit": 2500,
"totalRecords": 1,
"data": [
{
"date": "2021-05-28",
"peopleCount": 412
}
]
}
"""
let jsonData = json.data(using: .utf8)!
struct DailyCount: Decodable {
let date: String
let peopleCount: Int
}
// okay, here we go
do {
let dict = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [AnyHashable:Any]
let arr = dict?["data"] as? Array<Any>
let json2 = try JSONSerialization.data(withJSONObject: arr as Any, options: [])
let output = try JSONDecoder().decode([DailyCount].self, from: json2)
print(output) // yep, it's an Array of DailyCount
} catch {
print(error)
}

Decoding JSON array of different types in Swift

I'm trying to decode the following JSON Object
{
"result":[
{
"rank":12,
"user":{
"name":"bob","age":12
}
},
{
"1":[
{
"name":"bob","age":12
},
{
"name":"tim","age":13
},
{
"name":"tony","age":12
},
{
"name":"greg","age":13
}
]
}
]
}
struct userObject {
var name: String
var age: Int
}
Basically a JSON Array with two different object types
{ "rank":12, "user": {userObject} }
and a
"1" : array of [userObjects]
struct data: Decodable {
rank: Int
user: user
1: [user] <-- this is one area Im stuck
}
Thanks in advance
Just for fun:
First you need structs for the users and the representation of the first and second dictionary in the result array. The key "1" is mapped to one
struct User : Decodable {
let name : String
let age : Int
}
struct FirstDictionary : Decodable {
let rank : Int
let user : User
}
struct SecondDictionary : Decodable {
let one : [User]
private enum CodingKeys: String, CodingKey { case one = "1" }
}
Now comes the tricky part:
First get the root container.
Get the container for result as nestedUnkeyedContainer because the object is an array.
Decode the first dictionary and copy the values.
Decode the second dictionary and copy the values.
struct UserData: Decodable {
let rank : Int
let user : User
let oneUsers : [User]
private enum CodingKeys: String, CodingKey { case result }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
var arrayContainer = try container.nestedUnkeyedContainer(forKey: .result)
let firstDictionary = try arrayContainer.decode(FirstDictionary.self)
rank = firstDictionary.rank
user = firstDictionary.user
let secondDictionary = try arrayContainer.decode(SecondDictionary.self)
oneUsers = secondDictionary.one
}
}
If this code is preferable over traditional manual JSONSerialization is another question.
If your JSON format is given then you are pretty much out of luck, since you will most likely have to parse your array as [Any] which is, to put it mildly, not very useful. If on the other hand you are able to modify the format of the JSON you should start from the other direction. Define your desired Swift object and encode it using JSONEncoder.encode(...) in order to quickly determine how your JSON should look like in order to make it parse in as typed a way as possible.
This approach will easily half your JSON handling code as your web service protocol will end up being structured much better. This will likely improve the structure of the overall system since it will yield a much more stable communication protocol.
Sadly enough this approach is not always possible which is when things get messy. Given your example you will be able to parse your code as
let st = """
{
"result":[
{
"rank":12,
"user":{
"name":"bob",
"age":12
}
},
{
"1":[
{
"name":"bob","age":12
},
{
"name":"tim","age":13
},
{
"name":"tony","age":12
},
{
"name":"greg","age":13
}
]
}
]
}
"""
let jsonData1 = st.data(using: .utf8)!
let arbitrary = try JSONSerialization.jsonObject(with: jsonData1, options: .mutableContainers)
This will let you access your data with a bunch of casts as in
let dict = arbitrary as! NSDictionary
print(dict["result"])
you get the idea. not very useful as you would very much like to use the Codable protocol as in
struct ArrayRes : Codable {
let result : [[String:Any]]
}
let decoder1 = JSONDecoder()
do {
let addrRes = try decoder.decode(ArrayRes.self, from: jsonData1)
print(addrRes)
} catch {
print("error on decode: \(error.localizedDescription)")
}
Unfortunately this does not work since Any is not Codable for slightly obvious reasons.
I hope you are able to change your JSON protocol since the current one will be the root cause of lot of messy code.

Swift 4 Decodable multiple containers

I'm trying to understand how could I parse this multiple container JSON to an object. I've tried this approach (Mark answer), but he explain how to solve it using one-level container. For some reason I can't mimic the behaviour for multiple containers.
{
"graphql": {
"shortcode_media": {
"id": "1657677004214306744",
"shortcode": "BcBQHPchwe4"
}
}
}
class Post: Decodable {
enum CodingKeys: String, CodingKey {
case graphql // The top level "user" key
case shortcode_media
}
enum PostKeys: String, CodingKey {
case id
}
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let post = try values.nestedContainer(keyedBy: PostKeys.self, forKey: .shortcode_media)
self.id = try post.decode(String.self, forKey: .id)
}
var id: String
}
I'm getting:
Swift.DecodingError.Context(codingPath: [], debugDescription: "Cannot get KeyedDecodingContainer<PostKeys> -- no value found for key \"shortcode_media\"", underlyingError: nil))
Any help will be much appreciated, thank you!
As vadian notes, you haven't matched the JSON structure. There is no shortcode_media key at the top level like you've encoded in CodingKeys.
In order to decode this with a custom decoder, you will need to walk through each level and deal with it.
class Post: Decodable {
enum CodingKeys: String, CodingKey {
case graphql
}
enum GraphQLKeys: String, CodingKey {
case shortcode_media
}
enum PostKeys: String, CodingKey {
case id
}
required init(from decoder: Decoder) throws {
// unload the top level
let container = try decoder.container(keyedBy: CodingKeys.self)
// Unload the graphql key
let graphql = try container.nestedContainer(keyedBy: GraphQLKeys.self, forKey: .graphql)
// unload the shortcode_media key
let post = try graphql.nestedContainer(keyedBy: PostKeys.self, forKey: .shortcode_media)
// Finally, unload the actual object
self.id = try post.decode(String.self, forKey: .id)
}
var id: String
}
Please read the JSON.
Any opening { is quasi a separator. The indentation of the JSON indicates also the hierarchy.
For clarity I removed all coding keys and left the variable names – which should be camelCased – unchanged.
struct Root : Decodable {
let graphql : Graph
// to access the `Media` object declare a lazy instantiated property
lazy var media : Media = {
return graphql.shortcode_media
}()
}
struct Graph : Decodable {
let shortcode_media : Media
}
struct Media : Decodable {
let id: String
let shortcode : String
}
let jsonString = """
{
"graphql": {
"shortcode_media": {
"id": "1657677004214306744",
"shortcode": "BcBQHPchwe4"
}
}
}
"""
do {
let data = Data(jsonString.utf8)
var result = try decoder.decode(Root.self, from: data)
print(result.media)
} catch {
print("error: ", error)
}
Writing a custom initializer with nestedContainer is more effort than creating the actual hierarchy.
Please paste the entire code in a Playground and check it out.