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.
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 had a project on "spring boot 2" and I want to test it.
Table:
#Entity
#Table(name = "Contract")
public class Contract extends ADBObjectWithID<ContractBean>
{
#NotBlank
#Size(max = 512)
private String name;
#Size(max = 2056)
private String comment;
#Override
public ContractBean toBean()
{
return new ContractBean(getId(), getName(), getComment());
}
}
Repository is CrudRepository<Contract, Long>:
Service:
#Service
public class ContractServiceImpl implements ContractService
{
private ContractRepository contractRepository;
public ContractServiceImpl(ContractRepository contractRepository)
{
this.contractRepository = contractRepository;
}
#Override
#Transactional
public Contract saveObject(ContractBean contractBean)
{
Contract contract;
if (contractBean.getId() == null)
{
contract = new Contract();
}
else
{
contract = findById(contractBean.getId()).orElseThrow(() -> new NullPointerException("Contract not found"));
}
contract.setName(contractBean.getName());
contract.setComment(contractBean.getComment());
return contractRepository.save(contract);
}
#Override
#Transactional
public void deleteObject(ContractBean contractBean)
{
}
#Override
public Optional<Contract> findById(Long id)
{
return contractRepository.findById(id);
}
}
I wanting to test "Service" layer and testing it in the test database. Parameters of the test database available in the "application-test.properties", but I running test, "SpringBoot" used the real database from "application.properties".
Test:
#RunWith(SpringRunner.class)
#SpringBootTest
public class ContractTest
{
#Autowired
private ContractService contractService;
#Test
public void createContract()
{
String name = "Contract name";
String comment = "Contract comment";
ContractBean contractBean = new ContractBean();
contractBean.setName(name);
contractBean.setComment(comment);
Contract contract = contractService.saveObject(contractBean);
Assert.assertEquals(name, contract.getName());
Assert.assertEquals(comment, contract.getComment());
contractBean = contract.toBean();
Assert.assertEquals(name, contractBean.getName());
Assert.assertEquals(comment, contractBean.getComment());
}
}
Pls, tell me, how do I switch to the test base? I trying #PropertySource("classpath:application-test.properties") and #TestPropertySource("classpath:application-test.properties"), but not work
Run with Spring Profile test.
-Dspring.profiles.active=test
You can add the default profile as test into your application.yml to pick it automatically.
spring:
profiles.active: test
I scan two QR code and try to get them from My QR Code Android Mobile App and save it with repository.save() in my Local db.
My app send List to Backend but don't insert to db. When I run localhost/8090, i don't get back anything.
In Browser show only this:
-Find Devices
-Device Code
-Device ID
Developer.java
#Entity
public class Developer {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id = 0;
private String deviceCode;
private String deviceId;
public Developer() {
super();
}
public Developer(String deviceCode, String deviceID)
{
super();
this.deviceCode = deviceCode;
this.deviceId = deviceId;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String deviceCode() {
return deviceCode;
}
public void set DeviceCode(String deviceCode) {
this.deviceCode;
}
public String deviceId() {
return deviceId;
}
public void set DeviceId(String deviceId) {
this.deviceId;
}
}
DeveloperRepository.java
import org.springframework.data.repository.CrudRepository;
public interface DeveloperRepository extends CrudRepository<Developer, Long> {
}
DeveloperController.java
#Controller
public class DevelopersController {
#Autowired
DeveloperRepository repository;
#RequestMapping(value = "/", method = RequestMethod.POST)
#ResponseBody
private String addDevices(Developer deviceCodeAndId) {
System.out.println("xyz!");
if (!repository.exists(deviceCodeAndId.getId())) {
repository.save(deviceCodeAndId);
return "successfully added " + deviceCodeAndId.getId();
}
return deviceCodeAndId.getId();
}
#RequestMapping(value = "/showall",method = RequestMethod.GET)
public String index(Model model) {
model.addAttribute("index",repository.findAll());
return "index";
}
}
deviceCodeAndID is Class from Android App which scanned with app!
index.html
Have you enabled transaction management. Even you use Spring boot data repository. You need to enable transaction management, else by default everything will be read mode. And for read mode there is no need to transaction. But when you do any operation that will change data in DB, you need to perform transaction management.
Use #EnableTransactionManagement on in application class, and #Transactional in DAO or service class
#SpringBootApplication
public class Application {
#Autowired
DeveloperRepository developerRepository;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
Here is my Application Class
I am using JPA, Hibernate and Spring MVC. In the controller class all the methods works greatly. When I test them in the web browser the public String getModuleFormation(long id) method, that returns an object, and it gives me the following error:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
as a root cause, but yesterday I tried it, and it worked without problem in the localhost:45045/GestionModules/detail/xx URL.
What could cause this problem?
My detail.jsp:
<c:if test="${!empty detailModule}">
${detailModule.idModule}
${detailModule.libModule}
</c:if>
POJO Class + JPA :
#Entity
#Table(name="ModuleFormation")
public class ModuleFormation {
private long idModule;
private String libModule;
public ModuleFormation() {
// TODO Auto-generated constructor stub
}
public ModuleFormation(String libModule) {
this.libModule = libModule;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "seqModule")
#SequenceGenerator(name="seqModule", sequenceName = "seqModuleFormation")
#Column(name="idModule")
public long getIdModule() {
return this.idModule;
}
public void setIdModule(long idModule) {
this.idModule = idModule;
}
#Column(name="libModule", nullable=false, length = 100)
public String getLibModule() {
return this.libModule;
}
public void setLibModule(String libModule) {
this.libModule = libModule;
}
}
DAO Class :
#Repository
public class ModuleFormationDAOImpl implements ModuleFormationDAO {
#Autowired
private SessionFactory sessionFactory;
public void ajouterModuleFormation(ModuleFormation module) {
sessionFactory.getCurrentSession().save(module);
}
public void supprimerModuleFormation(long idModule) {
ModuleFormation module = (ModuleFormation) sessionFactory.getCurrentSession().load(ModuleFormation.class, idModule);
if(module != null)
sessionFactory.getCurrentSession().delete(module);
}
public List<ModuleFormation> listModuleFormation() {
return sessionFactory.getCurrentSession().createQuery("from ModuleFormation")
.list();
}
public ModuleFormation getModuleFormation(long idModule) {
return (ModuleFormation) sessionFactory.getCurrentSession().load(ModuleFormation.class, idModule);
}
public void majModuleFormation(ModuleFormation module) {
sessionFactory.getCurrentSession().merge(module);
}
}
Service Class :
#Service
public class ModuleFormationServiceImpl implements ModuleFormationService {
#Autowired
private ModuleFormationDAO moduleDao;
#Transactional
public void ajouterModuleFormation(ModuleFormation module) {
moduleDao.ajouterModuleFormation(module);
}
#Transactional
public void supprimerModuleFormation(long idModule) {
moduleDao.supprimerModuleFormation(idModule);
}
#Transactional
public List<ModuleFormation> listModuleFormation() {
return moduleDao.listModuleFormation();
}
#Transactional
public ModuleFormation getModuleFormation(long idModule) {
return moduleDao.getModuleFormation(idModule);
}
#Transactional
public void majModuleFormation(ModuleFormation module) {
moduleDao.majModuleFormation(module);
}
}
Controller Class :
#Controller
public class ModuleFormationController {
#Autowired
private ModuleFormationService moduleService;
#RequestMapping("/module")
public String listModulesFormations(Map<String, Object> map) {
map.put("module", new ModuleFormation());
map.put("moduleList", moduleService.listModuleFormation());
return "module";
}
#RequestMapping(value = "/ajouter", method = RequestMethod.POST )
public String ajouterModuleFormation(#ModelAttribute("module")
ModuleFormation module,BindingResult result) {
moduleService.ajouterModuleFormation(module);
return "redirect:/module";
}
#RequestMapping(value = "/supprimer/{idModule}")
public String supprimerModuleFormation(#PathVariable("idModule")
long idModule) {
moduleService.supprimerModuleFormation(idModule);
return "redirect:/module";
}
#RequestMapping(value= "/detail/{idModule}")
public String getModuleFormation(#PathVariable("idModule")
long idModule,Map<String, Object> map) {
map.put("detailModule", moduleService.getModuleFormation(idModule));
return "/detail";
}
#RequestMapping(value= "/detail/modifier", method = RequestMethod.POST )
public String majModuleFormation(#ModelAttribute("detailModule")
ModuleFormation module, BindingResult result) {
moduleService.majModuleFormation(module);
return "detail/{idModule}";
}
}
The Javadoc on the Hibernate Session#load(Class, Serializable) method says:
Return the persistent instance of the given entity class with the given identifier,
assuming that the instance exists. This method might return a proxied instance that
is initialized on-demand, when a non-identifier method is accessed.
When you access a property on the object in your JSP the session which loaded the object has been closed.
Use Session#get(Class, Serializable) to ensure that you don't load a proxy.
Instead of sessionFactory.getCurrentSession().load(ModuleFormation.class, idModule), have you tried sessionFactory.getCurrentSession().get(ModuleFormation.class, idModule)?
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.