Swift: константа в определении шаблона

Я работаю с внутренним разработчиком, который любит инкапсулировать тела json в другой объект, такой как данные:

Пример:

ПОЛУЧИТЬ: / пользователь / текущий:

{
  data: {
          firstName: "Evan",
          lastName: "Stoddard"
        }
}

Я просто хотел бы просто вызвать json decode в ответе, чтобы получить созданную мной структуру User, но для добавленного объекта данных требуется другая структура. Чтобы обойти это, я создал общий шаблонный класс:

struct DecodableData<DecodableType:Decodable>:Decodable {

    var data:DecodableType

}

Теперь я могу получить полезную нагрузку json, и если я хочу получить структуру User, просто получите свойство data моего шаблона:

let user = JSONDecoder().decode(DecodableData<User>.self, from: jsonData).data

Это все нормально и изящно, пока иногда ключ data не всегда data.

Мне кажется, что это, скорее всего, довольно тривиальная вещь, но есть ли способ добавить параметр в определение моего шаблона, чтобы я мог изменить ключи кодирования перечисления, поскольку этот ключ данных может измениться?

Что-то вроде следующего?

struct DecodableData<DecodableType:Decodable, Key:String>:Decodable {

    enum CodingKeys: String, CodingKey {
        case data = Key
    }

    var data:DecodableType

}

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


person evan.stoddard    schedule 20.05.2019    source источник
comment
Обратите внимание на полученную ошибку: Исходное значение для enum case должно быть буквальным.   -  person vadian    schedule 20.05.2019
comment
Всегда ли вы знаете во время компиляции, какой будет ключ, или вы узнаете только во время выполнения?   -  person rob mayoff    schedule 20.05.2019
comment
@vadian Я знаю об ошибке, поэтому и задал вопрос.   -  person evan.stoddard    schedule 21.05.2019
comment
@robmayoff я бы знал во время компиляции   -  person evan.stoddard    schedule 21.05.2019


Ответы (2)


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

struct Container<T>: Decodable where T: Decodable {
    let value: T

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let dict = try container.decode([String: T].self)

        guard dict.count == 1 else {
            throw DecodingError.dataCorruptedError(in: container, debugDescription: "expected exactly 1 key value pair, got \(dict.count)")
        }

        value = dict.first!.value
    }
}

Если JSON пуст или содержит более одной пары "ключ-значение", возникает исключение.

Предполагая простую структуру, такую ​​как

struct Foo: Decodable, Equatable {
    let a: Int
}

вы можете разобрать его независимо от ключа:

let foo1 = try! JSONDecoder().decode(
    Container<Foo>.self,
    from: #"{ "data": { "a": 1 } }"#.data(using: .utf8)!
).value

let foo2 = try! JSONDecoder().decode(
    Container<Foo>.self,
    from: #"{ "doesn't matter at all": { "a": 1 } }"#.data(using: .utf8)!
).value

foo1 == foo2 // true

Это также работает для ответов JSON, которые имеют null в качестве значения, и в этом случае вам нужно проанализировать его как необязательный для вашего типа:

let foo = try! JSONDecoder().decode(
    Container<Foo?>.self,
    from: #"{ "data": null }"#.data(using: .utf8)!
).value // nil
person fphilipe    schedule 20.05.2019
comment
Что, если его оболочка структуры получит другое свойство? - person iDevid; 21.05.2019
comment
@iDevid, это совсем другая проблема. Если у вас есть более одного свойства, ключ больше не имеет значения, и в этом случае типичный struct с соответствующими ключами будет правильным решением. Кстати, вы проголосовали против меня, чтобы продвинуть свой ответ? - person fphilipe; 21.05.2019
comment
Нет, потому что проблема заключалась в том, что я чувствую, что это, скорее всего, довольно тривиальный материал, но есть ли способ добавить параметр в определение моего шаблона, чтобы я мог изменить ключи кодирования перечисления, поскольку этот ключ данных может измениться? Я думаю, что ваше решение работает хорошо, если оболочка содержит одно свойство, вместо этого, если оно стало больше (с ошибками и статусами), оно вообще не работает. - person iDevid; 21.05.2019

Попробуйте что-то вроде этого:

struct GenericCodingKey: CodingKey {
    var stringValue: String

    init(value: String) {
        self.stringValue = value
    }

    init?(stringValue: String) {
        self.stringValue = stringValue
    }

    var intValue: Int?

    init?(intValue: Int) {
        return nil
    }
}

struct DecodableData<DecodableType: CustomDecodable>: Decodable {

    var data: DecodableType

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: GenericCodingKey.self)
        data = try container.decode(DecodableType.self, forKey: GenericCodingKey(value: DecodableType.dataKey))
    }
}

protocol CustomDecodable: Decodable {
    static var dataKey: String { get }
}

extension CustomDecodable {
    static var dataKey: String {
        return "data" // This is your default
    }
}


struct CustomDataKeyStruct: CustomDecodable {
    static var dataKey: String = "different"
}

struct NormalDataKeyStruct: CustomDecodable {
    //Change Nothing
}
person iDevid    schedule 20.05.2019