This is based on initial edit and it has some redundant code, but general idea should be understandable:
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)
It has zero optionals for fields not used in the current post type, and UUIDs are typed as UUID, and URLs as URL instead of Strings everywhere.
ownerType are typed as PostOwner and ImageOwner for .post and .imageMedium for extra type safety.
EDIT: Ok, i checked edit of the question: In your json only ".post"s go into "data", and rest goes into "included". In mine answer Posts and Includeds are merged into one single type.
So it should be like this:
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 type parsing/validating could/should be added by manually coding init(from: ).