Skip to content

Nested enums coding doesn't work #122

@plushcube

Description

@plushcube

Seems like I found a bug with enums decoding.

Consider the following set of structures describing a book:

struct Book: Codable {
    let title: String
    let chapters: [Chapter]
}

enum Chapter {
    struct Content {
        let title: String
        let content: String
    }

    case intro(Content)
    case body(Content)
    case outro(Content)
}

and the example xml:

let example = """
<?xml version="1.0" encoding="UTF-8"?>
<book title="Example">
    <chapters>
        <intro title="Intro">Content of first chapter</intro>
        <chapter title="Chapter 1">Content of chapter 1</chapter>
        <chapter title="Chapter 2">Content of chapter 2</chapter>
        <outro title="Epilogue">Content of last chapter</outro>
    </chapters>
</book>
"""

let book = try XMLDecoder().decode(Book.self, from: example.data(using: .utf8)!)

I'm getting an object with only one chapter Intro:

Book(title: "Example", 
    chapters: [
        PDF.Chapter.intro(PDF.Chapter.Content(title: "Intro", content: "Content of first chapter"))
    ]
)

But if I modify my example like this:

let example2 = """
<?xml version="1.0" encoding="UTF-8"?>
<chapters>
    <intro title="Intro">Content of first chapter</intro>
    <chapter title="Chapter 1">Content of chapter 1</chapter>
    <chapter title="Chapter 2">Content of chapter 2</chapter>
    <outro title="Epilogue">Content of last chapter</outro>
</chapters>
"""

let book = try XMLDecoder().decode([Chapter].self, from: example2.data(using: .utf8)!)

the result will be a proper array of chapters:

[
PDF.Chapter.intro(PDF.Chapter.Content(title: "Intro", content: "Content of first chapter")), 
PDF.Chapter.body(PDF.Chapter.Content(title: "Chapter 1", content: "Content of chapter 1")), 
PDF.Chapter.body(PDF.Chapter.Content(title: "Chapter 2", content: "Content of chapter 2")), 
PDF.Chapter.outro(PDF.Chapter.Content(title: "Epilogue", content: "Content of last chapter"))
]

Codable extensions implemented like this:

extension Chapter: Codable {
    enum CodingKeys: String, XMLChoiceCodingKey {
        case intro, body = "chapter", outro
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let value = try? container.decode(Content.self, forKey: .body) {
            self = .body(value)
        } else if let value = try? container.decode(Content.self, forKey: .intro) {
            self = .intro(value)
        } else if let value = try? container.decode(Content.self, forKey: .outro) {
            self = .outro(value)
        } else {
            throw BookError.unknownChapterType
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        switch self {
        case .intro(let value):
            try container.encode(value, forKey: .intro)
        case .body(let value):
            try container.encode(value, forKey: .body)
        case .outro(let value):
            try container.encode(value, forKey: .outro)
        }
    }
}

extension Chapter.Content: Codable, DynamicNodeEncoding {
    enum CodingKeys: String, CodingKey {
        case title
        case value = ""
    }

    static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
        switch key {
        case CodingKeys.value:
            return .element
        default:
            return .attribute
        }
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        title = try container.decode(String.self, forKey: .title)
        content = try container.decode(String.self, forKey: .value)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(title, forKey: .title)
        try container.encode(content, forKey: .value)
    }
}

Would appreciate any help or suggestion in resolving this issue.

Metadata

Metadata

Assignees

Labels

questionFurther information is requested

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions