File this in the "notes to self" category. The OpenAPI docs are very sparse when it comes to nullable
, and it is very unclear how to set an object to nullable when it uses oneOf
, allOf
, or anyOf
in OpenAPI 3.0.
I've been working on writing an OpenAPI spec for an existing API, and hooking it in to Postman for our team to use. Unfortunately, Postman was giving me a validation error when making requests, but all it would say was "The response body didn't match the specified schema", with no actual details on which parts of the schema were wrong.
After much struggle and head-desking, I managed to work it out.
To review, here is how you would make a property nullable:
properties:
myProperty:
type: string
nullable: true
This also works with normal objects:
properties:
myProperty:
type: object
nullable: true
properties:
mySubProperty:
type: string
However, if you have an object that uses oneOf
, allOf
, or anyOf
, setting nullable: true
on the object itself will not work.
properties:
# this works great
nullableObject:
type: object
nullable: true
properties:
id:
type: integer
# this doesn't work
notActuallyNullableObjectWithAllOf:
type: object
nullable: true
allOf:
- type: object
properties:
id:
type: integer
- type: object
properties:
anotherId:
type: integer
# neither does this
notActuallyNullableObjectWithAnyOf:
type: object
nullable: true
anyOf:
- type: object
properties:
id:
type: integer
- type: object
properties:
anotherId:
type: integer
The solution depends on your approach and which of oneOf
, allOf
, or anyOf
you are using.
oneOf
and anyOf
If you are using oneOf
or anyOf
, you're going to have the easiest time. As long as at least one of your specified schemas is nullable, the entire object will be nullable.
# oneOf
properties:
# this won't work
notActuallyNullableObjectWithOneOf:
type: object
nullable: true
oneOf:
- type: object
properties:
id:
type: integer
- type: object
properties:
anotherId:
type: integer
# neither will this
notActuallyNullableObjectWithAnyOfRefs:
type: object
nullable: true
anyOf:
- $ref: '#/components/schemas/SomeReferencedObject'
- $ref: '#/components/schemas/AnotherReferencedObject'
# this works great, since nullable: true is set on the first object
#
# important: do not include type: object at the top level
nullableObjectWithOneOf:
oneOf:
- type: object
nullable: true
properties:
id:
type: integer
- type: object
properties:
anotherId:
type: integer
nullableObjectWithAnyOf:
anyOf:
- type: object
nullable: true
properties:
id:
type: integer
- type: object
properties:
anotherId:
type: integer
However, you may run in to a case where you don't want to set nullable: true
on one of the specified schemas. This is fairly common when using $ref
to reference a schema that is also being used elsewhere.
properties:
# this won't work
notActuallyNullableObjectWithOneOfRefs:
type: object
nullable: true
oneOf:
# I don't want to edit those schemas to set nullable: true, since they're used elsewhere and my _not_ be nullable
- $ref: '#/components/schemas/SomeReferencedObject'
- $ref: '#/components/schemas/AnotherReferencedObject'
# neither will this
notActuallyNullableObjectWithAnyOfRefs:
type: object
nullable: true
anyOf:
- $ref: '#/components/schemas/SomeReferencedObject'
- $ref: '#/components/schemas/AnotherReferencedObject'
The solution here is to add an empty choice to the oneOf
or anyOf
list, and set nullable: true
on that. This becomes a valid choice, and will make the entire object nullable.
properties:
nullableObjectWithOneOfRefs:
oneOf:
# empty nullable object
- type: object
nullable: true
- $ref: '#/components/schemas/SomeReferencedObject'
- $ref: '#/components/schemas/AnotherReferencedObject'
nullableObjectWithAnyOfRefs:
anyOf:
# empty nullable object
- type: object
nullable: true
- $ref: '#/components/schemas/SomeReferencedObject'
- $ref: '#/components/schemas/AnotherReferencedObject'
allOf
Getting this to work with allOf
is a little bit more complicated. The problem is that allOf
requires all of the specified schemas to have nullable: true
set, or the entire object will not be nullable.
properties:
# this won't work
notActuallyNullableObjectWithAllOf:
type: object
nullable: true
allOf:
- type: object
properties:
id:
type: integer
- type: object
properties:
anotherId:
type: integer
# neither will this, since the secont object is not nullable
notActuallyNullableObjectWithAllOfAndNullableOptions:
allOf:
- type: object
nullable: true
properties:
id:
type: integer
- type: object
properties:
anotherId:
type: integer
The solution here is to either set nullable: true
on all of the specified schemas, or nest the allOf
array inside of a oneOf
array, and set nullable: true
on the first object in the oneOf
array. This approach works just as well for inline schemas as it does for $ref
schemas inside the anyOf
array.
properties:
# this works since each `allOf` schema is nullable
nullableObjectWithAllOfAndNullableOptions:
allOf:
- type: object
nullable: true
properties:
id:
type: integer
- type: object
nullable: true
properties:
anotherId:
type: integer
# this works great as well, without having to adjust the schemas
nullableObjectWithAllOf:
anyOf:
- type: object
nullable: true
- type: object
allOf:
- type: object
properties:
id:
type: integer
- type: object
properties:
anotherId:
type: integer
# the same approach works just as well with `$ref`
nullableObjectWithAllOfRefs:
anyOf:
- type: object
nullable: true
- type: object
allOf:
- $ref: '#/components/schemas/SomeReferencedObject'
- $ref: '#/components/schemas/AnotherReferencedObject'
How did I figure this out?
As I said before, Postman was letting me know the responses weren't matching the schema, but it wasn't telling me which part of the schema was wrong.
After a couple days of struggling, I finally found express-openapi-validator
. So I set up a quick Express server, and hooked it up to my OpenAPI spec. It gave me a much more detailed error message, and I was able to work out what was wrong and how to fix it.
I figured it might be useful for others to have a little tester that they could run, so I published my test setup on GitHub: openapi-3.0-nullable-object-example
. It's a very simple Express server that uses express-openapi-validator
to validate the responses against the OpenAPI spec.
Enjoy!
Top comments (0)