UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Decoding API. Again. How do I decode this JSON?

Forums > SwiftUI

Hello,

I have a second API question, apologies for that but I'm finding this decode thing tricky to understand, especially for large JSON files.

So I have a large JSON link below.

https://1drv.ms/t/s!Ahk-sk_KQft1jKkzdBku2jsfUQ3O9w

Out of this JSON I really only want to interrogate the fields Id, Name, Type and Kind. Below is an extract from the link above. (this starts at line 2049 of the linked JSON)

{
                    "id": "ga_transactions",  <<<<< THIS
                    "name": "Transactions",  <<<<< THIS
                    "type": "integer",  <<<<< THIS
                    "subtype": "integer",
                    "multiple": false,
                    "permissions": [
                        "filters",
                        "select",
                        "sort",
                        "agg:categorical",
                        "agg:numerical"
                    ],
                    "optional": false,
                    "kind": "metric",  <<<<< AND THIS
                    "global_field": null,
                    "diff_return_type": null,
                    "api_only": false,
                    "meta": {
                        "trends": "up",
                        "priority": 2,
                        "display_chart": true,
                        "ranges": [
                            0,
                            10,
                            100,
                            1000,
                            10000,
                            100000,
                            1000000,
                            10000000,
                            "inf"
                        ]
                    },
                    "suggestion": false
                },

I've tried a number of decode structs, the last two are below. Everything compiles OK but I get 'nil' returned for all fields wuth both of these.

STRUCT1

    struct APITransactionFields: Codable {
      struct Dataset: Codable {
        struct Field: Codable {
          struct Meta: Codable {
            let requiredFields: [String]?
            let incompatibleFields: [String]?
            let priority: Int?
            let trends: String?
            let displayChart: Bool?
            let ranges: String?
            let rendering: String?
            let currency: String?
            let currencySymbol: String?

            private enum CodingKeys: String, CodingKey {
              case requiredFields = "required_fields"
              case incompatibleFields = "incompatible_fields"
              case priority
              case trends
              case displayChart = "display_chart"
              case ranges
              case rendering
              case currency
              case currencySymbol = "currency_symbol"
            }
          }
          let id: String
          let name: String

          private enum CodingKeys: String, CodingKey {
            case id
            case name
          }
        }
        let id: String
        let name: String
      }
    }

STRUCT2

    struct Metric: Codable {
        let id: String?
        let name: String?
        let type: String?
        let subtype: String?
        let multiple: Bool?
        let permissions: [String]?
        let optional: Bool?
        let kind: String?
        let globalField: String?
        let diffReturnType: String?
        let apiOnly: Bool?
        let meta: MetricMeta?
        let suggestion: Bool?

        private enum CodingKeys: String, CodingKey {
            case id
            case name
            case type
            case subtype
            case multiple
            case permissions
            case optional
            case kind
            case globalField = "global_field"
            case diffReturnType = "diff_return_type"
            case apiOnly = "api_only"
            case meta
            case suggestion
        }
    }
    struct MetricMeta: Codable {
        let trends: String?
        let priority: Int?
        let displayChart: Bool?
        let ranges: [MetricRange]?
    }

    struct MetricRange: Codable {
        let range: String
    }

If anyone can advise before I pull out all of my hair i'd really appriciate it.

2      

Hi, My recomendation is to paste your json in to a website like Quicktype and use their structs, i got it to work with one i got from the site, just remove whatever you dont need.

2      

I have been (as Paul says) noodling about and com up with this

I add the JSON file to the Bundle The Data structs are

struct APITransactionField: Codable {
    let datasets: [Dataset]
}

struct Dataset: Codable, Hashable {
    let fields: [Field]
}

struct Field: Codable, Hashable, Identifiable {
    let id: String
    let name: String
    let type: String
    let kind: String
}

Then View

struct ContentView: View {
    let data = Bundle.main.decode(APITransactionField.self, from: "FullJSON.txt")

    var body: some View {
        List {
            ForEach(data.datasets, id: \.self) { data in
                ForEach(data.fields) { field in
                    VStack(alignment: .leading) {
                        HStack {
                            Text("ID")
                            Spacer()
                            Text(field.id)
                        }
                        HStack {
                            Text("Name")
                            Spacer()
                            Text(field.name)
                        }
                        HStack {
                            Text("Type")
                            Spacer()
                            Text(field.type)
                        }
                        HStack {
                            Text("kind")
                            Spacer()
                            Text(field.kind)
                        }
                    }
                }
            }
        }
    }
}

PS added an extension on Bundle

extension Bundle {
    func decode<T: Decodable>(
        _ type: T.Type,
        from file: String,
        dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate,
        keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys
    ) -> T {
        guard let url = self.url(forResource: file, withExtension: nil) else {
            fatalError("Failed to locate \(file) in bundle")
        }

        guard let data = try? Data(contentsOf: url) else {
            fatalError("Failed to load \(file) from bundle")
        }

        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = dateDecodingStrategy
        decoder.keyDecodingStrategy = keyDecodingStrategy

        // swiftlint:disable line_length
        do {
            return try decoder.decode(T.self, from: data)
        } catch DecodingError.keyNotFound(let key, let context) {
            fatalError("Failed to decode \(file) from bundle due to missing key '\(key.stringValue)' - \(context.debugDescription)")
        } catch DecodingError.typeMismatch(_, let context) {
            fatalError("Failed to decode \(file) from bundle due to type mismatch - \(context.debugDescription)")
        } catch DecodingError.valueNotFound(let type, let context) {
            fatalError("Failed to decode \(file) from bundle due to missing \(type) value = \(context.debugDescription)")
        } catch DecodingError.dataCorrupted(_) {
            fatalError("Failed to decode \(file) from bundle because it appears to be invalid JSON.")
        } catch {
            fatalError("Failed to decode \(file) from bundle: \(error.localizedDescription)")
        }
    }
}

