Обработка массива 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), но я еще не получил его, чтобы работать. Есть идеи?

591   3  

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, и UUID s типизируются как UUID, а URL s как URL вместо String S везде.

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

    Ничего не найдено.