Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Swift decodable snake case key which include numbers Issue

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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)
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading