I have add multiple custom Serializers and Deserializers to my ObjectMapper. How to check if serializers are registered?
#Bean
public ObjectMapper getMyObjectMapper(List<TestSerializer> serializers, List<TestDeserializer> deserializers) {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule customModule = new SimpleModule();
for (TestSerailizer h: serializers) {
customModule.addSerializer(h);
}
for (TestDeserializer d: deserializers) {
customModule.addDeserializer(Object.class, d);
}
objectMapper.registerModule(customModule);
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
return objectMapper;
}
You can invoke the ObjectMapper#getRegisteredModuleIds that returns the set of Module typeIds that are registered in the ObjectMapper mapper where by default the typeId for a module is it's full class name (see Module.getTypeId()):
ObjectMapper objectMapper = new ObjectMapper();
//it will print the modules registered
System.out.println(mapper.getRegisteredModuleIds());
If you are interested to check which serializer will be used for YourClass class, you can invoke the ObjectMapper#getSerializerProviderInstance returning a SerializerProvider instance that may be used for accessing serializers with the findValueSerializer method:
//it will print your serializer if you have defined one for your class
//otherwise one of the jackson java classes used for serialization
System.out.println(mapper.getSerializerProviderInstance()
.findValueSerializer(YourClass.class));
Related
I have a custom jackson serializer, and it works for serializing single pojos. Im trying to serialize a list of objects. Without the custom serializer I can just do:
public List<Sale> getAllSales() {
return saleRepository.getAll();
}
which works fine, but I want to return a very specific set of data, so I made a custom serializer, which also works but only for single objects:
public Sale getSale(int id) {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Sale.class, new SaleSerializer());
mapper.registerModule(module);
Sale sale = saleRepository.findById(1).orElse(null);
return mapper.writeValueAsString(sale);
}
How do I do the implement the custom serializer for a list of objects?
I'm not sure if this is the best way to do this, but I ended up doing it this way.
public ArrayNode getAllSalesToday() throws JsonProcessingException {
LocalDate localDate = LocalDate.now();
LocalDateTime startOfDay = localDate.atStartOfDay();
LocalDateTime endOfDay = localDate.atTime(LocalTime.MAX);
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Sale.class, new SaleSerializer());
mapper.registerModule(module);
List<Sale> saleList = saleRepository.getAllByInitialDepositDateIsBetween(startOfDay,endOfDay);
ArrayNode arrayNode = mapper.createArrayNode();
for (Sale sale: saleList){
String x = mapper.writeValueAsString(sale);
JsonNode jsonNode = mapper.readTree(x);
arrayNode.add(jsonNode);
}
return arrayNode;
}
Using spring hateoas 1.0.3 with a traverson client is causing problems when the rest-entity has an attribute of type "java.time.Instant".
The error i get is
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `java.time.Instant`
I found that the HttpMessageConverter used inside of the RestTemplate in traverson has only the Jackson2HalModule registered.
Is there a way that i can also register the jackson-modules-java8 module in traverson?
Or is there a way that i can register the Jackson2HalModule in my restTemplate outside of traverson?
Following worked for me in spring-hateoas 1.1.2.RELEASE
private static RestTemplate getRestTemplate() {
List<HttpMessageConverter<?>> httpMessageConverters = SpringFactoriesLoader.loadFactories(TraversonDefaults.class,
Traverson.class.getClassLoader()).get(0).getHttpMessageConverters(Collections.singletonList(MediaTypes.HAL_JSON));
Optional<HttpMessageConverter<?>> first = httpMessageConverters.stream().filter(i -> i instanceof MappingJackson2HttpMessageConverter)
.findFirst();
if (first.isPresent()) {
MappingJackson2HttpMessageConverter httpMessageConverter = (MappingJackson2HttpMessageConverter) first.get();
httpMessageConverter.getObjectMapper().registerModule(new JavaTimeModule());
}
return new RestTemplateBuilder().messageConverters(httpMessageConverters).build();
}
and then using it:
Traverson traverson = new Traverson(URI.create("http://localhost:8080"), MediaTypes.HAL_JSON);
traverson.setRestOperations(getRestTemplate());
After some investigation i found a solution that works for me.
The background is that the traverson client registers a MappingJackson2HttpMessageConverter with contains the Jackson2HalModule.
To fix the problem i had to register also the JavaTimeModule.
I did the following
RestTemplateBuilder genericBuilder = this.restTemplateBuilder
.setConnectTimeout(Duration.ofMillis(configuration.getSecurityRestConnectTimeout()))
.setReadTimeout(Duration.ofMillis(configuration.getSecurityRestReceiveTimeout()));
// my normal restTemplate
RestTemplateBuilder restTemplate = genericBuilder
.defaultMessageConverters()
.build();
// HAL specific restTemplate
RestTemplateBuilder restTemplateHal = genericBuilder
.messageConverters(getHalConverter(Arrays.asList(MediaTypes.HAL_JSON)))
.build();
The HalConverter is generated like this (registering also JavaTimeModule):
private static HttpMessageConverter<?> getHalConverter(List<MediaType> halFlavours) {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jackson2HalModule());
mapper.registerModule(new JavaTimeModule());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(mapper);
converter.setSupportedMediaTypes(halFlavours);
return converter;
}
Then you can use Traverson in setting the just generated restTemplateHal
Traverson traverson = new Traverson(uri, MediaTypes.HAL_JSON);
traverson.setRestOperations(restTemplateHal);
MyClass myclass = traverson.follow().toObject(MyClass.class);
I have a spring MVC config with the following:
public class SpringConfiguration extends WebMvcConfigurerAdapter {
public MappingJackson2HttpMessageConverter jacksonMessageConverter() {
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
//Registering Hibernate4Module to support lazy objects
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.registerModule(new Hibernate4Module());
messageConverter.setObjectMapper(mapper);
return messageConverter;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//Here we add our custom-configured HttpMessageConverter
converters.add(jacksonMessageConverter());
super.configureMessageConverters(converters);
}
}
The previous method used to ignore all lazy relation without adding JsonIgnore in model
The problem is I have a route to steam mp3 file as an octet response as following
#GetMapping(value = "/audio/{id}")
public ResponseEntity<byte[]> streamMp3FileToAdmin(#PathVariable Integer id) {
CorporateCampaign camp = corporateCampaignService.findById(id);
final HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
return new ResponseEntity<byte[]>(Utilities.getFileAsBytes(camp.getVoiceUrl()),httpHeaders,HttpStatus.OK);
}
If I remove jackson message converter the steaming works fine but when I add jackson message converter the stream doesn't work any more
I read this question Spring MVC: How to return image in #ResponseBody?
and a lot but I didn't find a solution yet
You need to add produces = MediaType.APPLICATION_OCTET_STREAM to the #GetMapping(value = "/audio/{id}") to specify produced result content type and let browser recognize it properly.
I have a problem in my webservice controller, due to jackson's serialisation of a third party object.
java.lang.IllegalArgumentException: Conflicting setter definitions for
property "X": ThirdPartyClass#setX(1 params) vs ThirdPartyClass#setX(1
params)
I've read that you can solve it thanks to MixIn annotation.
In my controller i'm giving a list, i'd like to know if there is a way to automatically define somewhere the use of the MixInAnnotation ?
If i had to do return a String instead of objects, i'd do something like that:
ObjectMapper mapper = new ObjectMapper();
mapper.getSerializationConfig().addMixInAnnotations(xxx);
return mapper.writeValueAsString(myObject);
Nevertheless, my controller is giving List:
#RequestMapping(method = RequestMethod.GET)
public #ResponseBody List<MyObject> getMyObjects
and several times returning MyObject in other methods, and so i'd like to declare only one time the use of the MixInAnnotation for jackson serialisation ?
Thank you,
RoD
I suggest that you use the "Spring Way" of doing this by following the steps provided in the Spring Docs.
If you want to replace the default ObjectMapper completely, define a #Bean of that type and mark it as #Primary.
Defining a #Bean of type Jackson2ObjectMapperBuilder will allow you to customize both default ObjectMapper and XmlMapper (used in MappingJackson2HttpMessageConverter and MappingJackson2XmlHttpMessageConverter respectively).
Another way to customize Jackson is to add beans of type com.fasterxml.jackson.databind.Module to your context. They will be registered with every bean of type ObjectMapper, providing a global mechanism for contributing custom modules when you add new features to your application.
Basically this means that if you simply register a Module as a bean with the provided mixin-settings you should be all set and there will be no need to define your own ObjectMapper or to alter the HttpMessageConverters.
So, in order to do this, i customised the Jackson JSON mapper in Spring Web MVC.
Custom mapper:
#Component
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
this.addMixInAnnotations(Target.class, SourceMixIn.class);
}
}
Register the new mapper at start up of spring context:
#Component
public class JacksonInit {
#Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
#Autowired
private CustomObjectMapper objectMapper;
#PostConstruct
public void init() {
List<HttpMessageConverter<?>> messageConverters = requestMappingHandlerAdapter.getMessageConverters();
for (HttpMessageConverter<?> messageConverter : messageConverters) {
if (messageConverter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter m = (MappingJackson2HttpMessageConverter) messageConverter;
m.setObjectMapper(objectMapper);
}
}
}
}
Thanks to that, i didn't modify my WebService Controller.
I started with a service that consumes and produces output in JSON. I use the resteasy-jackson-provider for (de)marshalling which takes its information from the class description. After a while I was asked to add XML as MediaType. So I annotated my DTOs with JAXB annotations and added the resteasy-jaxb-provider. As a result, I observed that the produced JSON output derives from the JAXB annotations which differs from the original format.
I am on RestEasy Version 3.0.4. As described I use the following providers
resteasy-jackson-provider
resteasy-axb-provider.
resteasy-jettison-provider, because I integrated RestEasy into Spring and this provider is a transitive dependency.
I got aware of the problem when I
used XmlElementWrapper for lists and when
I wrote a custom XmlAdapter which serializes a complex data structure Map<String, List<String>>. Requests with XML MediaType are fine. Requests with JSON MediaType cause an exception. Jackson seems to exploit the XmlAdapter for further information. This was not the case before. Jackson was able to marshall the Map without the JAXB annotations.
org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "customer" (Class x.y.z.OptionalParametersMapType), not marked as ignorable
at [Source: org.apache.catalina.connector.CoyoteInputStream#77119553; line: 1, column: 131] (through reference chain: x.y.z.Request["optional"]->x.y.zOptionalParametersMapType["customer"]
)
at org.codehaus.jackson.map.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:53)
at org.codehaus.jackson.map.deser.StdDeserializationContext.unknownFieldException(StdDeserializationContext.java:267)
at org.codehaus.jackson.map.deser.std.StdDeserializer.reportUnknownProperty(StdDeserializer.java:673)
at org.codehaus.jackson.map.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:659)
at org.codehaus.jackson.map.deser.BeanDeserializer.handleUnknownProperty(BeanDeserializer.java:1365)
at org.codehaus.jackson.map.deser.BeanDeserializer._handleUnknown(BeanDeserializer.java:725)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:703)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
at org.codehaus.jackson.xc.XmlAdapterJsonDeserializer.deserialize(XmlAdapterJsonDeserializer.java:59)
at org.codehaus.jackson.map.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:299)
at org.codehaus.jackson.map.deser.SettableBeanProperty$FieldProperty.deserializeAndSet(SettableBeanProperty.java:579)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:697)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
at org.codehaus.jackson.map.ObjectMapper._readValue(ObjectMapper.java:2704)
at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1315)
at org.codehaus.jackson.jaxrs.JacksonJsonProvider.readFrom(JacksonJsonProvider.java:419)
So, how can I prevent RestEasy from using the JAXB annotations for marshalling to and from JSON?
Here is the request class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "domainRecommendationRequest")
public class Request {
#XmlJavaTypeAdapter(OptionalParametersXmlAdapter.class)
private Map<String, List<String>> optional = new HashMap<>();
}
Here is the XmlAdapter:
#Override
public class OptionalParametersXmlAdapter extends XmlAdapter<OptionalParametersMapType, Map<String, List<String>>> {
public OptionalParametersMapType marshal(Map<String, List<String>> v) throws Exception {
OptionalParametersMapType result = new OptionalParametersMapType();
List<OptionalParameterItemType> optionalParameterItemTypes = new ArrayList<>();
Set<String> keySet = v.keySet();
for (String parameterName : keySet) {
OptionalParameterItemType item = new OptionalParameterItemType();
item.name = parameterName;
item.values = v.get(parameterName);
optionalParameterItemTypes.add(item);
}
result.parameter = optionalParameterItemTypes;
return result;
}
}
Here is the wrapper for the map:
public class OptionalParametersMapType {
public List<OptionalParameterItemType> parameter = new ArrayList<>();
}
Here is the actual map entry item:
public class OptionalParameterItemType {
#XmlAttribute
public String name;
#XmlElementWrapper(name = "values")
#XmlElement(name = "value")
public List<String> values = new ArrayList<>();
}
This is what I expect in the JSON request:
{"optional":{"customer":["Mike"]}}
As you can see, I do intend to have a different format in XML.
The problem is resteasy-jackson-provider depends on jackson-module-jaxb-annotations, which is used to map JAXB annotations/annotated classes to JSON. Now in a normal explicit use of ObjectMapper, in order to make use of this module, we would need to explicitly register this module like (See here)
ObjectMapper objectMapper = new ObjectMapper();
JaxbAnnotationModule module = new JaxbAnnotationModule();
objectMapper.registerModule(module);
-- OR --
AnnotationIntrospector introspector = new JaxbAnnotationIntrospector();
objectMapper.setAnnotationIntrospector(introspector);
That being said, it appears (not confirmed with any facts, but looks probable) that when the ObjectMapper is being created for your serialization, when the JAXB annotations are noticed, the module is automatically registered.
I don't know of any possible annotations we can use to stop this, but one way to solve this problem is to create a ContextResolver for the ObjectMapper, where we don't register the JAXB module.
#Provider
public class ObjectMapperContextResolver
implements ContextResolver<ObjectMapper> {
#Override
public ObjectMapper getContext(Class<?> type) {
return new ObjectMapper();
}
}
Once we register that with our JAX-RS application, it will be the context resolver used to get the ObjectMapper. We could configure the ObjectMapper further, but this is just an example. Test it and it works as expected.