Skip to content

Instantly share code, notes, and snippets.

@stammy
Created April 11, 2020 20:59
Show Gist options
  • Save stammy/0872636a4c740e8a2011e57eaf09bbff to your computer and use it in GitHub Desktop.
Save stammy/0872636a4c740e8a2011e57eaf09bbff to your computer and use it in GitHub Desktop.
swift json codable with dynamic key
// this uses a dynamic coding key (StockKey) but does not work with nested objects (ideally quote should have many properties inside
import SwiftUI
import Combine
import Foundation
/* todo modify to work with json in format:
{
"AAPL":{"quote":{},"chart":[]},
"MSFT":{"quote":{},"chart":[]}
}
*/
let json = """
{
"AAPL" : {
"quote" : "asdf asdf asdf",
"latestPrice" : 267.99
},
"MSFT" : {
"quote" : "asdfasdfas",
"latestPrice" : 165.14
}
}
"""
struct StocksModel : Codable {
struct StockKey : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
static let latestPrice = StockKey(stringValue: "latestPrice")!
}
let quote : [StockModel]
struct StockModel : Codable {
let symbol: String
let latestPrice: Double
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StockKey.self)
var quote: [StockModel] = []
for key in container.allKeys {
let nested = try container.nestedContainer(keyedBy: StockKey.self, forKey: key)
let latestPrice = try nested.decode(Double.self, forKey: .latestPrice)
quote.append(StockModel(symbol: key.stringValue, latestPrice: latestPrice))
}
self.quote = quote
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StockKey.self)
for item in quote {
let key = StockKey(stringValue: item.symbol)!
var nested = container.nestedContainer(keyedBy: StockKey.self, forKey: key)
try nested.encode(item.latestPrice, forKey: .latestPrice)
}
}
}
let decoder = JSONDecoder()
if let jsonData = json.data(using: .utf8) {
do {
let results = try decoder.decode(StocksModel.self, from: jsonData)
print(results)
} catch {
print(error)
}
}
@gromwel
Copy link

gromwel commented Oct 21, 2020

You can do it like this:

struct StockModel : Codable {
    let quote:          String
    let latestPrice:    Double
    let key:            String
    
    private enum CodingKeys: CodingKey {
        case quote
        case latestPrice
    }
    
    //  Init
    
    init(quote: String, latestPrice: Double, key: String) {
        self.quote = quote
        self.latestPrice = latestPrice
        self.key = key
    }
    
    //  Encode
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        try container.encode(self.quote, forKey: .quote)
        try container.encode(self.latestPrice, forKey: .latestPrice)
    }
        
    //  Decode
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        guard let key = container.codingPath.first?.stringValue else {
            throw NSError(domain: "Key not found", code: 0, userInfo: nil)
        }
        
        self.quote = try container.decode(String.self, forKey: .quote)
        self.latestPrice = try container.decode(Double.self, forKey: .latestPrice)
        self.key = key
    }
    
}

struct StockModels : Codable {
    let models: [StockModel]
    
    private struct StockKey : CodingKey {
        var stringValue: String
        init?(stringValue: String) {
            self.stringValue = stringValue
        }
        var intValue: Int?
        init?(intValue: Int) { return nil }
    }
    
    //  Init
    init(_ models: [StockModel]) {
        self.models = models
    }
    
    //  Encode
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: StockKey.self)
        
        for model in self.models {
            if let codingKey = StockKey(stringValue: model.key) {
                try container.encode(model, forKey: codingKey)
            }
        }
    }
    
    //  Decode
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: StockKey.self)
        
        var models: [StockModel] = []
        for key in container.allKeys {
            if let key = StockKey(stringValue: key.stringValue) {
                let model = try container.decode(StockModel.self, forKey: key)
                models.append(model)
            }
        }
        self.models = models
    }
}

//  Decoding test

let json =
"""
{
"AAPL" : {
  "quote" : "asdf asdf asdf",
  "latestPrice" : 267.99
},
"MSFT" : {
  "quote" : "asdfasdfas",
  "latestPrice" : 165.14
}
}
"""

let decoder = JSONDecoder()
if let jsonData = json.data(using: .utf8) {
    do {
        let results = try decoder.decode(StockModels.self, from: jsonData)
        results.models.forEach { print($0) }
    } catch {
        print(error)
    }
}

//  Encoding test

let models = StockModels([
    StockModel(quote: "Text", latestPrice: 100.0, key: "AAPL"),
    StockModel(quote: "Test", latestPrice: 99.0, key: "MSFT")
])

let encoder = JSONEncoder()
do {
    let data = try encoder.encode(models)
    if let string = String(data: data, encoding: .utf8) {
        print(string)
    }
} catch {
    print(error)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment