次のようにenum
を定義しました:
enum Type: String, Codable {
case text = "text"
case image = "image"
case document = "document"
case profile = "profile"
case sign = "sign"
case inputDate = "input_date"
case inputText = "input_text"
case inputNumber = "input_number"
case inputOption = "input_option"
case unknown
}
jSON文字列プロパティをマップします。自動シリアル化と逆シリアル化は正常に機能しますが、別の文字列に遭遇すると逆シリアル化が失敗することがわかりました。
他の利用可能なケースをマップするunknown
ケースを定義することは可能ですか?
このデータは、将来変更される可能性のあるRESTFul APIから取得されるため、これは非常に便利です。
Codable
Typeを拡張し、失敗した場合にデフォルト値を割り当てることができます。
enum Type: String {
case text,
image,
document,
profile,
sign,
inputDate = "input_date",
inputText = "input_text" ,
inputNumber = "input_number",
inputOption = "input_option",
unknown
}
extension Type: Codable {
public init(from decoder: Decoder) throws {
self = try Type(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown
}
}
編集/更新:
Swift 5以降CaseIterable
文字列列挙の最後のケースをデフォルトとするプロトコルを作成できます。
protocol CaseIterableDefaultsLast: Codable & CaseIterable & RawRepresentable
where Self.RawValue == String, Self.AllCases: BidirectionalCollection { }
extension CaseIterableDefaultsLast {
init(from decoder: Decoder) throws {
self = try Self(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? Self.allCases.last!
}
}
遊び場のテスト:
enum Type: String, CaseIterableDefaultsLast {
case text, image, document, profile, sign, inputDate = "input_date", inputText = "input_text" , inputNumber = "input_number", inputOption = "input_option", unknown
}
let types = try! JSONDecoder().decode([Type].self , from: Data(#"["text","image","sound"]"#.utf8)) // [text, image, unknown]
Type
の生の型を削除して、関連付けられた値を処理するunknownケースを作成できます。しかし、これにはコストがかかります。あなたはどういうわけかあなたのケースの生の値が必要です。 this および this SOの回答から着想を得た。あなたの問題に対するこのエレガントな解決策を思いついた。
raw値を保存できるように、別の列挙型を維持しますが、プライベートとして:
enum Type {
case text
case image
case document
case profile
case sign
case inputDate
case inputText
case inputNumber
case inputOption
case unknown(String)
// Make this private
private enum RawValues: String, Codable {
case text = "text"
case image = "image"
case document = "document"
case profile = "profile"
case sign = "sign"
case inputDate = "input_date"
case inputText = "input_text"
case inputNumber = "input_number"
case inputOption = "input_option"
// No such case here for the unknowns
}
}
encoding
およびdecoding
部分を拡張機能に移動します。
extension Type: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
// As you already know your RawValues is String actually, you decode String here
let stringForRawValues = try container.decode(String.self)
// This is the trick here...
switch stringForRawValues {
// Now You can switch over this String with cases from RawValues since it is String
case RawValues.text.rawValue:
self = .text
case RawValues.image.rawValue:
self = .image
case RawValues.document.rawValue:
self = .document
case RawValues.profile.rawValue:
self = .profile
case RawValues.sign.rawValue:
self = .sign
case RawValues.inputDate.rawValue:
self = .inputDate
case RawValues.inputText.rawValue:
self = .inputText
case RawValues.inputNumber.rawValue:
self = .inputNumber
case RawValues.inputOption.rawValue:
self = .inputOption
// Now handle all unknown types. You just pass the String to Type's unknown case.
// And this is true for every other unknowns that aren't defined in your RawValues
default:
self = .unknown(stringForRawValues)
}
}
}
extension Type: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .text:
try container.encode(RawValues.text)
case .image:
try container.encode(RawValues.image)
case .document:
try container.encode(RawValues.document)
case .profile:
try container.encode(RawValues.profile)
case .sign:
try container.encode(RawValues.sign)
case .inputDate:
try container.encode(RawValues.inputDate)
case .inputText:
try container.encode(RawValues.inputText)
case .inputNumber:
try container.encode(RawValues.inputNumber)
case .inputOption:
try container.encode(RawValues.inputOption)
case .unknown(let string):
// You get the actual String here from the associated value and just encode it
try container.encode(string)
}
}
}
次のようにコンテナ構造にラップしました(JSONEncoder/JSONDecoderを使用するため)。
struct Root: Codable {
let type: Type
}
不明なケース以外の値の場合:
let rootObject = Root(type: Type.document)
do {
let encodedRoot = try JSONEncoder().encode(rootObject)
do {
let decodedRoot = try JSONDecoder().decode(Root.self, from: encodedRoot)
print(decodedRoot.type) // document
} catch {
print(error)
}
} catch {
print(error)
}
大文字と小文字が不明な値の場合:
let rootObject = Root(type: Type.unknown("new type"))
do {
let encodedRoot = try JSONEncoder().encode(rootObject)
do {
let decodedRoot = try JSONDecoder().decode(Root.self, from: encodedRoot)
print(decodedRoot.type) // unknown("new type")
} catch {
print(error)
}
} catch {
print(error)
}
この例では、ローカルオブジェクトを使用しています。 REST APIレスポンスで試すことができます。
nayem の答えに基づいた代替案は、内部RawValues
初期化のオプションのバインディングを使用することにより、わずかに合理化された構文を提供します。
_enum MyEnum: Codable {
case a, b, c
case other(name: String)
private enum RawValue: String, Codable {
case a = "a"
case b = "b"
case c = "c"
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let decodedString = try container.decode(String.self)
if let value = RawValue(rawValue: decodedString) {
switch value {
case .a:
self = .a
case .b:
self = .b
case .c:
self = .c
}
} else {
self = .other(name: decodedString)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .a:
try container.encode(RawValue.a)
case .b:
try container.encode(RawValue.b)
case .c:
try container.encode(RawValue.c)
case .other(let name):
try container.encode(name)
}
}
}
_
既存の列挙型のすべてのケース名が、それらが表す基になる文字列値と一致することが確実な場合、RawValue
を合理化して次のことができます。
_private enum RawValue: String, Codable {
case a, b, c
}
_
...およびencode(to:)
から:
_func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
if let rawValue = RawValue(rawValue: String(describing: self)) {
try container.encode(rawValue)
} else if case .other(let name) = self {
try container.encode(name)
}
}
_
これを使用する実用的な例を次に示します。たとえば、enumとしてモデル化するプロパティを持つSomeValue
をモデル化します。
_struct SomeValue: Codable {
enum MyEnum: Codable {
case a, b, c
case other(name: String)
private enum RawValue: String, Codable {
case a = "a"
case b = "b"
case c = "letter_c"
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let decodedString = try container.decode(String.self)
if let value = RawValue(rawValue: decodedString) {
switch value {
case .a:
self = .a
case .b:
self = .b
case .c:
self = .c
}
} else {
self = .other(name: decodedString)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .a:
try container.encode(RawValue.a)
case .b:
try container.encode(RawValue.b)
case .c:
try container.encode(RawValue.c)
case .other(let name):
try container.encode(name)
}
}
}
}
let jsonData = """
[
{ "value": "a" },
{ "value": "letter_c" },
{ "value": "c" },
{ "value": "Other value" }
]
""".data(using: .utf8)!
let decoder = JSONDecoder()
if let values = try? decoder.decode([SomeValue].self, from: jsonData) {
values.forEach { print($0.value) }
let encoder = JSONEncoder()
if let encodedJson = try? encoder.encode(values) {
print(String(data: encodedJson, encoding: .utf8)!)
}
}
/* Prints:
a
c
other(name: "c")
other(name: "Other value")
[{"value":"a"},{"value":"letter_c"},{"value":"c"},{"value":"Other value"}]
*/
_
この拡張機能を追加してYourEnumName
を設定します。
extension <#YourEnumName#>: Codable {
public init(from decoder: Decoder) throws {
self = try <#YourEnumName#>(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown
}
}
init(from decoder: Decoder) throws
初期化子を実装し、有効な値を確認する必要があります。
struct SomeStruct: Codable {
enum SomeType: String, Codable {
case text
case image
case document
case profile
case sign
case inputDate = "input_date"
case inputText = "input_text"
case inputNumber = "input_number"
case inputOption = "input_option"
case unknown
}
var someType: SomeType
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
someType = (try? values.decode(SomeType.self, forKey: .someType)) ?? .unknown
}
}
@LeoDabus回答ありがとうございます。私はそれらを少し修正して、私にとってはうまくいくと思われる文字列列挙のプロトコルを作成しました:
protocol CodableWithUnknown: Codable {}
extension CodableWithUnknown where Self: RawRepresentable, Self.RawValue == String {
init(from decoder: Decoder) throws {
do {
try self = Self(rawValue: decoder.singleValueContainer().decode(RawValue.self))!
} catch {
if let unknown = Self(rawValue: "unknown") {
self = unknown
} else {
throw error
}
}
}
}