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
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
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));
}
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/
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"}
Related
I am trying to map the following JSON to my POJO using Jackson. I have the following JSON and following POJOs. kindly let me know how to map the JSON to POJO.
JSON string :
{
"Application": {
"id": "0",
"name": "MyApp",
"users": [
{
"User": {
"id": "2",
"name": "Beth Jones"
}
}
],
"groups": [
{
"Group": {
"id": "1",
"name": "SimpleGroup",
"users": [
{
"User": {
"id": "2",
"name": "Beth Jones"
}
}
]
}
}
]
}
}
The POJO according to the client specification is below :
package com.example.custom;
//import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.example.Application;
import com.example.Group;
import com.example.User;
import java.util.Collection;
//#JsonIgnoreProperties(ignoreUnknown = true)
public class MyApplication extends Application {
private Collection<User> users;
private Collection<Group> groups;
public MyApplication(String id, String name) {
super(id, name);
}
public void setUsers(Collection<User> users) {
this.users = users;
}
public void setGroups(Collection<Group> groups) {
this.groups = groups;
}
#Override
public Collection<User> getUsers() {
return this.users;
}
#Override
public User getUser(String userId) {
for (User user: MyParser.myApp.getUsers()) {
if (user.getId().equals(userId))
return user;
}
return null;
}
#Override
public Collection<Group> getGroups() {
return this.groups;
}
#Override
public Group getGroup(String groupId) {
for (Group group: MyParser.myApp.getGroups()) {
if (group.getId().equals(groupId))
return group;
}
return null;
}
#Override
public String toString() {
return "MyApplication{" +
"users=" + users +
", groups=" + groups +
'}';
}
}
Mapping Logic :
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MyParser.myApp = mapper.readValue(rewriter.getText(),MyApplication.class);
The resulting object is not able to capture anything as it is all null. Kindly help. Thanks in advance.
I think you should model your JSON correctly, In the users list you shouldn't specify it again that the key is User, that should be preassumed that a list of users will only contain user, same goes for groups list.
IMHO the JSON should look something like this :
{
"application": {
"id": "0",
"name": "MyApp",
"users": [ . ==> Since this is a user List, it will definitely contains user.
{
"id": "2",
"name": "Beth Jones"
}
],
"groups": [
{
"id": "1",
"name": "SimpleGroup",
"users": [
{
"id": "2",
"name": "Beth Jones"
}
]
}
]
}
}
Now the POJO also needs some modification, I am just adding the bare-minimum POJO.
class Application { <====== Top Level Class
private Long id;
private String name;
private List<User> users; // Application has some Users
private List<Group> groups; // Application has some groups
}
class User {
private Long id;
private String name;
}
class Group {
private Long id;
private String name;
private List<User> users; // Each group has some associated users.
}
Now you can use any standard JSON library for Java and convert your JSON into POJO. This will simplify your structure and you won't face null issues with this structure.
I got a null object attributes after deserialization of a json response.
Developing under android, I'm using retrofit2 , moshi as converter (https://github.com/kamikat/moshi-jsonapi ) .
When debugging ,I saw a json response fully retrieved (not null attributes),but deserialization fails. Should I use GSON instead?
Here's my retrofit builder I use to make my json call: (no issue)
public static JsonServerInterface getSimpleClient(){
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_AUTH_URL)a
.addConverterFactory(MoshiConverterFactory.create())
.build();
JsonServerInterface webServer=retrofit.create(JsonServerInterface.class);
return webServer;
}
My api json call,response contain UserModel with null attributes(deserialization fails without any error)
signInCall.enqueue(new Callback<UserModel>(){
#Override
public void onResponse
(Call<UserModel> call, Response<UserModel> response)
{
response.message();
}
}
My UserModel (as required by moshi ,but I think it lacks something):
#JsonApi(type = "users")
public class UserModel extends Resource {
#Json(name = "auth-token")
private String authToken;
#Json(name = "firstname")
private String firstname;
#Json(name = "lastname")
private String lastname;
#Json(name = "email")
private String email;
#Json(name = "created-at")
private String createdAt;
#Json(name = "updated-at")
private String updatedAt;
private HasMany<ActivityModel> activities;
My json response I saw when debugging http response, I retrieve without any trouve,but moshi sucks to deserialize it,and no errors are raised:
{
"data": {
"id": "21",
"type": "users",
"attributes": {
"auth-token": "t8S3BTqyPwN3T4QDMY1FwEMF",
"firstname": "aymen",
"lastname": "myself",
"email": "aymen.myself#gmail.com",
"created-at": "2017-11-13T22:52:39.477Z",
"updated-at": "2017-11-13T23:21:09.706Z"
},
"relationships": {
"activities": {
"data": [
{
"id": "81",
"type": "activities"
}
]
}
}
},
"included": [
{
"id": "81",
"type": "activities",
"attributes": {
"title": "activity 10",
"description": "how to draw a circle",
"start-at": "2017-11-13T23:06:13.474Z",
"duration": 10,
"created-at": "2017-11-13T23:06:32.630Z",
"updated-at": "2017-11-13T23:06:32.630Z"
},
"relationships": {
"user": {
"data": {
"id": "21",
"type": "users"
}
}
}
}
]
}
I find the solution after lot of hours:
I should use "Document" instead of UserModel
interface:
#POST("sign-in.json")
Call<Document> signIn(#Body Credentials credentials);
when calling:
signInCall.enqueue(new Callback<Document>(){
#Override
public void onResponse(Call<Document> call, Response<Document> response) {
hope it helps
I have to create a json schema such that when i create class from it, The field name are different from the json keys.
I want to map the json keys to custom fields in the domain object created using this schema
Consider the json scheme
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "sample_master",
"title": "Person",
"type": "object",
"name":"SampleMasterFile",
"properties": {
"first_Name": {
"type": "string"
},
"SYM_CD": {
"type": "string"
// "name":"symCdValue" -> some trick here so that when the domain is created, the field name corresponding to SYM_CD is symCdValue
},
"age": {
"description": "Age in years",
"type": "integer",
"minimum": 0
}
},
"required": ["first_Name", "SYM_CD","age"],
"additionalProperties" : false
}
And this is the generated class from the above schema. I want the field generated for json key SYM_CD to be mapped to symCdValue
#JsonInclude(JsonInclude.Include.NON_EMPTY)
#JsonPropertyOrder({
"first_Name",
"SYM_CD",
"age"
})
public class SampleMaster {
#JsonProperty("first_Name")
private String firstName;
#JsonProperty("SYM_CD")
private String sYMCD; // I want this field to be named as symCdValue ?
#JsonProperty("age")
#JsonPropertyDescription("Age in years")
private Integer age;
#JsonProperty("first_Name")
public String getFirstName() {
return firstName;
}
#JsonProperty("first_Name")
public void setFirstName(String firstName) {
this.firstName = firstName;
}
#JsonProperty("SYM_CD")
public String getSYMCD() {
return sYMCD;
}
#JsonProperty("SYM_CD")
public void setSYMCD(String sYMCD) {
this.sYMCD = sYMCD;
}
#JsonProperty("age")
public Integer getAge() {
return age;
}
#JsonProperty("age")
public void setAge(Integer age) {
this.age = age;
}
// Removed to string, equals method
}
I have a use case where I want the JSON to be converted to string as it is, but it is failing and giving me null, here is my POJO:
#Data
#JsonSnakeCase
#JsonIgnoreProperties(ignoreUnknown = true)
public class DocumentTemplateRequest {
#Enumerated(EnumType.STRING)
private TemplateState state;
#JsonDeserialize(using = JsonAsStringDeserializer.class)
private String inputSchema;
}
the Json I am using as payload:
{
"state": "staging",
"input_schema": {
"title": "Person",
"type": "object",
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
}
},
"required": ["firstName", "lastName"]
}
}
I am using object mapper for mappings:
ObjectMapper objectMapper = new ObjectMapper();
DocumentTemplateRequest documentTemplateRequest = null;
try {
documentTemplateRequest = objectMapper.readValue(str, DocumentTemplateRequest.class);
} catch (IOException e) {
e.printStackTrace();
}
and here is my deserializer:
public class JsonAsStringDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
TreeNode tree = jsonParser.getCodec().readTree(jsonParser);
return tree.toString();
}
}
and the results of deserialization is :
state : staging
inputSchema: null
why inputSchema is coming as null, what am I missing?
it is with the property?
you are missing underscroe. input_schema is different from inputSchema.
either use the same property name everywhere you use it or use jsonproperty annotation to be sepecific.
I am getting the following exception after writing the below code.
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Root name 'Filing' does not match expected ('List') for type [collection type; class java.util.List, contains [simple type, class MasMonthlyReportDetail]]
JSON Object
{
"Filing":
[
{
"periodInfo":
{
"date": "06-05-2013",
"year": "2015",
"Month": "January"
},
"employerInfo":
{
"name": "Y",
"place": "Y",
"country": "N",
},
"employeeInfo":
[
{
"name": "785-23-0370",
"dob": "05/25/1952",
}
],
"messages":
[
{
"defaultMessage" : "xx",
"messageType" : "yy",
"messageCode" : "102"
}
]
},
{
"periodInfo":
{
"date": "06-05-2013",
"year": "2015",
"Month": "January"
},
"employerInfo":
{
"name": "Y",
"place": "Y",
"country": "N",
},
"employeeInfo":
[
{
"name": "785-23-0370",
"dob": "05/25/1952",
}
],
"messages":
[
{
"defaultMessage" : "xx",
"messageType" : "yy",
"messageCode" : "102"
}
]
}
]
}
Main Class
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
List<MasMonthlyReportDetail> lcd = objectMapper.readValue(new File(filePath),new TypeReference<List<MasMonthlyReportDetail>>(){});
MasMonthlyReportDetail.java
#JsonRootName(value="Filing")
public class MasMonthlyReportDetail {
private PeriodInfo periodInfo;
private EmployerInfo employerInfo;
List<EmployeeInfo> employeeInfo;
List<Messages> messages;
public PeriodInfo getPeriodInfo() {
return periodInfo;
}
public void setPeriodInfo(PeriodInfo periodInfo) {
this.periodInfo = periodInfo;
}
public EmployerInfo getEmployerInfo() {
return employerInfo;
}
public void setEmployerInfo(EmployerInfo employerInfo) {
this.employerInfo = employerInfo;
}
public List<EmployeeInfo> getEmployeeInfo() {
return employeeInfo;
}
public void setEmployeeInfo(List<EmployeeInfo> employeeInfo) {
this.employeeInfo = employeeInfo;
}
public List<Messages> getMessages() {
return messages;
}
public void setMessages(List<Messages> messages) {
this.messages = messages;
}
}
I made the following changes and the code worked for me.
Main Class:
InputStream inputStream = resource.getInputStream();
ObjectMapper objectMapper = new ObjectMapper();
MasMonthlyReportDetailHolder masMonthlyReportDetailHolder = objectMapper.readValue(inputStream, MasMonthlyReportDetailHolder.class);
List<MasMonthlyReportDetail> masMonthlyReportDetail = masMonthlyReportDetailHolder.getMasMonthlyReportDetail();
MasMonthlyReportDetailHolder class:
public class MasMonthlyReportDetailHolder {
private List<MasMonthlyReportDetail> masMonthlyReportDetail;
#JsonProperty("Filing")
public List<MasMonthlyReportDetail> getMasMonthlyReportDetail() {
return masMonthlyReportDetail;
}
public void setMasMonthlyReportDetail(
List<MasMonthlyReportDetail> masMonthlyReportDetail) {
this.masMonthlyReportDetail = masMonthlyReportDetail;
}
}
Adding #JsonProperty("Filing") is the key to avoid this issue. In case of any other procedure, do let me know.
have u tried this ?
jacksonObjectMapper.reader(MasMonthlyReportDetail.class).withRootName("Filing").readValue(jsonAsString);