Обработка массива JSON, содержащего несколько типов-Swift 4 Декодируемый
Я пытаюсь использовать Swift 4 Decodable для анализа массива, содержащего два различных типа объектов. Данные выглядят примерно так, причем массив included является тем, который содержит как Member, так и ImageMedium объекты:
{
"data": [{
"id": "8f7cbbac-c133-4b5e-a2ec-1f32353018fa",
"type": "post",
"title": "Test Post 1",
"owner-id": "8986563c-438c-4d77-8115-9e5de2b6e477",
"owner-type": "member"
}, {
"id": "f6b3c640-a58b-449f-93c7-f6cb7b569a9c",
"type": "post",
"title": "Test Post 2",
"owner-id": "38d845a4-db66-48b9-9c15-d857166e255e",
"owner-type": "member"
}],
"included": [{
"id": "8986563c-438c-4d77-8115-9e5de2b6e477",
"type": "member",
"first-name": "John",
"last-name": "Smith"
}, {
"id": "d7218ca1-de53-4832-bb8f-dbceb6747e98",
"type": "image-medium",
"asset-url": "https://faketest.com/fake-test-1.png",
"owner-id": "f6b3c640-a58b-449f-93c7-f6cb7b569a9c",
"owner-type": "post"
}, {
"id": "c59b8c72-13fc-44fd-8ef9-4b0f8fa486a0",
"type": "image-medium",
"asset-url": "https://faketest.com/fake-test-2.png",
"owner-id": "8f7cbbac-c133-4b5e-a2ec-1f32353018fa",
"owner-type": "post"
}, {
"id": "38d845a4-db66-48b9-9c15-d857166e255e",
"type": "member",
"first-name": "Jack",
"last-name": "Doe"
}]
}
Я перепробовал кучу различных способов решить эту проблему чисто с помощью Decodable, но до сих пор единственное, что работало для меня, - это создание одной структуры для Included, которая содержит все свойства обоих объектов как опциональные, например это:
struct Root: Decodable {
let data: [Post]?
let included: [Included]?
}
struct Post: Decodable {
let id: String?
let type: String?
let title: String?
let ownerId: String?
let ownerType: String?
enum CodingKeys: String, CodingKey {
case id
case type
case title
case ownerId = "owner-id"
case ownerType = "owner-type"
}
}
struct Included: Decodable {
let id: String?
let type: String?
let assetUrl: String?
let ownerId: String?
let ownerType: String?
let firstName: String?
let lastName: String?
enum CodingKeys: String, CodingKey {
case id
case type
case assetUrl = "asset-url"
case ownerId = "owner-id"
case ownerType = "owner-type"
case firstName = "first-name"
case lastName = "last-name"
}
}
Это может работать путем реализации метода для создания Member и ImageMedium объектов из структуры Included, основанной на том, что ее свойство type, однако это явно не идеально. Я надеюсь, что есть способ выполнить это, используя обычай init(from decoder: Decoder), но я еще не получил его, чтобы работать. Есть идеи?
3 ответов:
Я выяснил, как декодировать смешанный массив
includedв два массива одного типа каждый. Использование двух Декодируемых структур проще и более универсально, чем использование одной структуры для покрытия нескольких типов данных.Вот как выглядит мое окончательное решение для всех, кто заинтересован:
struct Root: Decodable { let data: [Post]? let members: [Member] let images: [ImageMedium] init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) data = try container.decode([Post].self, forKey: .data) var includedArray = try container.nestedUnkeyedContainer(forKey: .included) var membersArray: [Member] = [] var imagesArray: [ImageMedium] = [] while !includedArray.isAtEnd { do { let memberData = try? includedArray.decode(Member.self) if let member = memberData { membersArray.append(member) } else { let imageData = try? includedArray.decode(ImageMedium.self) if let image = imageData { imagesArray.append(image) } } } } members = membersArray images = imagesArray } enum CodingKeys: String, CodingKey { case data case included } } struct Post: Decodable { let id: String? let type: String? let title: String? let ownerId: String? let ownerType: String? enum CodingKeys: String, CodingKey { case id case type case title case ownerId = "owner-id" case ownerType = "owner-type" } } struct Member: Decodable { let id: String? let type: String? let firstName: String? let lastName: String? enum CodingKeys: String, CodingKey { case id case type case firstName = "first-name" case lastName = "last-name" } } struct ImageMedium: Decodable { let id: String? let type: String? let assetUrl: String? let ownerId: String? let ownerType: String? enum CodingKeys: String, CodingKey { case id case type case assetUrl = "asset-url" case ownerId = "owner-id" case ownerType = "owner-type" } }
Я предлагаю использовать один тип
Postдля всех элементов. Чтобы различать различные типы, декодируйте ключtypeкак перечисление и декодируйте свойства в зависимости от случая., что требует объявить все неглобальные свойства как
var.struct Root : Decodable { let data : [Post] let included : [Post] } enum PostType : String, Decodable { case member, post, imageMedium = "image-medium" } struct Post : Decodable { let id: String let type: PostType var title: String? var assetUrl: String? var ownerId: String? var ownerType: String? var firstName: String? var lastName: String? enum CodingKeys: String, CodingKey { case id, type, title case assetUrl = "asset-url" case ownerId = "owner-id" case ownerType = "owner-type" case firstName = "first-name" case lastName = "last-name" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(String.self, forKey: .id) type = try container.decode(PostType.self, forKey: .type) switch type { case .member: firstName = try container.decode(String.self, forKey: .firstName) lastName = try container.decode(String.self, forKey: .lastName) case .post: title = try container.decode(String.self, forKey: .title) ownerId = try container.decode(String.self, forKey: .ownerId) ownerType = try container.decode(String.self, forKey: .ownerType) case .imageMedium: assetUrl = try container.decode(String.self, forKey: .assetUrl) ownerId = try container.decode(String.self, forKey: .ownerId) ownerType = try container.decode(String.self, forKey: .ownerType) } } }
Это основано на первоначальном редактировании и имеет некоторый избыточный код, но общая идея должна быть понятна:
enum Post: Codable { case post(id: UUID, title: String, ownerId: UUID, ownerType: PostOwner) case member(id: UUID, firstName: String, lastName: String) case imageMedium(id: UUID, assetURL: URL, ownerId: UUID, ownerType: ImageOwner) enum PostType: String, Codable { case post case member case imageMedium = "image-medium" } enum PostOwner: String, Codable { case member } enum ImageOwner: String, Codable { case post } enum CodingKeys: String, CodingKey { case id case type case title case assetUrl = "asset-url" case ownerId = "owner-id" case ownerType = "owner-type" case firstName = "first-name" case lastName = "last-name" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let id = try container.decode(UUID.self, forKey: .id) let type = try container.decode(PostType.self, forKey: .type) switch type { case .post: let title = try container.decode(String.self, forKey: .title) let ownerId = try container.decode(UUID.self, forKey: .ownerId) let ownerType = try container.decode(PostOwner.self, forKey: .ownerType) self = .post(id: id, title: title, ownerId: ownerId, ownerType: ownerType) case .member: let firstName = try container.decode(String.self, forKey: .firstName) let lastName = try container.decode(String.self, forKey: .lastName) self = .member(id: id, firstName: firstName, lastName: lastName) case .imageMedium: let assetURL = try container.decode(URL.self, forKey: .assetUrl) let ownerId = try container.decode(UUID.self, forKey: .ownerId) let ownerType = try container.decode(ImageOwner.self, forKey: .ownerType) self = .imageMedium(id: id, assetURL: assetURL, ownerId: ownerId, ownerType: ownerType) } } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .post(let id, let title, let ownerId, let ownerType): try container.encode(PostType.post, forKey: .type) try container.encode(id, forKey: .id) try container.encode(title, forKey: .title) try container.encode(ownerId, forKey: .ownerId) try container.encode(ownerType, forKey: .ownerType) case .member(let id, let firstName, let lastName): try container.encode(PostType.member, forKey: .type) try container.encode(id, forKey: .id) try container.encode(firstName, forKey: .firstName) try container.encode(lastName, forKey: .lastName) case .imageMedium(let id, let assetURL, let ownerId, let ownerType): try container.encode(PostType.imageMedium, forKey: .type) try container.encode(id, forKey: .id) try container.encode(assetURL, forKey: .assetUrl) try container.encode(ownerId, forKey: .ownerId) try container.encode(ownerType, forKey: .ownerType) } } } let jsonDecoder = JSONDecoder() let result = try jsonDecoder.decode([String: [Post]].self, from: yourJSONData) print(result)Он имеет нулевые опциональные значения для полей, не используемых в текущем типе post, и
UUIDs типизируются какUUID, аURLs какURLвместоStringS везде.
ownerTypeтипизируются какPostOwnerиImageOwnerдля.postи.imageMediumдля дополнительной безопасности типа.EDIT : Хорошо, я проверил редактирование вопроса: только в вашем json ".post "s перейти в раздел "Данные" и отдохнуть входит в "включено". В моем ответе
Posts иIncludeds объединены в один тип.Так и должно быть:
struct Post: Codable { let id: UUID let title: String let ownerId: UUID let ownerType: PostOwner enum PostOwner: String, Codable { case member } enum CodingKeys: String, CodingKey { case id case title case ownerId = "owner-id" case ownerType = "owner-type" } } enum Included: Codable { case member(id: UUID, firstName: String, lastName: String) case imageMedium(id: UUID, assetURL: URL, ownerId: UUID, ownerType: ImageOwner) enum PostType: String, Codable { case member case imageMedium = "image-medium" } enum ImageOwner: String, Codable { case post } enum CodingKeys: String, CodingKey { case id case type case title case assetUrl = "asset-url" case ownerId = "owner-id" case ownerType = "owner-type" case firstName = "first-name" case lastName = "last-name" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let id = try container.decode(UUID.self, forKey: .id) let type = try container.decode(PostType.self, forKey: .type) switch type { case .member: let firstName = try container.decode(String.self, forKey: .firstName) let lastName = try container.decode(String.self, forKey: .lastName) self = .member(id: id, firstName: firstName, lastName: lastName) case .imageMedium: let assetURL = try container.decode(URL.self, forKey: .assetUrl) let ownerId = try container.decode(UUID.self, forKey: .ownerId) let ownerType = try container.decode(ImageOwner.self, forKey: .ownerType) self = .imageMedium(id: id, assetURL: assetURL, ownerId: ownerId, ownerType: ownerType) } } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .member(let id, let firstName, let lastName): try container.encode(PostType.member, forKey: .type) try container.encode(id, forKey: .id) try container.encode(firstName, forKey: .firstName) try container.encode(lastName, forKey: .lastName) case .imageMedium(let id, let assetURL, let ownerId, let ownerType): try container.encode(PostType.imageMedium, forKey: .type) try container.encode(id, forKey: .id) try container.encode(assetURL, forKey: .assetUrl) try container.encode(ownerId, forKey: .ownerId) try container.encode(ownerType, forKey: .ownerType) } } }
Postтип parsing / validating может / должен быть добавлен вручную кодированиемinit(from: ).
Comments