Быстрый пользовательский декодируемый инициализатор без CodingKeys - PullRequest
1 голос
/ 15 апреля 2020

Допустим, у меня есть следующая декодируемая структура в качестве примера, иллюстрирующая, что я пытаюсь сделать:

struct Object: Decodable {
    var id: String
    var name: String
}

и это JSON:

[
    {
        "id": "a",
        "name": "test"
    },
    {
        "id": "b",
        "name": null
    }
]

Обратите внимание, что Свойство name иногда может быть null. В основном это работает нормально, так как ключи json соответствуют именам свойств структуры, поэтому мне не нужно перечисление CodingKey, но иногда свойство name может быть нулевым. Однако вместо того, чтобы сделать name необязательным, я хочу заменить строку по умолчанию, поэтому мне нужен собственный инициализатор:

struct Object: Decodable {
    var id: String
    var name: String

    init(from decoder: Decoder) throws {
        ...
        self.name = <value from decoder> ?? "default name"
        ...
    }
}

Но для этого требуется объект CodingKey. Я использую ключи по умолчанию. Нужно ли мне перечисление CodingKey сейчас? Даже если все мои ключи совпадают? Или есть способ иметь собственный инициализатор Decodable, использующий только те ключи, которые есть?

Может быть, я могу использовать какой-то контейнер по умолчанию?

let container = try decoder.container(keyedBy: <defaultContainer???>)

Я пытался используя оба эти варианта, но ни один из них не работал:

let container = try decoder.singleValueContainer ()

let container = try decoder.unkeyedContainer ()

Как мне получить пользовательский Decodable инициализатор , но также использовать ключи по умолчанию?

Ответы [ 4 ]

2 голосов
/ 15 апреля 2020

Автогенерация для CodingKeys действительно странная. Объем и доступность этого зависит от того, какие члены у вас есть.

Скажем, у вас просто есть Decodable. Они компилируются:

struct Decodable: Swift.Decodable {
  static var codingKeysType: CodingKeys.Type { CodingKeys.self }
}
struct Decodable: Swift.Decodable {
  static func `init`(from decoder: Decoder) throws -> Self {
    _ = CodingKeys.self
    return self.init()
  }
}

… и вы можете собрать их вместе, если добавите private.

struct Decodable: Swift.Decodable {
  private static var codingKeysType: CodingKeys.Type { CodingKeys.self }

  static func `init`(from decoder: Decoder) throws -> Self {
    _ = CodingKeys.self
    return self.init()
  }
}

… Но сделайте это func инициализатором, и опять же, без компиляции.

struct Decodable: Swift.Decodable {
  private static var codingKeysType: CodingKeys.Type { CodingKeys.self }

  init(from decoder: Decoder) throws {
    _ = CodingKeys.self
  }
}

Вы можете изменить его на полностью Codable, а не просто Decodable

struct Decodable: Codable {
  private static var codingKeysType: CodingKeys.Type { CodingKeys.self }

  init(from decoder: Decoder) throws {
    _ = CodingKeys.self
  }
}

Но тогда вы не сможете использовать CodingKeys в области видимости, чтобы свойство не компилировалось.

Учитывая, что вам, вероятно, не нужно такое свойство, просто используйте Codable, сообщите об ошибке Apple, ссылаясь на этот ответ, и, надеюсь, мы все сможем переключиться до Decodable, когда они это исправят. ?

1 голос
/ 15 апреля 2020

Вы можете эмулировать поведение, используя оболочки свойств, но это не идеальное решение, и оно немного хакерское. Эта проблема уже обсуждалась на форумах Swift уже несколько раз.

С оболочкой свойства:

struct Object: Decodable {
    var id: String
    @DecodableDefault var name: String
}

Код для оболочки свойства:

public protocol DecodableDefaultValue: Decodable {
    static var defaultDecodableValue: Self { get }
}

@propertyWrapper
public struct DecodableDefault<T: Decodable>: Decodable {
    public var wrappedValue: T

    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        wrappedValue = try container.decode(T.self)
    }

    public init(wrappedValue: T) {
        self.wrappedValue = wrappedValue
    }
}

