Декодируемые и перечисляемые значения

Как мне поступить в ситуации, когда значение перечисления не существует при декодировании объекта? Например, как мне поступить в случае, когда «ужасно» является типом рейтинга в приведенном ниже примере? Есть ли способ установить значение по умолчанию, если ни одно из значений не существует?

public struct Review: Decodable {

    public var ratingType: RatingType?

    enum CodingKeys: String, CodingKey {
        case ratingType = "rating_type"
    }

    public init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: CodingKeys.self)
        ratingType = try container.decodeIfPresent(RatingType.self, forKey: .ratingType)
    }
}

public enum RatingType: String, Codable {
    case Good = "good"
    case Bad = "bad"
}

person Adam Cooper    schedule 09.01.2018    source источник
comment
Если значение ужасное, оно выдаст ошибку. Я полагаю, вы можете поймать это.   -  person Sweeper    schedule 09.01.2018


Ответы (2)


decodeIfPresent возвращает nil, если ключ отсутствует, и, скорее всего, выдаст ошибку, если значение недействительно. Используя try?, мы можем сделать это:

ratingType = 
    (try? (container.decodeIfPresent(RatingType.self, forKey: .ratingType) ?? <some default value>)
    ) ?? <some default value>
person Sweeper    schedule 09.01.2018
comment
Можно декодировать без переопределения требуемой инициализации с помощью метода декодера. Есть ли способ сообщить декодеру, что он будет использовать «попробовать»? вместо «попытаться», пока выполняется decodeIfPresent. В моем случае я имею дело со структурами с большим количеством свойств, довольно утомительно реализовывать требуемую инициализацию с помощью декодера и CodingKeys и сопоставлять каждое значение с каждым свойством. Итак, есть ли способ сказать JSONDecoder использовать попытку? вместо попытки декодирования из контейнера? - person Ratul Sharker; 30.04.2019

В случае, если полученный неожиданный рейтинг должен быть фактически использован (защитное кодирование от несоответствия версий между клиентом и сервером или что-то еще), вы можете попробовать другое определение перечисления:

enum RatingType
{
    case good
    case bad
    case other(String)
}

Вы теряете автоматическое кодирование, но получаете гибкость.

Возможная реализация:

import Foundation

struct Review
{
    public var rating: RatingType?

    enum CodingKeys: String, CodingKey {
        case rating = "rating_type"
    }

    public init(rating: RatingType) {
        self.rating = rating
    }
}

extension Review: Decodable
{
    public init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let label = try container.decodeIfPresent(String.self, forKey: .rating) {
            rating = RatingType(label: label)
        }
    }
}

extension Review: Encodable
{
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        if let rating = rating {
            try container.encode(rating.label, forKey: .rating)
        }
    }
}

.

enum RatingType
{
    case good
    case bad
    case other(String)

    init(label: String) {
        switch label {
            case "good": self = .good
            case "bad": self = .bad
            default: self = .other(label)
        }
    }

    var label: String {
        switch self {
            case .bad: return "bad"
            case .good: return "good"
            case let .other(label): return label
        }
    }
}

В реальной жизни вы можете вместо этого сделать RatingType соответствующим Codable/Decodable, упростив кодирование/кодирование. Не стесняйтесь изменять.

Пример выполнения кодирования/декодирования:

func encode_review(_ review: Review) throws -> String
{
    let data = try JSONEncoder().encode(review)
    return String(data: data, encoding: String.Encoding.utf8) ?? "/* ERROR */"
}

func decode_json(_ json: String) throws -> Review?
{
    guard let data = json.data(using: String.Encoding.utf8) else { return nil }
    let review = try JSONDecoder().decode(Review.self, from: data)
    return review
}

print(try! encode_review(Review(rating: .good)))  // {"rating_type":"good"}
print(try! encode_review(Review(rating: .other("horrible")))) // {"rating_type":"horrible"}

let good_review_json = """
    {"rating_type":"good"}
"""
let great_review_json = """
    {"rating_type":"great"}
"""

if let review = try! decode_json(good_review_json), let label = review.rating?.label {
    print(label) // good
}
if let review = try! decode_json(great_review_json), let label = review.rating?.label {
    print(label) // great
}
person djromero    schedule 09.01.2018