14

I'm trying to omit null values in my ResponseEntity.

My controller looks something like this:

@RestController
public class FooController {

    //fields
    //constructor

    @PostMapping
    public ResponseEntity<CreateFooResponseV10> createFoo(@Valid @RequestBody CreateFooRequestV10 foo, HttpServletRequest request) {
        //some minor logic
        return new ResponseEntity<>(aFooResponseV10Builder()
                .withFirstName(foo.getFirstName())
                .withLastName(foo.getLastName())
                .withTestField(NULLABLE_OBJECT)
                .build(), ...);
    //I generated the builders from the output classes openapi-generator provided
    }
    // more stuff...
}

When NULLABLE_OBJECT is equal to null I expect the field to be omitted from the response like this:

{
  "firstName": "John",
  "lastName": "Doe"
}

But I either get these responses, depending on what I've tried so far:

{
  "firstName": "John",
  "lastName": "Doe",
  "testField": null
}

or

{
   "firstName": "John",
   "lastName": "Doe",
   "testField": {"present":false}
}

I generate my request/response objects (CreateFooResponseV10 and CreateFooRequestV10) with the use of openapi-generator

Here is my redacted api.json file:

{
  "openapi": "3.0.1",
  "info": { ... },
  "servers": [ ... ],
  "paths": {
    "/foo": {
      "post": {
        ...
        "requestBody": {
          "description": "Foo to be created",
          "content": {
            "application/foo+json;version=1.0": {
              "schema": {
                "$ref": "#/components/schemas/CreateFooRequest_V1_0"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Foo is successfully created",
            "headers": { ... },
            "content": {
              "application/foo+json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateFooResponse_V1_0"
                }
              }
            }
          },
          ...
        }
      }
    }
  },
  "components": {
    "schemas": {
      "CreateFooRequest_V1_0": {
        "required": [
          "firstName",
          "lastName"
        ],
        "type": "object",
        "properties": {
          "firstName": { ... },
          "lastName": { ... },
          "testField": {
            "description": "...",
            "type": "string",
            "nullable": true
          }
        }
      },
      "CreateFooResponse_V1_0": {
        "required": [
          "firstName",
          "lastName"
        ],
        "type": "object",
        "properties": {
          "firstName": { ... },
          "lastName": { ... },
          "testField": {
            "description": "...",
            "type": "string",
            "nullable": true
          }
        }
      }
    }
  }
}

As you can see in both the request and response testField is not required and can be nullable. So when testField is null it should be hidden from the response, but when it contains some date it should be shown of course.

I've tried overriding jackson's ObjectMapper bean as explained in this answer. Didn't work.
I've tried adding spring.jackson.default-property-inclusion=non_null to the application.properties. Didn't work.

What I think should work is adding @JsonIgnore above testField of the generated classes, but I don't know if this is something needed to be done manually (for each schema component, can be a lot of manual work for something that is generated) or if this can be configured in the plugin somewhere.

Thanks in advance.


extra info
OpenAPI 3.0.1
Maven 3.6.3
Java 11.0.2
jackson-databind-nullable 0.2.1
openapi-generator-maven-plugin 4.2.2

Edito
  • 3,030
  • 13
  • 35
  • 67

4 Answers4

14

You can set the following in application.properties

spring.jackson.default-property-inclusion = NON_NULL

See Customize the Jackson ObjectMapper

Note: To make use of this, you need to @Autowire the ObjectMapper, and not manually create it

Justice
  • 414
  • 5
  • 11
  • 1
    Thanks, also the solution by Popov works, but in that case I had to fix also the conversion in json text of the dates in OffsetDateTime generated by default settings. – user1708042 Mar 30 '21 at 16:32
  • its not working for me with spring boot version 2.1.4 I use application.yml file spring: jackson: default-property-inclusion: NON_NULL – MiGo Feb 18 '22 at 12:15
  • @MiGo It works for me only if I autowire the ApiClient Bean instead of creating with `new`. – WhiteCherry Feb 23 '22 at 08:26
  • It doesn't work – Jarvis Jul 16 '22 at 12:49
  • Using the classes generated by openapi and making certain fields nullable, all I had to do was add this spring config – jrsall92 May 11 '23 at 14:50
7

You can generate the model classes with additional class annotations using OpenApi generator. Just need to include this in your maven plugin:

<configOptions>
   <additionalModelTypeAnnotations>
@com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL)
   <additionalModelTypeAnnotations>
<configOptions>

see other config options here: https://openapi-generator.tech/docs/generators/spring/

4

Try registering the following bean in your spring context. It should override default bean

@Bean
public HttpMessageConverters httpMessageConverters() {
    ObjectMapper mapper = new ObjectMapper();
    mapper.setSerializationInclusion(Include.NON_NULL)
    return new HttpMessageConverters(
            new MappingJackson2HttpMessageConverter(mapper));
}
Maxim Popov
  • 1,167
  • 5
  • 9
  • Thanks. It's working. I am also searching if someway we can add config properties while open API code generator so that no need to include bean. – Tejas Mehta Jun 04 '21 at 07:38
-3

Try this code. I tested and it works.

@RestController
@RequestMapping("/testws")
public class TestWS {
    @RequestMapping(value = "test", method = { RequestMethod.POST,
            RequestMethod.GET }, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ResponseEntity<TestBean> test(@Context HttpServletRequest request) {
        TestBean testBean = new TestBean("John", "Doe", null);
        return ResponseEntity.ok()
                .body(testBean);
    }
}

@JsonInclude(Include.NON_NULL)
class TestBean {
    private String firstName;
    private String lastName;
    private String testField;

    public TestBean() {

    }

    public TestBean(String firstName, String lastName, String testField) {
        super();
        this.firstName = firstName;
        this.lastName = lastName;
        this.testField = testField;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getTestField() {
        return testField;
    }

    public void setTestField(String testField) {
        this.testField = testField;
    }

}

Json response:

{"firstName":"John","lastName":"Doe"}
Kevin Le
  • 266
  • 1
  • 5
  • Not helpful, as described in the question the request&response objects are automatically generated with Swagger and therefore adding a manual annotation is not possible. – Kosi2801 Apr 27 '21 at 09:08