extension DecodableDefault: Encodable where T: Encodable {
    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(wrappedValue)
    }
}

extension DecodableDefault: Equatable where T: Equatable { }
extension DecodableDefault: Hashable where T: Hashable { }

public extension KeyedDecodingContainer {
    func decode<T: DecodableDefaultValue>(_: DecodableDefault<T>.Type, forKey key: Key) throws -> DecodableDefault<T> {
        guard let value = try decodeIfPresent(DecodableDefault<T>.self, forKey: key) else {
            return DecodableDefault(wrappedValue: T.defaultDecodableValue)
        }

        return value
    }
}

extension Array: DecodableDefaultValue where Element: Decodable {
    public static var defaultDecodableValue: [Element] {
        return []
    }
}

extension Dictionary: DecodableDefaultValue where Key: Decodable, Value: Decodable {
    public static var defaultDecodableValue: [Key: Value] {
        return [:]
    }
}

extension String: DecodableDefaultValue {
    public static let defaultDecodableValue: String = ""
}

extension Int: DecodableDefaultValue {
    public static let defaultDecodableValue: Int = 0
}

Чтобы отобразить список несколько проблем:

  • вы не можете выбрать значение по умолчанию (может быть сделано по-другому, но это сложнее)
  • , если вы хотите использовать let, вам нужна отдельная неизменяемая оболочка.
1 голос
/ 15 апреля 2020

Проблема в том, что CodingKeys генерируется автоматически только в том случае, если вы не полностью соответствовали соответствующим протоколам вручную. (Это очень хорошо известно для разработчиков Objective- C, где поддерживающий свойство ivar не будет автоматически синтезироваться, если вы вручную реализуете все соответствующие методы доступа.)

Итак, в следующем сценарии ios, CodingKeys не создается автоматически для вас:

  1. Вы принимаете только Decodable и внедрили свой собственный init(from:);

  2. Вы принять только Encodable и внедрить собственный encode(to:); или

  3. Вы используете Encodable и Decodable (или просто Codable) и внедрили свои собственные init(from:) и encode(to:).

Таким образом, ваше дело относится к первому сценарию, описанному выше.

Было предложено, что вы можете обойти свою головоломку, приняв Codable, даже если вы реализовали только init(from:) и по-видимому, не планируйте когда-либо использовать поведение Encodable. По сути, это основано на побочном эффекте протокола, который вы не планируете реально использовать.

Это не имеет большого значения, и принятие Codable работает, но его можно считать будьте «правильнее» к go впереди и определите свой CodingKeys, а не полагайтесь на неосуществленный Encodable побочный эффект:

struct Object: Decodable {
    var id: String
    var name: String

    enum CodingKeys: String, CodingKey {
        case id, name
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        id = try container.decode(String.self, forKey: .id)
        name = (try? container.decode(String.self, forKey: .name)) ?? "Default Value"
    }
}
0 голосов
/ 15 апреля 2020

В комментарии к вашему вопросу говорится, что компилятор генерирует для вас объект CodingKeys. Вы можете реализовать пользовательский enum, когда ключи не соответствуют именам в вашем перечислении или классе относительно JSON данных, которые вы получаете от источника.

Вы можете реализовать свой объект следующим образом:

struct TestObject: Codable {
    var id: String
    var name: String

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        let id = try container.decode(String.self, forKey: .id)
        let nameOrNil = try? container.decode(String.self, forKey: .name)

        self.id = id
        self.name = nameOrNil ?? "Default value"
    }
}

Метод container *1008* может выдавать и выдавать ошибку, но если вы реализуете реализацию, чтобы вывести ошибку и вместо этого возвращать необязательное значение с помощью try?, вы можете применить оператор слияния nil, чтобы назначить значение по умолчанию. значение всякий раз, когда имя не включено в JSON.

Доказательство здесь:

let json = """
[
    {
        "id": "a",
        "name": "test"
    },
    {
        "id": "b",
        "name": null
    }
]
""".data(using: .utf8)!
let decodedArray = try JSONDecoder().decode([TestObject].self, from: json)
print(decodedArray)

Ссылки:

...