swift-openapi-generator: Swift OpenAPI Generator Fails to Decode anyOf Structure for nullable Field

When using the Swift OpenAPI Generator and runtime to decode a JSON payload from an API endpoint, a decoding error occurs for a field defined with the anyOf keyword in the OpenAPI spec. The error message is “The anyOf structure did not decode into any child schema.”

Here is a simplified snippet of the OpenAPI spec that defines the response schema:

"ResponseSchema": {
  "properties": {
    "status": { "type": "string", "title": "Status" },
    "timestampUTC": {
      "type": "string",
      "format": "date-time",
      "title": "Timestamputc"
    },
    "errorCode": {
      "anyOf": [{ "type": "integer" }, { "type": "null" }],
      "title": "Errorcode"
    },
    "errorMessage": {
      "anyOf": [{ "type": "string" }, { "type": "null" }],
      "title": "Errormessage"
    },
    "data": {
      "type": "object"
    }
  },
  "type": "object",
  "required": [
    "status",
    "timestampUTC",
    "errorCode",
    "errorMessage",
    "data"
  ]
}

Here is a snippet of the JSON payload received from the API endpoint:

{
  "status": "success",
  "timestampUTC": "2023-09-20T12:20:29.606380Z",
  "errorCode": null,
  "errorMessage": null,
  "data": {}
}

Error Message:

DecodingError: valueNotFound errorCodePayload - at CodingKeys(stringValue: "errorCode", intValue: nil): The anyOf structure did not decode into any child schema. (underlying error: <nil>)

Steps to Reproduce:

Generate Swift code using the provided OpenAPI spec. Make an API request to receive the JSON payload. Attempt to decode the JSON payload using the generated Swift code. Expected Behavior: The JSON payload should be successfully decoded into the corresponding Swift model.

Actual Behavior:

Decoding fails with the error message mentioned above.

Environment:

Swift OpenAPI Generator version: 0.2.2 Swift version: 5.9 Runtime: 0.2.3 OpenAPI Spec version: 3.1.0

OpenAPI Generator config:

generate:
  - types
  - client
featureFlags:
  - nullableSchemas

About this issue

  • Original URL
  • State: open
  • Created 9 months ago
  • Comments: 41

Most upvoted comments

JP has merged the fix into Yams and plans to cut a release! Best case outcome. Once that release is cut that should unblock work on this ticket, although both OpenAPIKit and this project will need to accept the next major version of Yams first.

The necessary change to OpenAPIKit was made with the newest release candidate. This project’s package manifest has already been updated to use that version, so the remaining work will be to filter out .null schemas under .anyOf schemas within this project.

That may not fully answer the question but I’m not as familiar with the internals of the generator so I’ll quit while I’m ahead. 🙂

I commented on the open PR in hopes that JP notices and has time to reply. I didn’t suggest the possibility of a fork right-off because that’s a proposed solution under only 1 of three possible scenarios and I wanted to ask which scenario we are looking at here ( (a) the open PR could be merged if conflicts were fixed, (b) JP would like a different solution to the problem, or © JP does not intend to merge any PRs solving the associated bug now or in the future).

Well thats confusing! Okay… I’ll proceed assuming the JSONSchemas being evaluated are correct. Thanks so much for taking the time to confirm @mattpolzin!

Filter it out, just keep track of the fact so that you also omit the verify call at the end of the decoding initializer.

RE keeping track of having filtered it out, this should be one of two viable options, the other one being checking the coreContext of the anyOf schema to see if it is nullable: true, which may be more direct or lower overhead (but whichever works best here).

@czechboy0 what are your thoughts on what the “correct” output should be for the translation? I have this idea that "anyOf": [{ "type": "string" }, { "type": "null" }] should just be translated to

public var value1: Swift.String?

Basically, ignoring the “null” value entirely, and then later removing the check to make sure at least 1 isn’t null.

Yup that’s what I was thinking as well. Filter it out, just keep track of the fact so that you also omit the verify call at the end of the decoding initializer.

Awesome! I’ll keep investigating. Thanks for the info!

How about this compromise: I’ll propagate nullable to the root of an anyOf and then swift-openapi-generator can choose to ignore .null json schemas. Roughly what you said but minus me removing the child schema.

I didn’t even really answer the question. In OpenAPIKit this is represented as an anyOf with two schemas within it, no funny business.

Just to speak for OpenAPIKit, I try not to implement this particular kind of heuristic (turning the above into a nullable string instead of leaving it as an anyOf) because I think the sheer number of ways anyOf might be used would make that difficult to do generally and not sustainable if tackled one specific case at a time.

The fact that JSON schemas can often represent simpler scenarios as more complex schemas was the motivation behind the OpenAPIKit code that “simplifies” schemas; this code only currently simplifies allOf schemas but I always wanted it to handle anyOf and others as well.

I realize I’ve walked a line here in the past since I do handle e.g. type: [string, null] as a nullable string rather than strictly storing the two types it can handle. Perhaps that is arguably a step too far as well but the redesign would have been more work and less backwards compatible so for now it will have to remain.

Ah, it’s not a hand-written document, I see.

I’ll try to repro locally and it’ll likely be on us to fix. Thanks for reporting!

Updated to version 0.2.X, but still seeing the same behavior. updated original description