Да ... но это немного сложно, и, в конце концов, может быть более надежным просто добавить CodingKeys. Но возможно и достойное введение в пользовательские стратегии декодирования ключей.
Во-первых, нам нужна функция для преобразования змейки. Мне действительно очень хотелось бы, чтобы это было представлено в stdlib, но это не так, и я не знаю никакого способа «добраться туда» без простого копирования кода. Итак, вот код, основанный непосредственно на JSONEncoder.swift. (Мне не нравится даже копировать это в ответ, но в противном случае вы не сможете воспроизвести остальное.)
// Makes me sad, but it's private to JSONEncoder.swift
// https://github.com/apple/swift/blob/master/stdlib/public/Darwin/Foundation/JSONEncoder.swift
func convertFromSnakeCase(_ stringKey: String) -> String {
guard !stringKey.isEmpty else { return stringKey }
// Find the first non-underscore character
guard let firstNonUnderscore = stringKey.firstIndex(where: { $0 != "_" }) else {
// Reached the end without finding an _
return stringKey
}
// Find the last non-underscore character
var lastNonUnderscore = stringKey.index(before: stringKey.endIndex)
while lastNonUnderscore > firstNonUnderscore && stringKey[lastNonUnderscore] == "_" {
stringKey.formIndex(before: &lastNonUnderscore)
}
let keyRange = firstNonUnderscore...lastNonUnderscore
let leadingUnderscoreRange = stringKey.startIndex..<firstNonUnderscore
let trailingUnderscoreRange = stringKey.index(after: lastNonUnderscore)..<stringKey.endIndex
var components = stringKey[keyRange].split(separator: "_")
let joinedString : String
if components.count == 1 {
// No underscores in key, leave the word as is - maybe already camel cased
joinedString = String(stringKey[keyRange])
} else {
joinedString = ([components[0].lowercased()] + components[1...].map { $0.capitalized }).joined()
}
// Do a cheap isEmpty check before creating and appending potentially empty strings
let result : String
if (leadingUnderscoreRange.isEmpty && trailingUnderscoreRange.isEmpty) {
result = joinedString
} else if (!leadingUnderscoreRange.isEmpty && !trailingUnderscoreRange.isEmpty) {
// Both leading and trailing underscores
result = String(stringKey[leadingUnderscoreRange]) + joinedString + String(stringKey[trailingUnderscoreRange])
} else if (!leadingUnderscoreRange.isEmpty) {
// Just leading
result = String(stringKey[leadingUnderscoreRange]) + joinedString
} else {
// Just trailing
result = joinedString + String(stringKey[trailingUnderscoreRange])
}
return result
}
Еще нам нужен небольшой швейцарский армейский нож CodingKey, который тоже должен быть в stdlib, но его нет:
struct AnyKey: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
init?(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
}
Это просто позволяет вам превратить любую строку в CodingKey. Он взят из документации JSONDecoder.
Наконец, это все банальное барахло. Теперь мы можем добраться до сути. Невозможно напрямую сказать «кроме словарей». CodingKeys интерпретируются независимо от любого фактического Decodable. Итак, что вам нужно, это функция, которая говорит «применить случай змеи, если это не ключ, вложенный в такой-то ключ». Вот функция, которая возвращает эту функцию:
func convertFromSnakeCase(exceptWithin: [String]) -> ([CodingKey]) -> CodingKey {
return { keys in
let lastKey = keys.last!
let parents = keys.dropLast().compactMap {$0.stringValue}
if parents.contains(where: { exceptWithin.contains($0) }) {
return lastKey
}
else {
return AnyKey(stringValue: convertFromSnakeCase(lastKey.stringValue))!
}
}
}
При этом нам просто нужна настраиваемая стратегия декодирования ключей (обратите внимание, что здесь используется версия userInfo в верблюжьем регистре, потому что путь CodingKey находится после применения преобразований):
decoder.keyDecodingStrategy = .custom(convertFromSnakeCase(exceptWithin: ["userInfo"]))
И результат:
User(userName: "Mark", userInfo: ["b_a1234": "value_1", "c_d5678": "value_2"])
Я не могу обещать, что это стоит проблем по сравнению с простым добавлением CodingKeys, но это полезный инструмент для набора инструментов.
person
Rob Napier
schedule
14.02.2019
.convertFromSnakeCase
является свойством декодера, а не вашихCodable
типов, я не понимаю, как это возможно. - person Joakim Danielson   schedule 14.02.2019