Skip to content

OpenAPI.XML legacy defaults don't survive a JSON round trip #509

Description

@Joseph-Cursio

Using the attribute:/wrapped: initializer with both flags left at their defaults produces a value that doesn't survive an encode/decode round trip (seen on main):

let xml = OpenAPI.XML(name: "hello", attribute: false, wrapped: false)
let decoded = try JSONDecoder().decode(OpenAPI.XML.self, from: JSONEncoder().encode(xml))
// decoded != xml

The initializer builds .legacy(attribute: false, wrapped: false). Encoding that writes no keys, and decoding no keys lands on the (false, false, nil) case, which sets structure to nil. Since == compares structure, the decoded value is unequal to the original.

Relatedly, OpenAPI.XML(name: "hello", nodeType: nil) encodes to identical JSON but compares unequal to the value above.

Both fields default to false in the spec, so "omitted" and "present and false" describe the same document. That suggests the legacy initializer should produce nil when neither flag is set. The alternative — having the decoder reconstruct .legacy(false, false) — would just move the asymmetry onto the nodeType: nil spelling instead.

OpenAPIKit30 isn't affected, since it stores attribute/wrapped as plain Bools.

Happy to open a PR for the first approach if that's the direction you'd prefer.

Found with AI assistance (Claude Code), via a generated round-trip property test; I've reviewed the analysis and am happy to discuss it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions