Допустим, у меня есть некоторый тип, который имеет представление JSON как таковое:
{
"count": 3,
"name": "Pianos",
"type": "instrument",
"id": 1,
}
Допустим, я хочу представить это как объект Swift, который выглядит так:
struct SomeObject: Codable { // this is the object I'd like to represent
let id: Int
let details: SomeDetails
}
struct SomeDetails: Codable {
let count: Int
let name: String
let type: String
}
Расшифровать этот объект не составляет труда. Но как будет работать кодирование в этом случае, чтобы я мог кодировать в плоскую структуру — ту же структуру, которую я использовал для создания этого объекта и поделился в приведенном выше примере JSON?
But how would encoding work in this instance?
Это просто работает:
struct SomeObject: Codable {
let id: Int
let details: SomeDetails
}
struct SomeDetails: Codable {
let count: Int
let name: String
let type: String
}
let someobject = SomeObject(id: 10, details: SomeDetails(count: 3, name: "ho", type: "hey"))
let json = try! JSONEncoder().encode(someobject)
Если вы настаиваете на искусственном сглаживании, просто напишите свой собственный encode(to:)
, например:
struct SomeObject: Codable {
let id: Int
let details: SomeDetails
enum Keys : String, CodingKey {
case id
case count
case name
case type
}
func encode(to enc: Encoder) throws {
var con = try enc.container(keyedBy: Keys.self)
try con.encode(id, forKey: .id)
try con.encode(details.count, forKey: .count)
try con.encode(details.name, forKey: .name)
try con.encode(details.type, forKey: .type)
}
}
struct SomeDetails: Codable {
let count: Int
let name: String
let type: String
}
let someobject = SomeObject(id: 10, details: SomeDetails(count: 3, name: "ho", type: "hey"))
let json = try! JSONEncoder().encode(someobject)
Я не знаю, что вы имеете в виду под всем этим. Структура нет «плоская». Этого не может быть. Это гнездо.
Идея состоит в том, что я хотел бы закодировать представление JSON с теми же ключами, которые я декодировал, — в приведенном выше примере будут ключи id
и details
. Я ищу json с ключами id
, count
, name
, type
. Другими словами, структура SomeDetails
— это абстракция на стороне клиента, которая не должна существовать при кодировании объекта swift обратно в его представление данных.
Тогда напишите свою encode(to:)
реализацию. Вы должны знать, как это сделать, потому что вам, вероятно, пришлось бы написать init(from:)
, чтобы превратить плоский JSON во вложенные структуры.
Абсолютно, это правильный ответ. Я испытываю мозговой пердеж в данный момент. В любом случае, я собираюсь опубликовать свой ответ на этот вопрос на случай, если кому-то еще это понадобится в будущем.
Если кто-то будет читать это в будущем (привет!), это просто вопрос шага в ваш сконструированный тип, который упаковывает значения, которые вы абстрагируете.
struct SomeDetails: Codable {
let count: Int
let name: String
let type: String
}
struct SomeObject: Codable {
let id: Int
let details: SomeDetails
enum CodingKeys: String, CodingKey {
case id
}
enum DetailKeys: String, CodingKey {
case count, name, type
}
init(from decoder: Decoder) throws {
let topLevelContainer = try decoder.container(keyedBy: CodingKeys.self)
let detailContainer = try decoder.container(keyedBy: DetailKeys.self)
id = try topLevelContainer.decode(Int.self, forKey: .id)
details = SomeDetails(
count: try detailContainer.decode(Int.self, forKey: .count),
name: try detailContainer.decode(String.self, forKey: .name),
type: try detailContainer.decode(String.self, forKey: .type))
}
func encode(to encoder: Encoder) throws {
var topLevelContainer = encoder.container(keyedBy: CodingKeys.self)
try topLevelContainer.encode(id, forKey: .id)
var detailContainer = encoder.container(keyedBy: DetailKeys.self)
try detailContainer.encode(details.count, forKey: .count)
try detailContainer.encode(details.name, forKey: .name)
try detailContainer.encode(details.type, forKey: .type)
}
}
Да, это то, что я сказал. Я думаю, что ваше использование двух ключей кодирования немного вынуждено.
Согласен, но это игрушечный пример; на самом деле это полезная нагрузка гигантский json в другом проблемном пространстве.
Улучшение предыдущих ответов. Нет необходимости вручную кодировать или декодировать другие объекты, вы можете просто перенаправить инициализаторы.
struct SomeDetails: Codable {
let count: Int
let name: String
let type: String
}
struct SomeObject: Codable {
enum CodingKeys: String, CodingKey {
case id
}
let id: Int
let details: SomeDetails
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
details = try SomeDetails(from: decoder)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try details.encode(to: encoder)
}
}
И для некоторого дополнительного сахара вы можете использовать динамический поиск членов Swift 5s, чтобы вы могли легко получить доступ к этим членам object.name
.
@dynamicMemberLookup
struct SomeObject {
...
subscript<T>(dynamicMember member: KeyPath<SomeDetails, T>) -> T {
details[keyPath: member]
}
}
Разве кодировка в этом случае не вернет содержимое
SomeDetails
под ключомdetails
? Цель состоит в том, чтобы получить плоскую структуру, точно такую же, как структура, которая пришла.