#JsonUnwrapped creates non-null object even when all fields are null - json

I ran into this problem in the context of SpringBoot, but it seems to be just a Jackson issue.
I have a DataObject which has several fields. The DataObject is contained within a Model. All the fields of DataObject are optional. But if all the fields are null, then I want the dataoObject itself to be null, but that doesn't seem to be the way it works.
Here my sample code
#Getter
#ToString
#NoArgsConstructor
public class TestContract {
private String field1;
#JsonUnwrapped
#Valid
private DataObject dataObject;
public static void main(String[] args) throws JsonProcessingException {
//language=JSON
String json = "{ \"field1\" : \"value1\"}";
ObjectMapper mapper = new ObjectMapper();
TestContract contract = mapper.readValue(json, TestContract.class);
System.out.println("contract: " + contract);
}
}
#Getter
#NoArgsConstructor
#ToString
public class DataObject {
private String nested1;
private String nested2;
}
The output I end up with is
contract: TestContract(field1=value1, dataObject=DataObject(nested1=null, nested2=null))
Is there any way to have the object end up as null?

The issue is due to the fact that the JsonUnwrapped annotation has the enabled property with a default true value, so even if the property dataObject is not present in your json message an empty dataObject object will be created in any case. To solve the issue, you could use a JsonUnwrapped(enabled = false) annotation or you could directly delete the JsonUnwrapped annotation.
Update : due to the fact that according to the project's specifics is it not possible to erase the JsonUnwrapped annotation, a workaround is the creation of a custom deserializer leaving untouched the main code like below :
#Getter
#ToString
#NoArgsConstructor
public class TestContract {
private String field1;
#JsonUnwrapped
private DataObject dataObject;
public static void main(String[] args) throws JsonProcessingException, IOException {
//language=JSON
String json = "{ \"field1\" : \"value1\"}";
ObjectMapper mapper = new ObjectMapper();
TestContract contract = mapper.readValue(json, TestContract.class);
System.out.println("contract: " + contract);
}
}
#Getter
#Setter
#NoArgsConstructor
#ToString
#JsonDeserialize(using = DataObjectDeserializer.class)
public class DataObject {
private String nested1;
private String nested2;
}
public class DataObjectDeserializer extends JsonDeserializer<DataObject> {
#Override
public DataObject deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
String nested1 = node.get("nested1").asText();
String nested2 = node.get("nested2").asText();
DataObject dataObject = new DataObject();
dataObject.setNested1(nested1);
dataObject.setNested2(nested2);
return dataObject;
}
}

Related

Can Jackson accept both wrapped and unwrapped Json when using JsonUnwrapped

I have a class like this:
...
#JsonUnwrapped
private DataObject dataObject;
...
Data object is
#Getter
public class DataObject {
private String nested1;
private String nested2;
}
Obviously, this will work if my Json input is
{
"nested1" : "nestedValue1",
"nested2" : "nestedValue2"
}
Is there a way to make it flexible so that it can accept both versions?
But what if I want it to also be able to accept
{
"dataObject: {
"nested1" : "nestedValue1",
"nested2" : "nestedValue2"
}
}
Taking the code of your TestContract class from your previous question below :
public class TestContract {
private String field1;
#JsonUnwrapped
private DataObject dataObject;
}
One way to solve the problem is to write a custom deserializer for your TestContract class that will use the following lombok annotations and the #JsonDeserialize annotation :
#Getter
#Setter
#ToString
#NoArgsConstructor
#JsonDeserialize(using = TestContractDeserializer.class)
public class TestContract {
private String field1;
#JsonUnwrapped
private DataObject dataObject;
}
The custom deserializer will check the field1 property and after will check if the two nested1 and nested2 properties are nested or not inside the dataObject property covering both cases you presented :
public class TestContractDeserializer extends JsonDeserializer<TestContract> {
#Override
public TestContract deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
//read the field1 property
String field1 = node.get("field1").asText();
JsonNode dataObjectNode = node.get("dataObject");
//check if properties are nested or not
if (dataObjectNode != null) {
node = dataObjectNode;
}
String nested1 = node.get("nested1").asText();
String nested2 = node.get("nested2").asText();
DataObject dataObject = new DataObject();
dataObject.setNested1(nested1);
dataObject.setNested2(nested2);
TestContract testContract = new TestContract();
testContract.setDataObject(dataObject);
testContract.setField1(field1);
return testContract;
}
}

Jackson deserialize with missing fields dos not throw exception

Trying with that simple app
public class JsonTest {
public static void main(String[] args) throws Exception {
String json = "{\"name\":\"john\"}";
System.out.println(json);
ObjectMapper objectMapper = new ObjectMapper();
Person person = objectMapper.readValue(json, Person.class);
System.out.println(person);
}
#Data
private static class Person {
String name;
Integer age;
}
}
I don't understand why it does not throw an exception.
It gives me
{"name":"john"}
JsonTest.Person(name=john, age=null)
I also tried adding objectMapper .configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, true); or even add #NotNull or #JsonProperty(required = true) in front of Integer age but it has no effect.
The only solution I found is, after the deserialization, to test if age is null.
Is there another solution ?

How to deserialize json to an abstract class in spring-boot

