Swift - 从 json 字符串解码的枚举有一个未知的大小写,不包含在大小写中

Swift - Have an unknown case for enum decoded from json string that is not included in cases

对于给定的 JSON,如下所示:

{
    "store": {
        "animals": [
            {
                "type": "dog"
            },
            {
                "type": "cat"
            }
        ]
    }
}

我可以用 type 的枚举来解析它,如下所示:

final class AnimalStore: Decodable {
    let store: Store
}

extension AnimalStore {
    struct Store: Decodable {
        let animals: [Animal]
    }
}

extension AnimalStore.Store {
    struct Animal: Decodable {
        let type: AnimalType?
    }
}

extension AnimalStore.Store.Animal {
    enum AnimalType: String, Decodable {
        case dog = "dog"
        case cat = "cat"
        //case unknown = how such a case could be implemented?
    }
}

而且因为它是可选的;如果 json.

中缺少 type 键值对,它会正常工作

但我想有另一种情况,我们称之为 unknown 这样如果任何给定类型不是狗或猫(字符串是其他东西),类型将被初始化为未知。现在,如果给出的类型不是狗或猫,它就会崩溃。

如何使用枚举实现除给定类型之外的其他类型的初始化?

换句话说,对于给定的类型,例如:"type": "bird" 我希望将 type 初始化为 unknown

用字符串加上枚举大小写,你也可以用"unknown".

要将不匹配的字符串转换为未知数,您必须在某些时候手动实施 init(from decoder: Decoder),在您的 Animal 或 AnimalType 中。我赞成使用 AnimalType,这样您就不必手动解码 Animal 的任何其他属性。

enum AnimalType: String, Decodable {
    case dog = "dog"
    case cat = "cat"
    case unknown = "unknown"

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let string = try container.decode(String.self)
        self = AnimalType(rawValue: string) ?? .unknown
    }
}

如果您是在 Animal 中完成的,您需要类似的东西:

// Decode everything else...
type = try? decoder.decode(AnimalType.self, forKey: .type) ?? .unknown

如果你想允许一些替代你的枚举值,你可以使用这样的东西:

enum Alt<S, A> {
    case standard(S)
    case alternative(A)
}

extension Alt: Decodable where S: Decodable, A: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let standard = try? container.decode(S.self) {
            self = .standard(standard)
        } else if let alternative = try? container.decode(A.self) {
            self = .alternative(alternative)
        } else {
            throw DecodingError.typeMismatch(
                Self.self,
                DecodingError.Context(codingPath: container.codingPath, debugDescription: "")
            )
        }
    }
}

然后将 AnimalStore.Store.Animal 声明更改为:

extension AnimalStore.Store {
    struct Animal: Decodable {
        let type: Alt<AnimalType, String>?
    }
}

现在它将首先尝试将其解码为 AnimalType 类型,如果失败则将其解码为 alternative 类型。所以你可以保留不在枚举中的字符串的值。


编辑:或者在 alternativeRawValue of standard 的情况下,你可以使用这样的东西:

enum RawBox<T>: RawRepresentable where T: RawRepresentable {
    typealias RawValue = T.RawValue

    case packed(T)
    case raw(RawValue)

    init(rawValue: Self.RawValue) {
        if let packed = T(rawValue: rawValue) {
            self = .packed(packed)
        } else {
            self = .raw(rawValue)
        }
    }

    var rawValue: T.RawValue {
        switch self {
        case .packed(let packed):
            return packed.rawValue
        case .raw(let raw):
            return raw
        }
    }
}

extension RawBox: Decodable where RawValue: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let raw = try container.decode(RawValue.self)
        self.init(rawValue: raw)
    }
}

extension RawBox: Encodable where RawValue: Encodable {
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(self.rawValue)
    }
}

extension AnimalStore.Store {
    struct Animal: Decodable {
        let type: RawBox<AnimalType>?
    }
}

我想你可以试试这个

extension AnimalStore.Store {
  struct Animal: Decodable {
    let type: AnimalType?

    enum CodingKeys: String, CodingKey {
      case type
    }

    init(from decoder: Decoder) throws {
      let values = try decoder.container(keyedBy: CodingKeys.self)
      type = try? values.decode(AnimalType.self, forKey: .type) ?? .unknown
    }
  }
}

extension AnimalStore.Store.Animal {
  enum AnimalType: String {
    case dog
    case cat
    case unknown
  }
}