#ios #json #swift
Вопрос:
В настоящее время у меня есть следующие 2 строки JSON — unoptimized_json
и optimized_json
.
let unoptimized_json = "[{"id":1,"text":"hello","checked":true}]"
let optimized_json = "[{"i":1,"t":"hello","c":true}]"
Я хотел бы декодировать их в один и тот же объект структуры.
struct Checklist: Hashable, Codable {
let id: Int64
var text: String?
var checked: Bool
enum CodingKeys: String, CodingKey {
case id = "i"
case text = "t"
case checked = "c"
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
static func == (lhs: Checklist, rhs: Checklist) -> Bool {
return lhs.id == rhs.id
}
}
Однако текущий дизайн может принимать только формат optimized_json
, а не unoptimized_json
.
В Java Android я могу добиться этого с помощью alternate
.
import com.google.gson.annotations.SerializedName;
public class Checklist {
@SerializedName(value="i", alternate="id")
private final long id;
@SerializedName(value="t", alternate="text")
private String text;
@SerializedName(value="c", alternate="checked")
private boolean checked;
}
Мне было интересно, в Swift есть ли у нас эквивалентная функция для достижения этой цели?
Возможно ли, чтобы строка JSON с другим именем поля была декодирована в один и тот же объект структуры?
Комментарии:
1. Знаете ли вы, какой из них вы получаете раньше, например, другую конечную точку или по какому-либо параметру в запросе?
2. Не совсем, так как данные генерируются сторонним приложением. Это может быть любой из форматов.
3. Это оптимизация по пирру . Усилия по обработке различных форматов намного больше, чем польза от нескольких сохраненных символов.
4. @vadian в зависимости от загрузки сервера любое сокращение объема отправляемых данных может оказать положительное влияние. Мы говорим здесь не только о фактической длине json, но также о форматировании json и других процессах, таких как сжатие полезной нагрузки, которые складываются.
Ответ №1:
Невозможно сделать это автоматически, но вы можете реализовать свою собственную логику декодирования и попробовать использовать там разные значения.
struct User: Decodable {
enum CodingKeys: String, CodingKey {
case i, id
}
let id: Int
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let i = try container.decodeIfPresent(Int.self, forKey: .i)
let id = try container.decodeIfPresent(Int.self, forKey: .id)
guard let identifier = i ?? id else { throw NoIDFoundError }
self.id = identifier
}
}
Ответ №2:
Я бы предложил использовать обычай keyDecodingStrategy
при декодировании и позволить этой стратегии возвращать правильный ключ. Это решение предполагает, что имена оптимизированных ключей всегда являются первой буквой(буквами) обычного ключа. Его можно использовать и в других случаях, но тогда требуется более жестко запрограммированное решение.
Сначала мы используем наш собственный тип для ключей, интересная часть заключается в init(stringValue: String)
том, где мы используем CodingKeys
перечисление (см. Ниже) CheckList
, чтобы получить правильный ключ
struct VaryingKey: CodingKey {
var intValue: Int?
init?(intValue: Int) {
self.intValue = intValue
stringValue = "(intValue)"
}
var stringValue: String
init(stringValue: String) {
self.stringValue = Checklist.CodingKeys.allCases
.first(where: { stringValue == $0.stringValue.prefix(stringValue.count) })?
.stringValue ?? stringValue
}
}
Нам нужно определить CodingKeys
перечисление для контрольного списка и привести его в соответствие с CaseIterable
приведенным выше кодом для работы
enum CodingKeys: String, CodingKey, CaseIterable {
case id
case text
case type
case checked
}
и затем мы используем это при декодировании
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ codingPath in
let key = codingPath.last!.stringValue
return VaryingKey(stringValue: key)
})
do {
let unoptimized = try decoder.decode([Checklist].self, from: unoptimized_json.data(using: .utf8)!)
let optimized = try decoder.decode([Checklist].self, from: optimized_json.data(using: .utf8)!)
print(unoptimized)
print(optimized)
} catch {
print(error)
}
[__lldb_expr_377.Контрольный список(идентификатор: 1, текст: Необязательно(«привет»), тип: «мир», проверено: верно)]
[__lldb_expr_377.Контрольный список(идентификатор: 1, текст: Необязательно(«привет»), тип: «мир», проверено: верно)]
(Дополнительный атрибут «тип» использовался только для тестирования решения)
Обновить
Я упоминал выше, что если короткая версия ключа json не могла быть динамически вычтена из обычного ключа, то требовалось более жестко закодированное решение, поэтому для полноты картины вот пример такого решения с новым init(stringValue:)
в VaryingKey
init(stringValue: String) {
if let codingKey = Checklist.CodingKeys(stringValue: stringValue) {
print(codingKey)
self.stringValue = stringValue
} else {
switch stringValue {
case "i":
self.stringValue = Checklist.CodingKeys.id.stringValue
case "t":
self.stringValue = Checklist.CodingKeys.text.stringValue
case "ty":
self.stringValue = Checklist.CodingKeys.type.stringValue
case "c":
self.stringValue = Checklist.CodingKeys.checked.stringValue
default:
fatalError()
}
}
}