ios 在Swift中,如何使用Decodable协议解码小写和Pascal大小写JSON?

mftmpeh8  于 2023-11-19  发布在  iOS
关注(0)|答案(3)|浏览(161)

直到最近,我已经能够使用Decodable协议来解码小写(“fooBar”)JSON和Pascal大小写(“FooBar”)JSON,只需包含一个像这样的CodingKeys枚举。

enum CodingKeys: String, CodingKey {
    case bars = "Bars"
}

字符串
这允许我以以下两种形式之一解码JSON:{"bars":[]}{"Bars":[]}
但这对我不再有效。
完整的例子如下。当我运行这段代码时,只有具有Pascal大小写字段名的JSON被解码。解码小写JSON的唯一方法是将CodingKeys更改为小写(简单地匹配定义的字段名)或完全删除它们。
范例:

import UIKit

struct Foo: Codable {
    var bars: [Bar]
    
    enum CodingKeys: String, CodingKey {
        case bars = "Bars"
    }

    struct Bar: Codable {
        var barBaz: String
        
        enum CodingKeys: String, CodingKey {
            case barBaz = "BarBaz"
        }
    }
}

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let fooBars = [
            "{\"bars\": [{\"barBaz\": \"abc\"}]}",
            "{\"Bars\": [{\"BarBaz\": \"abc\"}]}",
            "{\"Bars\": [{\"BarBaz\": \"abc\"},{\"BarBaz\": \"def\"}]}",
            "{\"Bars\": []}",
            "{\"bars\": []}"
        ]
        
        fooBars.forEach {
            if let fooBar = $0.data(using: .utf8) {
                do {
                    let data = try JSONDecoder().decode(Foo.self, from: fooBar)
                    print("Success:\nFound \(data.bars.count) bar(s).\n")
                } catch {
                    print("Fail:\n\(error)\n")
                }
            }
        }
    }
    
}


输出量:

Fail:
keyNotFound(CodingKeys(stringValue: "Bars", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"Bars\", intValue: nil) (\"Bars\").", underlyingError: nil))

Success:
Found 1 bar(s).

Success:
Found 2 bar(s).

Success:
Found 0 bar(s).

Fail:
keyNotFound(CodingKeys(stringValue: "Bars", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"Bars\", intValue: nil) (\"Bars\").", underlyingError: nil))


我的应用程序仍然使用这种CodingKey方法引用旧的Codable类。所以,我怀疑我在这个特定的例子中一定做错了什么。
有人能解释一下我做错了什么吗?

3bygqnnd

3bygqnnd1#

我可以通过实现这样的自定义初始化器来实现这一点.

enum CodingKeys: String, CodingKey {
    case bars, Bars
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
            
    if let bars = try? container.decode([Bar].self, forKey: .bars) {
        self.bars = bars
    } else if let bars = try? container.decode([Bar].self, forKey: .Bars) {
        self.bars = bars
    }
}

字符串
这允许我在解码时捕获值并将其解析为相应的属性。现在我可以将{"bars":[]}"{Bars:[]}"读入bars属性。

pkwftd7m

pkwftd7m2#

扩展Joel自己的答案。如果没有解码你的json,你的代码将默默地失败。不要使用try?忽略错误。你应该总是抓住它们。顺便说一句data(using: .utf8)永远不会失败。你可以安全地强制解包结果或使用Data non falible initializer Data($0.utf8)
自定义解码器的正确实现应该是这样的:

struct Foo: Decodable {
    let bars: [Bar]
    enum CodingKeys: String, CodingKey {
        case bars, Bars
    }
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        do {
            bars = try container.decode([Bar].self, forKey: .bars)
        } catch {
            bars = try container.decode([Bar].self, forKey: .Bars)
        }
    }
    struct Bar: Decodable {
        let barBaz: String
        enum CodingKeys: String, CodingKey {
            case barBaz, BarBaz
        }
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            do {
                barBaz = try container.decode(String.self, forKey: .barBaz)
            } catch {
                barBaz = try container.decode(String.self, forKey: .BarBaz)
            }
        }
    }
}

字符串

jhkqcmku

jhkqcmku3#

这是一种不同的方法,使用自定义密钥解码策略,它使所有CodingKeys的第一个字符为

extension JSONDecoder.KeyDecodingStrategy {
    static var lowerCamelCase: JSONDecoder.KeyDecodingStrategy {
        .custom {
            let currentKey = $0.last!.stringValue
            return AnyKey(stringValue: currentKey.first!.lowercased() + currentKey.dropFirst())! }
    }
}

字符串
它需要一个自定义的CodingKey

public struct AnyKey: CodingKey {
   public let stringValue: String
   public init?(stringValue: String) { self.stringValue = stringValue }
   public var intValue: Int? { return nil }
   public init?(intValue: Int) { return nil }
}


现在应用该策略,它将删除CodingKeys枚举

struct Foo: Codable {
    let bars: [Bar]

    struct Bar: Codable {
        let barBaz: String
    }
}

let fooBars = [
    "{\"bars\": [{\"barBaz\": \"abc\"}]}",
    "{\"Bars\": [{\"BarBaz\": \"abc\"}]}",
    "{\"Bars\": [{\"BarBaz\": \"abc\"},{\"BarBaz\": \"def\"}]}",
    "{\"Bars\": []}",
    "{\"bars\": []}"
]

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .lowerCamelCase
fooBars.forEach {
    let data = Data($0.utf8)
    do {
        let fooBar = try decoder.decode(Foo.self, from: data)
        print("Success:\nFound \(fooBar.bars.count) bar(s).\n")
    } catch {
        print("Fail:\n\(error)\n")
    }
}

相关问题