Use dynamic CodingKeys to decode JSON in Swift - json

I cant figure out the way to decode this JSON using Swift.
I tried to follow a bunch of tutorials and guides but there is nothing that works for me.
The only things I am able to add static CodingKeys are: "especificaciones", "id", "titulo" and "default"
"especificaciones" : {
"EXTERIOR" : {
"sistema_automatico_de_ajuste_de_altura_para_faros" : {
"id" : "865",
"titulo" : "Sistema automatico de ajuste de altura para faros",
"default" : ""
},
"antena_" : {
"id" : "1366",
"titulo" : "Antena ",
"default" : ""
},
"luces_direccionales_en_espejos_laterales" : {
"id" : "734",
"titulo" : "Luces direccionales en espejos laterales",
"default" : ""
},
"faros_traseros" : {
"id" : "1430",
"titulo" : "Faros traseros",
"default" : ""
},
},
"DIMENSIONES INTERIORES" : {
"espacio_para_las_piernas___delantera_trasera" : {
"id" : "1417",
"titulo" : "Espacio para las piernas - delantera/trasera",
"default" : ""
},
"espacio_para_los_hombros____delantera_trasera" : {
"id" : "1418",
"titulo" : "Espacio para los hombros - delantera/trasera",
"default" : ""
},
"area_de_carga__l_" : {
"id" : "1498",
"titulo" : "Area de carga (L)",
"default" : ""
}
}
}
This is not the whole JSON but probably with this you can get an idea of what I am looking for.