3      

Thank you very much @Hectorcrdna and @NigelGee. Both super helpful responses.

I think I need to do better understaing the structure of JSON. My challenge here is that it's tricky to see the structure of complex json vis-a-vis the various arrays within arrays, then build a STRUCT to get at the data. QuickType is great but it gives me everything, then I need to understand how to trim the output to just focus on the fields i'm interested in. So @NigelGees response was great because it showed me exactly how the STRUCT should be trimmed.

It would be really handy if a tool like Ducky (or QuickType) would asked the user what fields they want to include in the STRUCT and trim accordingly. :)

Thanks again fellas!

2      

@flaneur7508 I tend to use Ducky as I can choose either flat or nested. I choose flat on this time as there was alot of keys. And got this

import Foundation

struct APITransactionField: Codable {
    let datasets: [Dataset]
    let metadata: Metadatum
}

struct Dataset: Codable {
    let id: String
    let name: String
    let multiple: Bool
    let fields: [Field]
    let groups: [Any]
    let category: [Any]?
}

struct Field: Codable {
    let id: String
    let name: String
    let type: String
    let subtype: String
    let multiple: Bool
    let permissions: [String]
    let optional: Bool
    let kind: String
    let globalField: String?
    let diffReturnType: Any?
    let apiOnly: Bool
    let meta: Meta?
    let suggestion: Bool?
}

struct Meta: Codable {
    let requiredFields: [String]?
    let incompatibleFields: [String]?
    let priority: Int?
    let trends: String?
    let displayChart: Bool?
    let ranges: [Any]?
    let rendering: String?
    let currency: String?
    let currencySymbol: String?
}

struct Metadatum: Codable {
    let timestamped: Bool
    let dateStart: String
    let dateEnd: String
    let dateLastModified: Date
    let dateLastSuccess: Date
    let config: Config
}

struct Config: Codable {
    let nbDays: Int
    let flatTable: Bool
    let gcpProject: String
    let lastMinDate: Int
    let segmentationUri: Any?
    let conversionSetting: ConversionSetting
    let preferredProtocol: String
    let googleAnalyticsSiteId: Int
    let googleAnalyticsAccountId: Int
    let googleAnalyticsWebpropertyId: String
    let googleAnalyticsIgnoreHostname: Bool
    let googleAnalyticsLandingPagePath: Any?
}

struct ConversionSetting: Codable {
    let currency: String
    let airlockDirectory: Any?
    let fieldsDefinition: [FieldsDefinition]
    let providerAutoimport: String
}

struct FieldsDefinition: Codable {
    let kind: String
    let meta: Meta
    let slug: String
    let type: String
    let column: Any?
    let `default`: Any?
    let displayName: String
}

struct Meta: Codable {
    let priority: Int
    let rendering: String?
    let trends: String?
    let displayChart: Bool?
}

Then look at the keys you want and put a comment (// Required) by them and then commented out the other keys (I do this so if i mistaken commented out a key that is require it easy to add back)

struct APITransactionField: Codable {
    let datasets: [Dataset] // Required because the field is in DataSet struct
//    let metadata: Metadatum
}

struct Dataset: Codable {
//    let id: String
//    let name: String
//    let multiple: Bool
    let fields: [Field] // Required because the required keys are in Field struct
//    let groups: [Any]
//    let category: [Any]?
}

struct Field: Codable {
    let id: String // Required
    let name: String // Required
    let type: String // Required
//    let subtype: String
//    let multiple: Bool
//    let permissions: [String]
//    let optional: Bool
    let kind: String // Required
//    let globalField: String?
//    let diffReturnType: Any?
//    let apiOnly: Bool
//    let meta: Meta?
//    let suggestion: Bool?
}

//struct Meta: Codable {
//    let requiredFields: [String]?
//    let incompatibleFields: [String]?
//    let priority: Int?
//    let trends: String?
//    let displayChart: Bool?
//    let ranges: [Any]?
//    let rendering: String?
//    let currency: String?
//    let currencySymbol: String?
//}
//
//struct Metadatum: Codable {
//    let timestamped: Bool
//    let dateStart: String
//    let dateEnd: String
//    let dateLastModified: Date
//    let dateLastSuccess: Date
//    let config: Config
//}
//
//struct Config: Codable {
//    let nbDays: Int
//    let flatTable: Bool
//    let gcpProject: String
//    let lastMinDate: Int
//    let segmentationUri: Any?
//    let conversionSetting: ConversionSetting
//    let preferredProtocol: String
//    let googleAnalyticsSiteId: Int
//    let googleAnalyticsAccountId: Int
//    let googleAnalyticsWebpropertyId: String
//    let googleAnalyticsIgnoreHostname: Bool
//    let googleAnalyticsLandingPagePath: Any?
//}
//
//struct ConversionSetting: Codable {
//    let currency: String
//    let airlockDirectory: Any?
//    let fieldsDefinition: [FieldsDefinition]
//    let providerAutoimport: String
//}
//
//struct FieldsDefinition: Codable {
//    let kind: String
//    let meta: Meta
//    let slug: String
//    let type: String
//    let column: Any?
//    let `default`: Any?
//    let displayName: String
//}
//
//struct Meta: Codable {
//    let priority: Int
//    let rendering: String?
//    let trends: String?
//    let displayChart: Bool?
//}

If if works and builds then you can delete the comment keys and end up with the simple structs as above.

PS You can (in Ducky) to ignore properties by adding the "path" eg metadata or datasets.id etc and it will then remove ALL the structs that it needs, however you need to do it for all the properties that you do not want!

2      

@NigelGee that's a great tip. Thank you!

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.