Переопределить декодируемый для определенного типа - PullRequest
2 голосов
/ 08 мая 2019

Есть ли способ переопределить реализацию Decodable для определенного типа, чтобы он использовал, что тип является частью Codable struct?

В частности, у меня есть эта структура:

struct Activity: Codable {
    let id: Int
    let name: String
<...>
    let distance: Measurement<UnitLength>
}

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


extension Measurement where UnitType == UnitLength {
    public init(from decoder: Decoder) throws {
        self = Measurement(value: try decoder.singleValueContainer().decode(Double.self), unit: UnitLength.meters)
    }
}

Так что я могу преобразовать Double значение в Measurement<UnitLength> при декодировании, без необходимости предоставлять пользовательский init(from decoder: Decoder) для каждого struct, в котором есть Measurement.

То, что init компилируется нормально, но, похоже, не вызывается из стандартного процесса декодирования Codable.

Моя временная реализация - это init для структуры Activity:

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

<...>
    distance = Measurement(value: try values.decode(Double.self, forKey: .distance), unit: UnitLength.meters)
}

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

1 Ответ

1 голос
/ 08 мая 2019

Одним из решений, которое может удовлетворить ваши потребности, является определение собственной структуры измерения, которая предполагает измерение длины в метрах.

struct MyMeasurement {
    let length: Measurement<UnitLength>
}

extension MyMeasurement: Codable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        length = Measurement(value: try container.decode(Double.self), unit: UnitLength.meters)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(length.value)
    }
}

Затем обновите структуру Activity, чтобы использовать MyMeasurement:

struct Activity: Codable {
    let id: Int
    let name: String
<...>
    let distance: MyMeasurement
}

Тогда вы можете нормально декодировать:

let decoder = JSONDecoder()
do {
    let activity = try decoder.decode(Activity.self, from: someJSONData)
    print(activity.distance.length)
} catch {
    print(error)
}

Возможно, вам нужно более подходящее имя для MyMeasurement, которое намекает на то, что это длина в метрах.

Вы можете повторно использовать MyMeasurement в любой структуре, имеющей некоторое свойство длины в метрах, без необходимости писать пользовательский init(from:) для этой структуры.

...