In my Application i have something like this.
public class Question{}
public class MCQ extends Question{}
public class TrueAndFalse Question{}
public class Match Question{}
and in my RestController i have a service that adds question.
#RequestMapping(value = "/game/question/add", method = RequestMethod.POST)
public Question addQuuestion(#RequestParam("gameId") long id, #RequestBody Question question)
But i get an error when i try to call this service as i send json file with different structures one for MCQ, TrueAndFalse and Match.
so is it possible to deserialize the received json to Question abstract class.
And thanks in advance.
You can create a custom deserializer which will create Question instances based on json payload properties.
For example if the Question class looks like this:
public class Question {
private final String name;
#JsonCreator
Question(#JsonProperty("name") String name) {
this.name = name;
}
public String getName() {
return name;
}
}
And sub-class TrueAndFalse:
public class TrueAndFalse extends Question {
private final String description;
#JsonCreator
TrueAndFalse(#JsonProperty("name") String name,
#JsonProperty("description") String description) {
super(name);
this.description = description;
}
public String getDescription() {
return description;
}
}
Then you can create a deserializer, which will create an instance of TrueAndFale sub-class by checking if it has description property:
public class QuestionDeserializer extends JsonDeserializer<Question> {
#Override
public Question deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
ObjectCodec codec = p.getCodec();
JsonNode tree = codec.readTree(p);
if (tree.has("description")) {
return codec.treeToValue(tree, TrueAndFalse.class);
}
// Other types...
throw new UnsupportedOperationException("Cannot deserialize to a known type");
}
}
And afterwards, make sure to register it on the object mapper:
#Configuration
public class ObjectMapperConfiguration {
#Bean
public ObjectMapper objectMapper() {
SimpleModule module = new SimpleModule();
module.addDeserializer(Question.class, new QuestionDeserializer());
return new ObjectMapper().registerModules(module);
}
}

How to ignore attributes while serializing a class by ObjectMapper

I have a class with lots of attributes which are required for server side logic, but a few of those are required for UI. Now when I am creating a json from the class, all the attributes are written to json. I want to ignore some values only when it is converted to json. I Tried with #JsonIgnore. But it is not working.
My Class Is
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonIgnoreProperties(ignoreUnknown = true)
public class Student {
#JsonProperty("id")
protected Integer id;
#JsonProperty("name")
protected String name;
/**
* This field I want to ignore in json.
* Thus used #JsonIgnore in its getter
*/
#JsonProperty("securityCode")
protected String securityCode;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#JsonIgnore
public String getSecurityCode() {
return securityCode;
}
public void setSecurityCode(String securityCode) {
this.securityCode = securityCode;
}
}
And I am writing this using
public static StringBuilder convertToJson(Object value){
StringBuilder stringValue = new StringBuilder();
ObjectMapper mapper = new ObjectMapper();
try {
stringValue.append(mapper.writeValueAsString(value));
} catch (JsonProcessingException e) {
logger.error("Error while converting to json>>",e);
}
return stringValue;
}
My Expected json should contain only :
id:1
name:abc
but what I am getting is
id:1
name:abc
securityCode:_gshb_90880..some_value.
What is wrong here, please help
Your #JsonProperty annotation overrides #JsonIgnore annotation. Remove #JsonProperty from securityCode and your desired json output will be produced.
If you want more advanced ignoring / filtering please take a look at:
#JsonView : http://wiki.fasterxml.com/JacksonJsonViews
#JsonFilter : http://wiki.fasterxml.com/JacksonFeatureJsonFilter

how to handle Json body in post request in jax-rs

I have a project (homework) about JAX-RS. I'm working with NetBeans, Jersey and Tomcat.
This is my "User" class for main object in the system.
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name="user")
public class User {
//#XmlElement
//public int id ;
#XmlElement
public String username;
#XmlElement
public String fullname;
#XmlElement
public String gender;
#XmlElement
public String birthDate;
public User(){
}
public User(String username,String fullname, String gender,String birthDate){
//this.id = id;
this.username = username;
this.fullname = fullname;
this.gender = gender;
this.birthDate = birthDate;
}
}
This is my "JAXBContextResolver" Class
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.api.json.JSONJAXBContext;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.JAXBContext;
#Provider
public class JAXBContextResolver implements ContextResolver<JAXBContext>{
private JAXBContext context;
private Class[] types = {User.class};
public JAXBContextResolver() throws Exception {
this.context =
new JSONJAXBContext( JSONConfiguration.mapped().build(), types);
}
#Override
public JAXBContext getContext(Class<?> objectType) {
for (Class type : types) {
if (type == objectType) {
return context;
}
}
return null;
}
}
And this is my post method in the "UserService" class
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public List<User> createNewUser(User tUser) {
List<User> list = new ArrayList<User>();
list.add(tUser);
return list;
}
When I am trying a post new user in the localhost with RESTClient (Firefox add-ons) my request body is a json input like that:
{"user":{"username":"blabla","fullname":"blabla","gender":"M","birthDate":"05.01.1978"}}
In the post method (in the UserService class) must the variable "tUser" automatically filled with the coming input ? "tUser" variable shows null elements in it in the debugging mode like that:
If I know wrong could somebody correct me please? Why this values shows null? Must not them shows "blabla" - "blabla" - "M" - "05.01.1878" ? Could you help me please?
I solved this problem; In the JAXBContextResolver class I change the method like that :
public JAXBContextResolver() throws Exception {
this.context =
new JSONJAXBContext( JSONConfiguration.mapped().rootUnwrapping(false).build(), types);
}
The difference with the first one is adding "rootUnwrapping(false)" expression.
#XmlRootElement is not working in your example. Send
{"username":"blabla","fullname":"blabla","gender":"M","birthDate":"05.01.1978"}
instead
EDIT
1)
public List<User> createNewUser(Request tUser)
and class
class Request
{
public User user;
}
2)
public List<User> createNewUser(String tUser)
and convert String to object using google-gson or jackson json processor