【问题标题】:JSONDecoder unable to decode an object encoded by JSONEncoderJSONDecoder 无法解码由 JSONEncoder 编码的对象
【发布时间】:2021-03-16 12:53:25
【问题描述】:

我需要对蛇形 JSON 进行编码/解码。我发现编码器正确编码Value2 对象,但是解码器无法解码它。我在这里做错了什么?

需要的Json格式:

{
  "address_line_1" : "Address",
  "full_name" : "Name",
  "id" : 2
}

代码:

struct Value1: Codable {
    let id: Int
    let fullName: String
    let addressLine1: String
}

struct Value2: Codable {
    let id: Int
    let fullName: String
    let addressLine_1: String
}

func printJson(_ object: Data) throws {
    let json = try JSONSerialization.jsonObject(with: object, options: [])
    let data = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted, .sortedKeys])
    print(String(data: data, encoding: .utf8)!)
}

func encode<T: Encodable>(_ object: T) throws -> Data {
    let encoder = JSONEncoder()
    encoder.keyEncodingStrategy = .convertToSnakeCase
    return try encoder.encode(object)
}

func decode<T: Decodable>(_ type: T.Type, from data: Data) throws {
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    _ = try decoder.decode(type, from: data)
    print("✅ Decoded \(type) from:")
    try printJson(data)
}

do {
    var data: Data

    data = try encode(Value1(id: 1, fullName: "Name", addressLine1: "Address"))
    try decode(Value1.self, from: data)
    

    data = try encode(Value2(id: 2, fullName: "Name", addressLine_1: "Address"))
    _ = try decode(Value1.self, from: data)
    _ = try decode(Value2.self, from: data)
    
} catch {
    print("❌ Failed with error:", error)
}

输出:

✅ Decoded Value1 from:
{
  "address_line1" : "Address",
  "full_name" : "Name",
  "id" : 1
}
✅ Decoded Value1 from:
{
  "address_line_1" : "Address",
  "full_name" : "Name",
  "id" : 2
}
❌ Failed with error: keyNotFound(CodingKeys(stringValue: "addressLine_1", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"addressLine_1\", intValue: nil) (\"addressLine_1\"), with divergent representation addressLine1, converted to address_line_1.", underlyingError: nil))

【问题讨论】:

  • 因为您使用的是addressLine_1,所以它需要addressLine_1 ,但是由于您的编码器键策略是snakeCase,它会改变它。不要这样混,要么只用驼峰式,用snake策略,要么用你自己的CodingKeys。

标签: json swift jsondecoder


【解决方案1】:

convertFromSnakeCase 工作正常,您可以在第一次解码时检查它:

_ = try decode(Value1.self, from: data)

之后,当您尝试解码相同的 data 但使用 Value2 类型时,它肯定会失败,因为它需要不同的属性名称。这是您的编码蛇案例 JSON:

{
  "address_line_1" : "Address",
  "full_name" : "Name",
  "id" : 2
}

解码器转换后address_line_1 变为addressLine1(同样适用于full_name)符合Value1 的属性。如果您尝试解码 Value2 的相同数据,则会失败,因为属性名称需要 addressLine_1

在您的情况下,最佳策略是使用自定义编码键,如下所示:

struct Value2: Codable {
    private enum Value2CodingKey: String, CodingKey {
        case id
        case fullName = "full_name"
        case addressLine1 = "address_line_1"
    }

    let id: Int
    let fullName: String
    let addressLine1: String
}

【讨论】:

    【解决方案2】:

    我找到了一个不使用自定义编码键,而是使用自定义编码策略的解决方案,因此编码人员也可以在数字之前处理 _。 这样addressLine1 编码为address_line_1address_line_1 解码为addressLine1

    用法:

    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCaseWithNumbers
    
    let encoder = JSONEncoder()
    encoder.keyEncodingStrategy = .convertToSnakeCaseWithNumbers
    

    编码器实现:

    extension JSONEncoder.KeyEncodingStrategy {
        static var convertToSnakeCaseWithNumbers: JSONEncoder.KeyEncodingStrategy {
            .custom { codingKeys -> CodingKey in
                let stringValue = codingKeys.last!.stringValue
                let newKey = AnyKey(stringValue: convertToSnakeCase(stringValue))!
                return newKey
            }
        }
        
        private static func convertToSnakeCase(_ stringKey: String) -> String {
            var key = stringKey
            let searchRange = key.index(after: key.startIndex)..<key.endIndex
            let nsRange = key.nsRange(from: searchRange)
            let matches = NSRegularExpression("([A-Z])|([0-9]+)").matches(in: key, options: [], range: nsRange)
            for match in matches.reversed() {
                guard let range = key.range(from: match.range) else { continue }
                key.insert("_", at: range.lowerBound)
            }
            return key.lowercased()
        }
    }
    
    extension JSONDecoder.KeyDecodingStrategy {
        static var convertFromSnakeCaseWithNumbers: JSONDecoder.KeyDecodingStrategy {
            .custom { (codingKeys) -> CodingKey in
                let stringValue = codingKeys.last!.stringValue
                let newKey = AnyKey(stringValue: convertFromSnakeCase(stringValue))!
                return newKey
            }
        }
        
        private static func convertFromSnakeCase(_ stringKey: String) -> String {
            guard stringKey.contains("_") else {
                return stringKey
            }
            let components = stringKey.split(separator: "_").map({ $0.firstCapitalized })
            return components.joined().firstLowercased
        }
    }
    
    private extension NSRegularExpression {
        convenience init(_ pattern: String) {
            do {
                try self.init(pattern: pattern)
            } catch {
                preconditionFailure("Illegal regular expression: \(pattern).")
            }
        }
    }
    
    private extension StringProtocol {
        var firstLowercased: String { prefix(1).lowercased() + dropFirst() }
        var firstCapitalized: String { prefix(1).capitalized + dropFirst() }
    }
    
    enum AnyKey: CodingKey {
        case string(String)
        case int(Int)
        
        var stringValue: String {
            switch self {
            case .string(let string):
                return string
            case .int(let int):
                return "\(int)"
            }
        }
        
        var intValue: Int? {
            guard case let .int(int) = self else { return nil }
            return int
        }
        
        init?(stringValue: String) {
            guard !stringValue.isEmpty else { return nil }
            self = .string(stringValue)
        }
        
        init?(intValue: Int) {
            self = .int(intValue)
        }
    }
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-05-20
      • 2022-01-07
      • 2020-04-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-10
      相关资源
      最近更新 更多