【问题标题】:Custom intialiser on Primitive types for JSONDecoderJSONDecoder 的原始类型的自定义初始化程序
【发布时间】:2020-03-29 18:09:43
【问题描述】:

如何为 primitive 类型(如 Int、Bool)自定义 JSONDecoder 的行为?

问题来了:

  • 类型不能依赖后端。例如:Bool 可以是 true/false 或 "true"/"false"(bool 可以用双引号括起来)

  • 我们至少有 300 个 Codable 结构,其中平均有 15 个属性,编写解码逻辑很麻烦。此外,逻辑或多或少保持不变,因此代码变得重复

因此我正在寻找一种解决方案,如果有 Type mismatch 原始类型应该能够处理它,如果没有,那么它应该设置为 nil,前提是该类型是可选的。


为此我尝试了多种方法

1。在所有原始类型上使用 Wrapper 并处理解码逻辑。以下是 Bool 上的包装器示例

struct Bool1: Codable{
    private var bool: Bool?

    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let b = try? container.decode(Bool.self) {
            self.bool = b
        } else if let string = try? container.decode(String.self) {
            if string.lowercased() == "true" {
                self.bool = true
            } else if string.lowercased() == "false" {
                self.bool = false
            } else {
                throw Error()
            }
        }
    }
}

但这在其他开发人员中造成了不必要的混淆,因为 Wrapped 类型不像 Native 类型那样自然地出现。此外,无法直接访问该值(它总是需要 xyz.bool)来提取原始值

2。创建一个继承自 Decodable 和子类 JSONDecoder 的新协议

protocol KKDecodable: Decodable {
    init(decoder1: Decoder)
}

extension Bool: KKDecodable {
    init(decoder1: Decoder) {
     // Logic for creating Bool from different types
    }
}

class JSONDecoder1: JSONDecoder {
    func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : KKDecodable {
         // Some code that would invoke `init(decoder1: Decoder)`
         // which is defined in `KKDecodable`
    }
}

我无法使用这种方法编写工作代码

【问题讨论】:

  • 这个怎么样? stackoverflow.com/a/46850257/562298(为您的自定义类型创建一个自定义包装器,然后添加一个调用 xyz.bool 的计算属性,因此开发人员无需混淆。)
  • 是的。这就是我现在实施的解决方案。但它有一些缺点。 1. 所有的开发者都需要使用封装类型,看起来不直观 2. 所有的变量都需要额外的函数来提取实际值。
  • 类型不能依赖后端。 为什么?这是荒谬的。出于性能原因,后端应该做出努力,而不是前端。
  • 我想JSONSerialization 在你的情况下不起作用?

标签: swift jsondecoder


【解决方案1】:

属性包装器

您可以使用属性包装器。想象一下这个例子:

@propertyWrapper
struct SomeKindOfBool: Decodable {
    var wrappedValue: Bool?
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let stringifiedValue = try? container.decode(String.self) {
            switch stringifiedValue.lowercased() {
            case "false": wrappedValue = false
            case "true": wrappedValue = true
            default: wrappedValue = nil
            }
        } else {
            wrappedValue = try? container.decode(Bool.self)
        }
    }
}

用法

struct MyType: Decodable {
    @SomeKindOfBool var someKey: Bool?
}

您现在可以像普通的Bool 一样使用someKey 值:

测试:

let jsonData = """
[
 { "someKey": "something else" },
 { "someKey": "true" },
 { "someKey": true }
]
""".data(using: .utf8)!

let decodedJSON = try! JSONDecoder().decode([MyType].self, from: jsonData)

for decodedType in decodedJSON {
    print(decodedType.someKey)
}

结果:

可选(真)

可选(真)


您可以对其他情况以及您需要的任何其他类型执行类似操作。另请注意我已更改代码以满足您的需求,但您可以使用我发布为gist here in GitHub的更兼容版本

【讨论】:

  • 嗯,这个解决方案似乎不适用于不存在的密钥。我想将这些键解码为零。使用属性包装器时,会抛出 Swift.DecodingError.keyNotFound
  • 是的,您也应该更改支持选项的代码。我有,但不是要点。您可以提出问题,以便社区和我可以在那里回答该特定问题。
  • 谢谢,我发布了一个新问题:stackoverflow.com/q/69978668/235297
猜你喜欢
  • 2017-11-16
  • 1970-01-01
  • 2012-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-19
相关资源
最近更新 更多