{ "especificaciones": { "RINES Y LLANTAS": { "llantas_delanteras": { "id": "935", "titulo": "Llantas delanteras", "default": "" }
private struct CustomCodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
struct RootOutput {
var entities:Entities?
var mainRoot:String?
var subRoot:String?
}
struct Root: Decodable {
var rootLevel: SubTopRoot?
init(from decoder: Decoder) {
do{
let container = try decoder.container(keyedBy: CustomCodingKeys.self)
for key in container.allKeys{
rootLevel = try container.decodeIfPresent(SubTopRoot.self, forKey: CustomCodingKeys.init(stringValue: key.stringValue)!)
}
}catch{
print(error.localizedDescription)
}
}
}
struct SubTopRoot: Decodable {
var subTopLevel:SubRoot?
init(from decoder: Decoder) {
do{
let container = try decoder.container(keyedBy: CustomCodingKeys.self)
for key in container.allKeys{
subTopLevel = try container.decodeIfPresent(SubRoot.self, forKey: CustomCodingKeys.init(stringValue: key.stringValue)!)
for index in 0..<(subTopLevel?.SubRootLevel.count ?? 0){
subTopLevel?.SubRootLevel[index].mainRoot = key.stringValue
}
}
}catch{
print(error.localizedDescription)
}
}
}
struct SubRoot: Decodable {
var SubRootLevel:[RootOutput] = [RootOutput]()
init(from decoder: Decoder) {
do{
let container = try decoder.container(keyedBy: CustomCodingKeys.self)
for key in container.allKeys{
let entities = try container.decodeIfPresent(Entities.self, forKey: CustomCodingKeys.init(stringValue: key.stringValue)!)
SubRootLevel.append(RootOutput.init(entities: entities, mainRoot:"" , subRoot: key.stringValue))
}
}catch{
print(error.localizedDescription)
}
}
}
struct Entities: Codable {
var id: String?
var titulo: String?
var defaultItem: String?
init(from decoder: Decoder) {
do{
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: .id)
self.titulo = try container.decodeIfPresent(String.self, forKey: .titulo)
self.defaultItem = try container.decodeIfPresent(String.self, forKey: .defaultItem)
}catch{
print(error.localizedDescription)
}
}
enum CodingKeys: String, CodingKey {
case id = "id"
case titulo = "titulo"
case defaultItem = "default"
}
}
Hope this is helpful for you !
//Dummy Test
do {
let dataString = "{\r\n\t\"especificaciones\": {\r\n\t\t\"RINES Y LLANTAS\": {\r\n\t\t\t\"llantas_delanteras\": {\r\n\t\t\t\t\"id\": \"935\",\r\n\t\t\t\t\"titulo\": \"Llantas delanteras\",\r\n\t\t\t\t\"default\": \"\"\r\n\t\t\t},\r\n\t\t\t\"llantas_traseras\": {\r\n\t\t\t\t\"id\": \"936\",\r\n\t\t\t\t\"titulo\": \"Llantas traseras\",\r\n\t\t\t\t\"default\": \"\"\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"
let data = Data(dataString.utf8)
//here dataResponse received from a network request
let decoder = JSONDecoder()
let model = try decoder.decode(Root.self, from:data) //Decode JSON Response Data
print(model.rootLevel?.subTopLevel?.SubRootLevel)
} catch let parsingError {
print("Error", parsingError)
}
}

Related

Parse using Codable in Swift

Need to parse this JSON in such a way that i should be able to access the benefits associated to each plan inside the "enabled" key within "items" node as below:
let items = Items[0].plans.main[0].enabled[0].text
{
"MyData": {
"data": {
"benefits": {
"B1": {
"text": "Text1"
},
"B2": {
"text": "Text2"
},
"B3": {
"text": "text3"
}
}
},
"items": [
{
"plans": {
"main": [
{
"name": "plan1",
"enabled": [
"B1",
"B2"
],
"disabled": [
"B2",
"B3"
]
}
]
}
}
]
}
}
I have tried as below to achieve but seems like this is not working
class Main: Codable {
var name: String?
var enabled: [String]?
var disabled: [String]?
enum CodingKeys: String, CodingKey {
case name = "name"
case enabled = "enabled"
case disabled = "disabled"
}
class MyData: Codable {
var benefits: [String: Benefit]?
enum CodingKeys: String, CodingKey {
case benefits = "benefits"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let propertyContainer = try container.nestedContainer(keyedBy: CustomDynamicKey.self, forKey: .benefits)
self.benefits = propertyContainer.decodeValues()
}
class Benefit: Codable {
var text: String?
enum CodingKeys: String, CodingKey {
case text = "text"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
text = try container.decode(String.self, forKey: .text)
}
}
struct CustomDynamicKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
extension KeyedDecodingContainer where Key == DynamicKey {
func decodeValues() -> [String : Benefit] {
var dict = [String : Benefit]()
for key in allKeys {
if let md = try? decode(Benefit.self, forKey: key) {
dict[key.stringValue] = md
} else {
print("unsupported key")
}
}
return dict
}
}
I tried to parse the models individually. However, i am able to access the models separately but i need to map the corresponding the benefit with the respective plan at the time of parsing JSON itself inside the required init() methods using Manual parsing.
One way could be to use JSONDecoder alongside JSONSerialization. Codables are a bit uncomfortable to use in such situations, and you can make var benefits: [String: Benefit]? optional (which you already have) and remove it from the CodingKeys enum. Then, use JSONSerialization to get the benefits field filled.
See This

In Swift: How do I decode this JSON (which has variable keys)?

There is an API that supplies JSON data that I would like to use. I've given a summary of the JSON below. At the top level, the key to each record is a unique ID that matches the ID in the record itself. These keys are integers in quotes (starting at 1, unsorted and probably not contiguous).
Reading the JSON isn't a problem. What is the Codable "Response" struct required to receive the data?
if let response = try? JSONDecoder().decode(Response.self, from: data)
The JSON
{
"2546": {
"id": "2546",
"title": "Divis and the Black Mountain"
},
"1": {
"id": "1",
"title": "A la Ronde"
},
"2": {
"id": "2",
"title": "Aberconwy House"
}
}
I had this once also, looks like whoever created this endpoint doesn't really understand how JSON works...
try this out and then just return response.values so you have a list of items
struct Item: Codable {
let id, title: String
}
typealias Response = [String: Item]
Use a more dynamic version of CodingKey. You can read more about it here: https://benscheirman.com/2017/06/swift-json/
Check the section "Dynamic Coding Keys"
The Codable type struct Response should be,
struct Response: Decodable {
let id: String
let title: String
}
Now, parse the json data using [String:Response] instead of just Response like so,
do {
let response = try JSONDecoder().decode([String:Response].self, from: data)
print(response) //["1": Response(id: "1", title: "A la Ronde"), "2546": Response(id: "2546", title: "Divis and the Black Mountain"), "2": Response(id: "2", title: "Aberconwy House")]
} catch {
print(error)
}
You should implement a custom CodingKey, something like that:
struct MyResponse {
struct MyResponseItemKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
static let id = MyResponseItemKey(stringValue: "id")!
static let title = MyResponseItemKey(stringValue: "title")!
}
struct MyResponseItem {
let id: String
let subItem: MyResponseSubItem
}
struct MyResponseSubItem {
let id: String
let title: String
}
let responseItems: [MyResponseItem]
}
Not sure if the key of each item and the value of id are always equal, that's why there are 2 IDs in MyResponse.
And, of course, MyResponse should conform to Codable:
extension MyResponse: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: MyResponseItemKey.self)
responseItems = try container.allKeys.map { key in
let containerForKey = try container.nestedContainer(keyedBy: MyResponseItemKey.self, forKey: key)
let id = try containerForKey.decode(String.self, forKey: .id)
let title = try containerForKey.decode(String.self, forKey: .title)
return MyResponseItem(id: key.stringValue, subItem: MyResponseSubItem(id: id, title: title))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: MyResponseItemKey.self)
for responseItem in responseItems {
if let key = MyResponseItemKey(stringValue: responseItem.id) {
var subItemContainer = container.nestedContainer(keyedBy: MyResponseItemKey.self, forKey: key)
try subItemContainer.encode(responseItem.subItem.id, forKey: .id)
try subItemContainer.encode(responseItem.subItem.title, forKey: .title)
}
}
}
}
This is how you can use MyResponse:
let jsonString = """
{
"2546": {
"id": "2546",
"title": "Divis and the Black Mountain"
},
"1": {
"id": "1",
"title": "A la Ronde"
},
"2": {
"id": "2",
"title": "Aberconwy House"
}
}
"""
if let dataForJSON = jsonString.data(using: .utf8),
let jsonDecoded = try? JSONDecoder().decode(MyResponse.self, from: dataForJSON) {
print(jsonDecoded.responseItems.first ?? "")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
if let dataFromJSON = try? encoder.encode(jsonDecoded) {
let jsonEncoded = String(data: dataFromJSON, encoding: .utf8)
print(jsonEncoded ?? "")
}
}

