I have a problem decoding JSON which has keys in the snake case including numbers.
I’ve already spent days trying to solve it and can’t tell I see any solution to it. Even though I need it badly. Nor do I have an idea why it is failing. Any help here is appreciated. Thanks!
for example JSON:
{
"result": [
{
"symbol": "A",
"v_price_24h": "39652.50",
}
],
"time_now": "1645931312.379455"
}
Will throw error:
keyNotFound(CodingKeys(stringValue: "vPrice24h", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "result", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"vPrice24h\", intValue: nil) (\"vPrice24h\").", underlyingError: nil))
my model property is vPrice24h
key v_price_24h is in JSON
This works for everything but fails when I have a number in a key
decoder.keyDecodingStrategy = .convertFromSnakeCase
same stuff with my custom one:
I spent days trying to figure it out and can’t solve this issue.
Does anyone know how to deal with it?
Here is playground:
import Foundation
var json = """
{
"result": [
{
"symbol": "A",
"v_price_24h": "39652.50",
}
],
"time_now": "1645931312.379455"
}
"""
struct CustomKey: CodingKey {
let stringValue: String
let intValue: Int?
init(stringValue: String) {
self.stringValue = stringValue
intValue = nil
}
init(intValue: Int) {
self.intValue = intValue
stringValue = String(intValue)
}
}
struct TopModel: Decodable {
let timeNow: String
let result: [InsideModel]
}
struct InsideModel: Decodable {
enum CodingKeys: String, CodingKey {
case symbol = "symbol"
case vPrice24h = "v_price_24h"
}
let symbol: String
let vPrice24h: String
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let decoder2 = JSONDecoder()
decoder2.keyDecodingStrategy = .custom { keys in
let last = keys.last!.stringValue
let snakePattern :String = "(\\w{0,1})_"
var key = last.capitalized.replacingOccurrences(of: snakePattern, with: "$1", options: .regularExpression, range: nil)
if key.count > 1 {
key = key.prefix(1).lowercased() + key.dropFirst()
}
print(key, last)
return CustomKey(stringValue: key)
}
let decoder3 = JSONDecoder()
decoder3.keyDecodingStrategy = .useDefaultKeys
do {
_ = try decoder.decode(TopModel.self, from: json.data(using: .utf8)!)
} catch let thrownError {
print("🚀", thrownError)
}
do {
_ = try decoder2.decode(TopModel.self, from: json.data(using: .utf8)!)
} catch let thrownError {
print("✅", thrownError)
}
do {
_ = try decoder3.decode(TopModel.self, from: json.data(using: .utf8)!)
} catch let thrownError {
print("🛑", thrownError)
}
and result
🚀 keyNotFound(CodingKeys(stringValue: "v_price_24h", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "result", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"v_price_24h\", intValue: nil) (\"v_price_24h\").", underlyingError: nil))
timeNow time_now
result result
symbol symbol
vPrice24H v_price_24h
✅ keyNotFound(CodingKeys(stringValue: "v_price_24h", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "result", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"v_price_24h\", intValue: nil) (\"v_price_24h\").", underlyingError: nil))
🛑 keyNotFound(CodingKeys(stringValue: "timeNow", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"timeNow\", intValue: nil) (\"timeNow\").", underlyingError: nil))
>Solution :
The built-in and your custom snake case strategies both convert v_price_24h into vPrice24H. So, if you rename the field in your struct with a capital H, you can drop every customization except the strategy, and it will work.
import Foundation
var json = """
{
"result": [
{
"symbol": "A",
"v_price_24h": "39652.50",
}
],
"time_now": "1645931312.379455"
}
"""
struct TopModel: Decodable {
let timeNow: String
let result: [InsideModel]
}
struct InsideModel: Decodable {
let symbol: String
let vPrice24H: String
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let model = try decoder.decode(TopModel.self, from: json.data(using: .utf8)!)
print("🚀✅", model)
} catch let thrownError {
print("🚀❌", thrownError)
}