Hi I'm facing with a nasty problem while using Jackson JSON PRocessor with ObjectMapper class.
This is my test class that should Serialize an Object (UserHeader) into a Json String.
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
public class TestJson {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
UserHeader userHeader = new UserHeader();
userHeader.setFirstName("A");
userHeader.setLastName("A1");
userHeader.setSystem("2A");
mapper.configure(SerializationConfig.Feature.USE_ANNOTATIONS, false);
StringWriter sw = new StringWriter();
mapper.writeValue(sw, userHeader);
System.out.println(sw.toString());
}
}
This is my UserHeader class with Annotations that are used from a different ObjectMapper (not this one)
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.map.annotate.JsonSerialize.Typing;
#JsonSerialize(typing=Typing.STATIC)
public class UserHeader implements Serializable,LoggedObject, MessagesInterface {
private static final long serialVersionUID = 1L;
private String system;
private String userName;
private String firstName;
private String lastName;
private List<Scrivania> scrivanie;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
#JsonProperty("Nome utente")
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
#JsonProperty("Cognome utente")
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
#JsonProperty("Scrivanie associate")
public List<Scrivania> getScrivanie() {
return scrivanie;
}
public void setScrivanie(List<Scrivania> scrivanie) {
this.scrivanie = scrivanie;
}
#JsonProperty("Sistema (IAM o EDOC)")
public String getSystem() {
return system;
}
public void setSystem(String system) {
this.system = system;
}
#Override
#JsonIgnore
public String getObjectId() {
return this.userName;
}
#Override
#JsonIgnore
public Object getObjectData() {
try {
return this.clone();
}
catch (Exception e) {
return null;
}
}
#Override
public String getName() {
return this.userName;
}
}
However if I run the main method the system returns to me this exception:
Exception in thread "main" java.lang.NullPointerException
at org.codehaus.jackson.map.introspect.AnnotatedClass.resolveClassAnnotations(AnnotatedClass.java:295)
at org.codehaus.jackson.map.introspect.AnnotatedClass.construct(AnnotatedClass.java:141)
at org.codehaus.jackson.map.introspect.BasicClassIntrospector.forClassAnnotations(BasicClassIntrospector.java:185)
at org.codehaus.jackson.map.introspect.BasicClassIntrospector.forClassAnnotations(BasicClassIntrospector.java:15)
at org.codehaus.jackson.map.SerializationConfig.introspectClassAnnotations(SerializationConfig.java:661)
at org.codehaus.jackson.map.ser.BasicSerializerFactory.createTypeSerializer(BasicSerializerFactory.java:180)
at org.codehaus.jackson.map.ser.BeanSerializerFactory.findPropertyContentTypeSerializer(BeanSerializerFactory.java:406)
at org.codehaus.jackson.map.ser.BeanSerializerFactory._constructWriter(BeanSerializerFactory.java:778)
at org.codehaus.jackson.map.ser.BeanSerializerFactory.findBeanProperties(BeanSerializerFactory.java:608)
at org.codehaus.jackson.map.ser.BeanSerializerFactory.constructBeanSerializer(BeanSerializerFactory.java:436)
at org.codehaus.jackson.map.ser.BeanSerializerFactory.findBeanSerializer(BeanSerializerFactory.java:349)
at org.codehaus.jackson.map.ser.BeanSerializerFactory.createSerializer(BeanSerializerFactory.java:295)
at org.codehaus.jackson.map.ser.StdSerializerProvider._createUntypedSerializer(StdSerializerProvider.java:778)
at org.codehaus.jackson.map.ser.StdSerializerProvider._createAndCacheUntypedSerializer(StdSerializerProvider.java:731)
at org.codehaus.jackson.map.ser.StdSerializerProvider.findValueSerializer(StdSerializerProvider.java:369)
at http://stackoverflow.com/questions/6661717/how-to-process-an-invalid-value-with-jackson-json-processororg.codehaus.jackson.map.ser.StdSerializerProvider.findTypedValueSerializer(StdSerializerProvider.java:452)
at org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:597)
at org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:280)
at org.codehaus.jackson.map.ObjectMapper._configAndWriteValue(ObjectMapper.java:2260)
at org.codehaus.jackson.map.ObjectMapper.writeValue(ObjectMapper.java:1813)
at it.unina.edoc.json.TestJson.main(TestJson.java:65)
I have no idea about this exception because #Annotations should be ignored due to USE_ANNOTATION false config.
Moreover if I set USE_ANNOTATION to true the error disappears.
I have these jars on my buildpath:
jackson-core-asl-1.8.3.jar
jackson-jaxrs-1.8.3.jar
jackson-mapper-asl-1.8.3.jar
jackson-xc-1.8.3.jar
The usage of the DeserializationConfig.Feature.USE_ANNOTATIONS property (set to false) will cause the JACKSON DeserializerConfig class to use a NopAnnotationIntrospector. Annotations of a class will then be resolved using this NopAnnotationIntrospector. The NopAnnotationIntrospector will return false on each isHandled request for any annotation on a class - and in fact will not use this annotation in further processing.
So - the system still "inspects" the annotations - which have to be on the Classpath in this case. As Android does not provide any jaxb-api annotations this leads to the NoClassDefFoundError.
I expected USE_ANNOTATIONS = false would bring JACKSON to totally ignore any annotations - but unfortunately it does not. I will now use the Jackson Streaming API to parse the JSON string instead of using JACKSON Data Binding capabilities.
Related
I have a class pojo used to return a response to an API call in the rest controller
EmployeeResponse response = validationService.validate(request);
return new ResponseEntity<>(response, HttpStatus.OK);
However now we want to feature flag the controller class so that if a configuration property is not set, the response will not include a property. How can we do that?
public class EmployeeResponse {
private String firstName;
private String lastName
private String address; // don't want to include this if boolean flag is not set
}
EDIT: adding the controller code here to show that an object is returned without being serialized so I don't see how to fit objectMapper into that
#RestController
public class EmployeeController {
#PostMapping(value = "/validate", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<EmployeeResponse> get(final #RequestBody EmployeeRequest employeeRequest) {
MasterSubResponse response = validationService.validate(employeeRequest);
return new ResponseEntity<>(response, HttpStatus.OK);
}
}
You can use Jackson Filter to control the serialization process. When using JSON format, Spring Boot will use an ObjectMapper instance to serialize responses and deserialize requests. The idea is to create custom filter where you will place business logic for conditionally rendering desired field from DTO. Then you should add that filter to object mapper.
To summarize,here are the steps youn need to follow :
Anottate your DTO class with #JsonFilter("myFilter")
Create implementation class for your custom filter
Create configuration class for ObjectMapper where you will set filter created in step 1.
Create your boolean flag in application.properties file
Step 1:
import com.fasterxml.jackson.annotation.JsonFilter;
#JsonFilter("myFilter")
public class EmployeeResponse {
private String firstName;
private String lastName;
private String address;
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 getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
Step 2:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.PropertyFilter;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
public class CustomFilter extends SimpleBeanPropertyFilter implements PropertyFilter {
private boolean isSerializable;
#Override
public void serializeAsField
(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer)
throws Exception {
if (include(writer)) {
if (!writer.getName().equals("address")) {
writer.serializeAsField(pojo, jgen, provider);
return;
}
System.out.println(isSerializable);
if (isSerializable) {
writer.serializeAsField(pojo, jgen, provider);
}
} else if (!jgen.canOmitFields()) { // since 2.3
writer.serializeAsOmittedField(pojo, jgen, provider);
}
}
#Override
protected boolean include(BeanPropertyWriter writer) {
return true;
}
#Override
protected boolean include(PropertyWriter writer) {
return true;
}
public boolean isSerializable() {
return isSerializable;
}
public void setSerializable(boolean serializable) {
isSerializable = serializable;
}
}
Step 3:
import com.example.demo.filter.CustomFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
#Configuration
public class ObjectMapperCofiguration {
#Value("${isSerializable}")
public boolean isSerializable;
#Configuration
public class FilterConfiguration {
public FilterConfiguration(ObjectMapper objectMapper) {
SimpleFilterProvider simpleFilterProvider = new SimpleFilterProvider().setFailOnUnknownId(true);
CustomFilter customFilter = new CustomFilter();
customFilter.setSerializable(isSerializable);
simpleFilterProvider.addFilter("myFilter", customFilter);
objectMapper.setFilterProvider(simpleFilterProvider);
}
}
}
Step 4 :
In application.properties file add following property :
isSerializable= false
Step 5:
Create Controller class to test it:
#RestController
public class RestSpringBootController {
#GetMapping(path = "/test")
public ResponseEntity<EmployeeResponse> test() throws JsonProcessingException {
EmployeeResponse employeeResponse = new EmployeeResponse();
employeeResponse.setAddress("addres");
employeeResponse.setFirstName("first");
employeeResponse.setLastName("last");
ResponseEntity<EmployeeResponse> responseEntity = ResponseEntity.ok(employeeResponse);
return responseEntity;
}
}
Finally, when you start your SpringBoot app, with boolean flag isSerializable set to false you should get following response:
If you set isSerializable flag to true and restart the app, you shoud see following response:
I have a Java class which has 2 List Object inside it and i am Json serializing the parent class.
#JsonSerialize
public class RequestSalesJson {
#JsonProperty("nonUniqueSalesList")
private List<SalesDataJson> getNonUniqueSalesDataJson;
#JsonProperty("uniqueSalesList")
private List<SalesDataJson> uniqueSalesDataJson;
public List<SalesDataJson> getGetNonUniqueSalesDataJson() {
return getNonUniqueSalesDataJson;
}
public void setGetNonUniqueSalesDataJson(List<SalesDataJson> getNonUniqueSalesDataJson) {
this.getNonUniqueSalesDataJson = getNonUniqueSalesDataJson;
}
public List<SalesDataJson> getUniqueSalesDataJson() {
return uniqueSalesDataJson;
}
public void setUniqueSalesDataJson(List<SalesDataJson> uniqueSalesDataJson) {
this.uniqueSalesDataJson = uniqueSalesDataJson;
}
}
SalesReturnJson.java
#JsonSerialize
public class SalesReturnJson {
#JsonProperty("starttime")
private String startTime;
#JsonProperty("pn")
private String partNumber;
#JsonProperty("so")
private String SalesOrderNumber;
#JsonProperty("wo")
private String workOrderNumber;
#JsonProperty("loc")
//other variables declared..
}
Controller.java :-
#RequestMapping(value = "/addAllSalesData",method = RequestMethod.POST)
public void addAllSalesData(#RequestBody RequestSalesJson requestSalesJsons){
log.info("POST : '/addSalesData'");
try{
System.out.print("In Controller "+requestSalesJsons.getUniqueSalesDataJson());
//salesService.processSalesData(requestSalesJsons);
}
catch(Exception e){
// return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}
}
The value here is coming to be null.
Below is the json i am using :-
{ "uniqueSalesJson": [{"SO":4000955,"Part Number":"000","Locator":255638,"Lot Number":"P01-2059139","Reservation Quantity":2,"Status":"Released to warehouse","COE":"Fabrication","ORG":"P07","Start_Time":"2017-09-19 11:21:36"},{"SO":4000955,"Part Number":"000","Locator":255652,"Lot Number":"P01-2059140","Reservation Quantity":10,"Status":"Released to warehouse","COE":"Fabrication","ORG":"P07","Start_Time":"2017-09-19 11:21:36"}],"nonUniqueSalesJson":[{"SO":4000992,"Part Number":"1276M84G15","Locator":12345,"Lot Number":"P01-2344141","Reservation Quantity":6,"Status":"PACKED","COE":"Fabrication","ORG":"P07","Start_Time":"2017-09-19 11:21:36"},{"SO":4000992,"Part Number":"1276M84G15","Locator":12345,"Lot Number":"P01-2344141","Reservation Quantity":6,"Status":"PICKED","COE":"Fabrication","ORG":"P07","Start_Time":"2017-09-19 11:21:36"}]}
There are some issues in your code that let me doubt that your application compiles. First of all, rename the SalesReturnJson class to SalesDataJson.
Then check your #JsonProperty annotations. The value here must match exactly the property key in the Json String. Refactoring all this stuff will lead you to your root entity class:
#JsonSerialize
public class RequestSalesJson {
#JsonProperty("nonUniqueSalesJson")
private List<SalesDataJson> nonUniqueSalesDataJson;
#JsonProperty("uniqueSalesJson")
private List<SalesDataJson> uniqueSalesDataJson;
...
}
and your SalesDataJson class (missing a lot of attributes which the mapper ignores by configuration):
#JsonSerialize
public class SalesDataJson {
#JsonProperty("Start_Time")
private String startTime;
#JsonProperty("Part Number")
private String partNumber;
#JsonProperty("SO")
private String SalesOrderNumber;
}
This sample works as expected with the com.fasterxml.jackson.databind.ObjectMapper
Hope that helps!
I'm currently playing around with Jackson's de/serialization features and I encountered a problem, I don't know how to solve.
During my test the #JsonProperty(access = JsonProperty.Access.WRITE_ONLY) annotation is ignored and it only shows null.
However with e.g. Postman everything works as expected.
I using just a Spring Boot Starter with Web Starter and Test Starter dependency.
Example Code:
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
#RestController
class JacksonExampleRestController {
#PostMapping("/api")
public void getResti(#RequestBody JacksonModel jacksonModel) {
System.out.println(jacksonModel.getId());
System.out.println(jacksonModel.getPassword());
}
}
class JacksonModel {
private String id;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Test:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = DemoApplication.class)
public class DemoApplicationTests {
private MockMvc mockMvc;
#Before
public void setUp() {
JacksonExampleRestController jacksonExampleRestController = new JacksonExampleRestController();
mockMvc = MockMvcBuilders.standaloneSetup(jacksonExampleRestController)
.build();
}
#Test
public void testJackson() throws Exception {
JacksonModel jacksonModel = new JacksonModel();
jacksonModel.setId("id");
jacksonModel.setPassword("password");
mockMvc.perform(post("/api").
contentType(APPLICATION_JSON_UTF8)
.content(convertObjectToJsonBytes(jacksonModel)));
}
public static byte[] convertObjectToJsonBytes(Object object)
throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper.writeValueAsBytes(object);
}
}
Is this the default behaviour and do I have to configure something in my test or is it something else I don't see right now?
Ignoring all the annotations can be problematic. To handle a finer configuration you can implement your custom JacksonAnnotationIntrospector:
public class IgnoreJacksonWriteOnlyAccess extends JacksonAnnotationIntrospector {
#Override
public JsonProperty.Access findPropertyAccess(Annotated m) {
JsonProperty.Access access = super.findPropertyAccess(m);
if (access == JsonProperty.Access.WRITE_ONLY) {
return JsonProperty.Access.AUTO;
}
return access;
}
}
Then, after instantiating the mapper:
mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess());
add the line for your ObjectMapper:
mapper.disable(MapperFeature.USE_ANNOTATIONS);
I had the same problem, with a similar setup. The problem is in the test input data. Basically, writeValueAsBytes() will ignore the password while serializing exactly as instructed by the annotation.
Note that, Access.WRITE_ONLY basically means "SetterOnly" or "DeserializationOnly" not the other way around.
I have a POJO with custom setter methods for all properties that track whether the property was explicitly set. The setter stores to fieldNameSet boolean fields and exposes isFieldNameSet getters for those flags. I want Jackson to dynamically serialize the class with only those fields that have isFieldNameSet as true.
Background:
I started writing a custom JsonFilter implementation but it doesn't give any context as to the current object instance being serialized so obviously I can't read the current values of the isFieldNameSet properties.
Quickly hacked from a Jackson example
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ser.FilterProvider;
import org.codehaus.jackson.map.ser.impl.SimpleBeanPropertyFilter;
import org.codehaus.jackson.map.ser.impl.SimpleFilterProvider;
public class JacksonExample {
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
User user = createDummyUser();
try {
//Its age here , this is conditional based on your fieldset
SimpleBeanPropertyFilter theFilter = SimpleBeanPropertyFilter.serializeAllExcept("age");
FilterProvider filters = new SimpleFilterProvider().addFilter("myFilter", theFilter);
// Convert object to JSON string
String jsonInString = jsonInString = mapper.writer(filters).writeValueAsString(user);
System.out.println(jsonInString);
// Convert object to JSON string and pretty print
//System.out.println(jsonInString);
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static User createDummyUser() {
User user = new User();
user.setName("mkyong");
user.setAge(33);
List<String> msg = new ArrayList<>();
msg.add("hello jackson 1");
msg.add("hello jackson 2");
msg.add("hello jackson 3");
user.setMessages(msg);
return user;
}
}
package org.soproject;
import java.util.List;
import org.codehaus.jackson.map.annotate.JsonFilter;
#JsonFilter("myFilter")
public class User {
private String name;
private int age;
private List<String> messages;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List<String> getMessages() {
return messages;
}
public void setMessages(List<String> messages) {
this.messages = messages;
}
// getters and setters
}
Ignores age as you see :
{"name":"mkyong","messages":["hello jackson 1","hello jackson 2","hello jackson 3"]}
Note jackson source is from : https://www.mkyong.com/java/how-to-convert-java-object-to-from-json-jackson/
I have used Jackson and JSONObject to generate a plain JSON - things are fine here. I have a specific case where my pojo looks like below and i need the JSON is the specified format.
package test;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "login")
public class LoginApi implements IRestBean {
private String username;
private String password;
private String sfSessionId;
private String sfServerUrl;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSfSessionId() {
return sfSessionId;
}
public void setSfSessionId(String sfSessionId) {
this.sfSessionId = sfSessionId;
}
public String getSfServerUrl() {
return sfServerUrl;
}
public void setSfServerUrl(String sfServerUrl) {
this.sfServerUrl = sfServerUrl;
}
}
The JSON that i am able to generate looks like this:
{
"username" : null,
"password" : null,
"sfSessionId" : null,
"sfServerUrl" : null
}
But this is not my requirement - i need the JSON in the below format so that my server accepts this as a valid JSON:
{
"#type":"login",
"username":"username#domain.com",
"password":"password",
"sfSessionId":null,
"sfServerUrl":null
}
Please help. Thanks in advance!
Add a private field to the POJO with the type.
#XmlRootElement(name = "login")
public class LoginApi implements IRestBean {
...
#XmlAttribute(name = "type")
private String getJsonType() {
return "login";
}
...
}
Note the use of XmlAttribute to automatically append an "#" to the name.
Change the IRestBean interface to include the #JsonTypeInfo annotation:
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="#type")
public interface IRestBean {
...
}
Next, annotate the LoginApi class with #JsonTypeName:
#XmlRootElement(name = "login")
#JsonTypeName("login")
public class LoginApi implements IRestBean {
...
}
These are both Jackson-specific annotations.