Swift 4 JSON Decodable with multidimensional and multitype array reading influx data

Can anybody help me with decoding a SQL query result from an influx database? I could not find any hint on this yet...
There is a lot of examples out there, but nothing to work with such a structure like the SQL query towards an influx database would be able to decode...
{
"results": [
{
"statement_id": 0,
"series": [
{
"name": "XiaomiMiSensors",
"columns": [
"time",
"battery",
"collectorip",
"collectormac",
"conductivity",
"cputmp",
"firmwareversion",
"light",
"mac",
"moisture",
"sensorname",
"temperature"
],
"values": [
[
"2019-06-23T11:16:52Z",
23,
"192.168.178.81",
"B8:27:EB:0A:47:12",
1434,
66.604,
"3.1.9",
7670,
"C4:7C:8D:65:FA:95",
52,
"Plumeria",
23.6
],
[
"2019-06-23T12:19:56Z",
36,
"192.168.178.81",
"B8:27:EB:0A:47:12",
1401,
66.604,
"3.1.9",
10160,
"C4:7C:8D:65:FA:95",
53,
"Plumeria",
26.8
]
]
}
]
}
}
I have tried this one
struct Response: Decodable {
var values: [[[IntOrString]]]
}
struct Response1: Decodable {
var columns: [IntOrString]
}
enum IntOrString: Decodable {
case int(Int)
case string(String)
init(from decoder: Decoder) throws {
if let string = try? decoder.singleValueContainer().decode(String.self) {
self = .string(string)
return
}
if let int = try? decoder.singleValueContainer().decode(Int.self) {
self = .int(int)
return
}
throw IntOrStringError.intOrStringNotFound
}
enum IntOrStringError: Error {
case intOrStringNotFound
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let data = """
{
"series": [
{
"name": "XiaomiMiSensors",
"columns": [
"T1", "C2", "d3"
],
"values": [
[1, 1, 7, "Azuan Child", "Anak Azuan", "12345", "ACTIVE", "Morning", 7, 12, "2017-11-09 19:45:00"],
[28, 1, 0, "Azuan Child2", "Amran", "123456", "ACTIVE", "Evening", 1, 29, "2017-11-09 19:45:00"]
]
}
]
}
""".data(using: .utf8)!
if let response = try? JSONDecoder().decode(Response.self, from: data) {
let values = response.values
for value in values {
for intOrString in value {
switch intOrString {
case .int(let int): print("It's an int: \(int)")
case .string(let string): print("It's a string: \(string)")
}
}
}
}
if let response1 = try? JSONDecoder().decode(Response1.self, from: data) {
let columns = response1.columns
for intOrString in columns {
switch intOrString {
case .int(let int): print("It's an int: \(int)")
case .string(let string): print("It's a string: \(string)")
}
}
}
}
}
and this one
import Foundation
struct RawServerResponse {
enum OuterKeys: String, CodingKey {
case results
}
enum RootKeys: String, CodingKey {
case id, user, reviewCount = "reviews_count"
}
enum UserKeys: String, CodingKey {
case userName = "user_name", realInfo = "real_info"
}
enum RealInfoKeys: String, CodingKey {
case fullName = "full_name"
}
enum ReviewCountKeys: String, CodingKey {
case count
}
let results: Int
let id: Int
let userName: String
let fullName: String
let reviewCount: Int
}
extension RawServerResponse: Decodable {
init(from decoder: Decoder) throws {
// results
let outerContainer = try decoder.container(keyedBy: OuterKeys.self)
// id
let container = try decoder.container(keyedBy: RootKeys.self)
id = try container.decode(Int.self, forKey: .id)
// userName
let userContainer = try container.nestedContainer(keyedBy: UserKeys.self, forKey: .user)
userName = try userContainer.decode(String.self, forKey: .userName)
// fullName
let realInfoKeysContainer = try userContainer.nestedContainer(keyedBy: RealInfoKeys.self, forKey: .realInfo)
fullName = try realInfoKeysContainer.decode(String.self, forKey: .fullName)
// reviewCount
var reviewUnkeyedContainer = try container.nestedUnkeyedContainer(forKey: .reviewCount)
var reviewCountArray = [Int]()
while !reviewUnkeyedContainer.isAtEnd {
let reviewCountContainer = try reviewUnkeyedContainer.nestedContainer(keyedBy: ReviewCountKeys.self)
reviewCountArray.append(try reviewCountContainer.decode(Int.self, forKey: .count))
}
guard let reviewCount = reviewCountArray.first else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath + [RootKeys.reviewCount], debugDescription: "reviews_count cannot be empty"))
}
self.reviewCount = reviewCount
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let jsonString = """
{
"results": [
{
"id": 1,
"user": {
"user_name": "Tester",
"real_info": {
"full_name":"Jon Doe"
}
},
"reviews_count": [
{
"count": 4
}
]
}
]
}
"""
let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let serverResponse = try! decoder.decode(RawServerResponse.self, from: jsonData)
dump(serverResponse)
}
}
but both is obviously not working...
Try this:
// MARK: - Response
struct Response: Codable {
let results: [Result]
}
// MARK: - Result
struct Result: Codable {
let statementID: Int
let series: [Series]
enum CodingKeys: String, CodingKey {
case statementID = "statement_id"
case series
}
}
// MARK: - Series
struct Series: Codable {
let name: String
let columns: [String]
let values: [[Value]]
}
enum Value: Codable {
case double(Double)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Double.self) {
self = .double(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
throw DecodingError.typeMismatch(Value.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Value"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .double(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}
do {
let response = try JSONDecoder().decode(Response.self, from: jsonData)
print(response)
} catch {
print(error)
}

decoding complicated JSON objects in Swift

I'm totally new to development. Tried to find solution but they don't help me. I need do decode next JSON data:
{
"page": {
"currentPage": 1,
"batchSize": 400,
"totalItems": "23"
},
"items": [
{
"id": "b435a598-421c-4812-a3a9-773c47864558",
"firstname": "\u041d\u0410\u0422\u0410\u041b\u0406\u042f \u0412\u0406\u041a\u0422\u041e\u0420\u0406\u0412\u041d\u0410",
"lastname": "\u0413\u0423\u041d\u042c\u041a\u041e",
"placeOfWork": "\u0437\u0430\u0441\u0442\u0443\u043f\u043d\u0438\u043a \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0430-\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u0438\u043a \u0432\u0456\u0434\u0434\u0456\u043b\u0443 \u0441\u043e\u0446\u0456\u0430\u043b\u044c\u043d\u043e\u0457 \u0440\u043e\u0431\u043e\u0442\u0438 ",
"position": "",
"linkPDF": "https://public.nazk.gov.ua/storage/documents/pdf/b/4/3/5/b435a598-421c-4812-a3a9-773c47864558.pdf"
},
......
]
}
I tried this code:
struct Declarant: Codable {
var id: String
var firstname: String
var lastname: String
var placeOfWork: String
var position: String
var linkPDF: String
enum CodingKeys: String, CodingKey {
case id
case firstname
case lastname
case placeOfWork
case position
case linkPDF
}
init(from decoder: Decoder) throws {
let valueContainer = try decoder.container(keyedBy: CodingKeys.self)
self.id = try valueContainer.decode(String.self, forKey: CodingKeys.id)
self.firstname = try valueContainer.decode(String.self, forKey: CodingKeys.firstname)
self.lastname = try valueContainer.decode(String.self, forKey: CodingKeys.lastname)
self.placeOfWork = try valueContainer.decode(String.self, forKey: CodingKeys.placeOfWork)
self.position = try valueContainer.decode(String.self, forKey: CodingKeys.position)
self.linkPDF = try valueContainer.decode(String.self, forKey: CodingKeys.linkPDF)
}
}
struct DeclarationInfo: Codable {
let items: [Declarant]
enum CodingKeys: String, CodingKey {
case items
}
init(from decoder: Decoder) throws {
let valueContainer = try decoder.container(keyedBy: CodingKeys.self)
self.items = [try valueContainer.decode(Declarant.self, forKey: CodingKeys.items)]
}
}
...
let dataTask = URLSession.shared.dataTask(with: url) {
(data, response, error) in
let jsonDecoder = JSONDecoder()
print("Trying to decode data...")
if let data = data, let declarationInfo = try? jsonDecoder.decode(DeclarationInfo.self, from: data) {
completion(declarationInfo)
print(declarationInfo)
} else {
print("Either no data was returned, or data was not properly decoded.")
completion(nil)
}
}
and getting
Either no data was returned, or data was not properly decoded.
Where is the mistake?
self.items = [try valueContainer.decode(Declarant.self, forKey: CodingKeys.items)]
Should be
self.items = try valueContainer.decode( [ Declarant ].self, forKey: CodingKeys.items )
Full code:
import UIKit
struct Declarant: Codable {
var id: String
var firstname: String
var lastname: String
var placeOfWork: String
var position: String
var linkPDF: String
}
struct DeclarationInfo: Codable {
let items: [Declarant]
}
let json = """
{ \"page\" : {
\"currentPage\" : 1
, \"batchSize\" : 400
, \"totalItems\" : "23"
}
, \"items\" : [
{ \"id\" : "b435a598-421c-4812-a3a9-773c47864558 1"
, \"firstname\" : "The first name 1"
, \"lastname\" : "The last name 1"
, \"placeOfWork\" : "The placeofWork 1"
, \"position\" : "The position 1"
, \"linkPDF\" : "https://public.nazk.gov.ua/storage/documents/pdf/b/4/3/5/b435a598-421c-4812-a3a9-773c47864558.pdf"
}
, { \"id\" : "b435a598-421c-4812-a3a9-773c47864558 2"
, \"firstname\" : "The first name 2"
, \"lastname\" : "The last name 2"
, \"placeOfWork\" : "The placeofWork 2"
, \"position\" : "The position 2"
, \"linkPDF\" : "https://public.nazk.gov.ua/storage/documents/pdf/b/4/3/5/b435a598-421c-4812-a3a9-773c47864558.pdf"
}
]
}
"""
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let jsonDecoder = JSONDecoder()
print("Trying to decode data...")
if let data = json.data(using: .utf8),
let declarationInfo = try? jsonDecoder.decode(DeclarationInfo.self, from: data) {
print(declarationInfo)
} else {
print("Either no data was returned, or data was not properly decoded.")
}
}
}
Result in console:
Trying to decode data...
DeclarationInfo(items: [Dec.Declarant(id: "b435a598-421c-4812-a3a9-773c47864558 1", firstname: "The first name
1", lastname: "The last name 1", placeOfWork: "The placeofWork 1",
position: "The position 1", linkPDF:
"https://public.nazk.gov.ua/storage/documents/pdf/b/4/3/5/b435a598-421c-4812-a3a9-773c47864558.pdf"),
Dec.Declarant(id: "b435a598-421c-4812-a3a9-773c47864558 2", firstname:
"The first name 2", lastname: "The last name 2", placeOfWork: "The
placeofWork 2", position: "The position 2", linkPDF:
"https://public.nazk.gov.ua/storage/documents/pdf/b/4/3/5/b435a598-421c-4812-a3a9-773c47864558.pdf")])

Swift 4 Decodable with keys not known until decoding time

How does the Swift 4 Decodable protocol cope with a dictionary containing a key whose name is not known until runtime? For example:
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
}
]
Here we have an array of dictionaries; the first has keys categoryName and Trending, while the second has keys categoryName and Comedy. The value of the categoryName key tells me the name of the second key. How do I express that using Decodable?
The key is in how you define the CodingKeys property. While it's most commonly an enum it can be anything that conforms to the CodingKey protocol. And to make dynamic keys, you can call a static function:
struct Category: Decodable {
struct Detail: Decodable {
var category: String
var trailerPrice: String
var isFavorite: Bool?
var isWatchlist: Bool?
}
var name: String
var detail: Detail
private struct CodingKeys: CodingKey {
var intValue: Int?
var stringValue: String
init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
init?(stringValue: String) { self.stringValue = stringValue }
static let name = CodingKeys.make(key: "categoryName")
static func make(key: String) -> CodingKeys {
return CodingKeys(stringValue: key)!
}
}
init(from coder: Decoder) throws {
let container = try coder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.detail = try container.decode([Detail].self, forKey: .make(key: name)).first!
}
}
Usage:
let jsonData = """
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourite": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourite": null,
"isWatchlist": null
}
]
}
]
""".data(using: .utf8)!
let categories = try! JSONDecoder().decode([Category].self, from: jsonData)
(I changed isFavourit in the JSON to isFavourite since I thought it was a mispelling. It's easy enough to adapt the code if that's not the case)
You can write a custom struct that functions as a CodingKeys object, and initialize it with a string such that it extracts the key you specified:
private struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
Thus, once you know what the desired key is, you can say (in the init(from:) override:
let key = // whatever the key name turns out to be
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
So what I ended up doing is making two containers from the decoder — one using the standard CodingKeys enum to extract the value of the "categoryName" key, and another using the CK struct to extract the value of the key whose name we just learned:
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CodingKeys.self)
self.categoryName = try! con.decode(String.self, forKey:.categoryName)
let key = self.categoryName
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
}
Here, then, is my entire Decodable struct:
struct ResponseData : Codable {
let categoryName : String
let unknown : [Inner]
struct Inner : Codable {
let category : String
let trailerPrice : String
let isFavourit : String?
let isWatchList : String?
}
private enum CodingKeys : String, CodingKey {
case categoryName
}
private struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CodingKeys.self)
self.categoryName = try! con.decode(String.self, forKey:.categoryName)
let key = self.categoryName
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
}
}
And here's the test bed:
let json = """
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
}
]
"""
let myjson = try! JSONDecoder().decode(
[ResponseData].self,
from: json.data(using: .utf8)!)
print(myjson)
And here's the output of the print statement, proving that we've populated our structs correctly:
[JustPlaying.ResponseData(
categoryName: "Trending",
unknown: [JustPlaying.ResponseData.Inner(
category: "Trending",
trailerPrice: "",
isFavourit: nil,
isWatchList: nil)]),
JustPlaying.ResponseData(
categoryName: "Comedy",
unknown: [JustPlaying.ResponseData.Inner(
category: "Comedy",
trailerPrice: "",
isFavourit: nil,
isWatchList: nil)])
]
Of course in real life we'd have some error-handling, no doubt!
EDIT Later I realized (in part thanks to CodeDifferent's answer) that I didn't need two containers; I can eliminate the CodingKeys enum, and my CK struct can do all the work! It is a general purpose key-maker:
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CK.self)
self.categoryName = try! con.decode(String.self, forKey:CK(stringValue:"categoryName")!)
let key = self.categoryName
self.unknown = try! con.decode([Inner].self, forKey: CK(stringValue:key)!)
}
Here's what I eventually came up for this json:
let json = """
{
"BTC_BCN":{
"last":"0.00000057",
"percentChange":"0.03636363",
"baseVolume":"47.08463318"
},
"BTC_BELA":{
"last":"0.00001281",
"percentChange":"0.07376362",
"baseVolume":"5.46595029"
}
}
""".data(using: .utf8)!
We make such a structure:
struct Pair {
let name: String
let details: Details
struct Details: Codable {
let last, percentChange, baseVolume: String
}
}
then decode:
if let pairsDictionary = try? JSONDecoder().decode([String: Pair.Details].self, from: json) {
var pairs: [Pair] = []
for (name, details) in pairsDictionary {
let pair = Pair(name: name, details: details)
pairs.append(pair)
}
print(pairs)
}
It is also possible to call not pair.details.baseVolume, but pair.baseVolume:
struct Pair {
......
var baseVolume: String { return details.baseVolume }
......
Or write custom init:
struct Pair {
.....
let baseVolume: String
init(name: String, details: Details) {
self.baseVolume = details.baseVolume
......