【问题标题】:Swift - Have an unknown case for enum decoded from json string that is not included in cases [duplicate]Swift - 有一个从 json 字符串解码的枚举的未知案例,该案例不包含在案例中[重复]
【发布时间】:2019-10-09 11:17:38
【问题描述】:

对于给定的 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

【问题讨论】:

  • 我搜索了这样一个问题但找不到,感谢指出重复@LeoDabus

标签: json swift enums decodable


【解决方案1】:

用字符串添加枚举大小写,你也可以使用"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

【讨论】:

  • 直接在AnimalType中实现,工作正常。非常感谢!
【解决方案2】:

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

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 类型。所以你可以保持字符串的值不在你的枚举中。


编辑:或者在alternativeRawValuestandard 的情况下,您可以使用这样的东西:

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

【讨论】:

    【解决方案3】:

    我觉得你可以试试这个

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-26
      • 2015-11-16
      • 1970-01-01
      • 2018-12-14
      • 2017-12-09
      相关资源
      最近更新 更多