Swift 包含数字的可解码蛇形钥匙 Issue

Swift decodable snake case key which include numbers Issue

我在解码 JSON 时遇到问题,它的键在蛇形盒中,包括数字。 我已经花了几天时间试图解决它,但我看不出有任何解决方案。尽管我非常需要它。我也不知道它为什么会失败。在这里的任何帮助表示赞赏。谢谢!

例如JSON:

{
  "result": [
    {
      "symbol": "A",
      "v_price_24h": "39652.50",

    }
  ],
  "time_now": "1645931312.379455"
}

会抛出错误:

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))

我的模型 属性 是 vPrice24hv_price_24h 在 JSON

这适用于所有情况,但当我的键中有数字时会失败

decoder.keyDecodingStrategy = .convertFromSnakeCase

与我定制的相同: 我花了几天时间试图弄清楚,但无法解决这个问题。 有谁知道怎么处理吗?

这里是游乐场:

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: "", 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)
}


和结果

 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))

built-in 和您自定义的 snake case 策略都将 v_price_24h 转换为 vPrice24H。因此,如果您将结构中的字段重命名为大写 H,则可以删除除策略之外的所有自定义项,并且它会起作用。

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)
}

最简单的形式 convertFromSnakeCase

let jsonString = """
{
  "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
}

do {
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let result = try decoder.decode(TopModel.self, from: Data(jsonString.utf8))
    print(result)
} catch {
    print(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"), with divergent representation vPrice24H, converted to v_price24h.", underlyingError: nil))

只需在数字

后用大写字母声明受影响的属性
let vPrice24H : String

如果你真的需要小写 h 应用这些 CodingKeys

struct InsideModel: Decodable {
    
    private enum CodingKeys: String, CodingKey {
        case symbol, vPrice24h = "vPrice24H"
    }
    
    let symbol: String
    let vPrice24h : String
}