Да ... но это немного сложно, и, в конце концов, может быть более надежным просто добавить 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 docs .
Наконец, это все шаблонный мусор.Теперь мы можем понять суть этого.Там нет никакого способа сказать «кроме словари» напрямую.Ключи Coding интерпретируются независимо от любого фактического 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, но это полезный инструмент для панели инструментов.