Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for decoupled, type-dependent node-encoding strategies #26

Closed
wants to merge 1 commit into from
Closed

Added support for decoupled, type-dependent node-encoding strategies #26

wants to merge 1 commit into from

Conversation

regexident
Copy link

@regexident regexident commented Oct 5, 2018

The current way that AttributeEncodingStrategy is implemented does not allow for compositional strategy delegation.

After a bit of a redesign XMLParsing now supports this (while remaining the option to keep doing it the current way):

struct Foo: Codable {
    let title: String

    let bar: Bar
}

struct Bar: Codable {
    let title: String

    let baz: Baz
}

struct Baz: Codable {
    let title: String
}

protocol XMLNodeEncodingStrategy {
    static func nodeEncoding(forKey codingKey: CodingKey) -> XMLEncoder.NodeEncoding
}

extension Foo: XMLNodeEncodingStrategy {
    static func nodeEncoding(forKey codingKey: CodingKey) -> XMLEncoder.NodeEncoding {
        switch codingKey.stringValue {
        case "title": return .attribute
        default: return .default
        }
    }
}

extension Baz: XMLNodeEncodingStrategy {
    static func nodeEncoding(forKey codingKey: CodingKey) -> XMLEncoder.NodeEncoding {
        switch codingKey.stringValue {
        case "title": return .attribute
        default: return .default
        }
    }
}

Note that the XMLNodeEncodingStrategy protocol here is user-specified and you're free to do things your own way (i.e. nobody forces one to add the node encoding strategy to the encodable value type itself, you're free to put it in a dedicated encoding strategy, which could then also be shared across values).

I personally prefer to have small neatly scoped encoding strategies that can easily be composed. But if one wishes to use a plain closure as strategy, there's nothing holding one up from doing so:

let nodeEncodingStrategy: (Encodable.Type, Encoder) -> ((CodingKey) -> XMLEncoder.NodeEncoding) = { codableType, encoder in
    switch codableType {
    case is Foo.Type, is Baz.Type:
        return { codingKey in
            switch codingKey.stringValue {
            case "title": return .attribute
            default: return .default
            }
        }
    default:
        return { _ in return .default }
    }
}

Either way using the above API like this …

let foo = Foo(
    title: "foo",
    bar: Bar(
        title: "bar",
        baz: Baz(
            title: "baz"
        )
    )
)

let encoder = XMLEncoder()
encoder.outputFormatting = [.prettyPrinted]
encoder.nodeEncodingStrategy = .custom { codableType, encoder in
    guard let strategy = codableType as? XMLNodeEncodingStrategy.Type else {
        return { _ in return .default }
    }
    return strategy.nodeEncoding(forKey:)
}

let encoded = try encoder.encode(foo, withRootKey: "foo")

… produces an output like this:

<foo title="foo">
    <bar>
        <title>bar</title>
        <baz title="baz" />
    </bar>
</foo>

@regexident
Copy link
Author

regexident commented Oct 5, 2018

@stonedauwg, you said in #17 that "[you] have used it [the current API] in a test app and works perfectly". Thought you might like this. 🙃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant