I have a class:
package my.package;
public class TreeItem{
...
}
When I want to serialize a Class literal like TreeItem.class the jackson serializes it as:
"class my.package.TreeItem"
I want to serialize it as
"my.package.TreeItem"
EDIT:
public class TreeConfigMap extends HashMap<Class<? extends TreeItem>, Map<String, Object>>{
//empty
}
TreeController: (a rest controller)
#RequestMapping("/config")
public TreeConfigMap config(...){
TreeConfigMap config= new TreeConfigMap();
Map<String,Object> m = new HashMap<>();
m.put("ali","gholi");
config.put(TreeItem.class,m);
return config;
}
The output is:
{"class my.package.TreeItem":{"ali":"gholi"}}
You can use custom key serializer.
Annotate your TreeConfigMap to use custom key serializer
#JsonSerialize(keyUsing = ClassNameSerializer.class)
public static class TreeConfigMap extends HashMap<Class<? extends TreeItem>, Map<String, Object>>{
//empty
}
Alternatively, you register the serializer with ObjectMapper
ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addKeySerializer(Class.class, new ClassNameSerializer());
mapper.registerModule(simpleModule);
...
mapper.writeValueAsString(treeConfigMap)
Here is serializer code
public static class ClassNameSerializer extends JsonSerializer<Class> {
#Override
public void serialize(Class value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeFieldName(value.getCanonicalName());
}
}
Related
I have JSON that looks something like this (the full JSON is much larger):
{
"legalLastName": "lastName",
"legalFirstName": "firstName",
"terminationDate": null,
"collegeEducation": [{
"major": "Finance",
"school": "Towson University",
"quarter": null,
"degreeType": "B.S.",
"yearEarned": "1990",
"degreeLevel": "Undergraduate"
}]
}
How do I use the ObjectMapper to apply custom serialization to collegeEducation? I can't use the annotation JsonSerializer, because the POJO is generated, and that library doesn't provide a way to apply that annotation. So I'd like to use the default ObjectMapper serialization for all fields but the list.
Objective: A field in Education named as degreeType will be serialized to json field degree
Mixin or Custom serializer can be used
Student and Education class
Lets define the Student and Education class as follows
static class Education {
String major;
String degreeType;
}
static class Student {
String legalLastName;
String legalFirstName;
List<Education> educationList;
}
Mixin
Use mixin to define a parallel class with updated field specifications
Mixin class for Education
Define the field in the actual object String degreeType;
Add the custom field name to be serialized to #JsonProperty("degree")
abstract class EducationMixin {
#JsonProperty("degree")
String degreeType;
}
Configure ObjectMapper with Mixin
Enable field visibility(as the class does not have getter/setter)
Register the education mixin
static void withMixin(Student student) throws Exception {
ObjectMapper studentMapper = new ObjectMapper();
studentMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
studentMapper = studentMapper.addMixIn(Education.class, EducationMixin.class);
System.out.println(studentMapper.writeValueAsString(student));
}
Or Custom Serializer
Custom serializer's can be used to serialize java objects.
Add custom serializer for Education
Add the custom serializer class to Serialize Education
static class EducationSerializer extends JsonSerializer<Education> {
#Override
public void serialize(Education education, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeStartObject();
gen.writeStringField("major", education.major);
gen.writeStringField("degree", education.degreeType);
gen.writeEndObject();
}
}
Initialize ObjectMapper with necessary configuration
Enable field visibility(as the class does not have getter/setter)
Register the education serializer
static void withCustom(Student student) throws Exception {
ObjectMapper studentMapper = new ObjectMapper();
studentMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
SimpleModule studentModule = new SimpleModule();
studentModule.addSerializer(Education.class, new EducationSerializer());
studentMapper.registerModule(studentModule);
System.out.println(studentMapper.writeValueAsString(student));
}
Full working code
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class MixinTest {
public static void main(String[] args) throws Exception {
Student student = new Student();
student.legalFirstName = "first";
student.legalLastName = "last";
Education education = new Education();
education.degreeType = "degreetypevalue";
education.major = "majorvalue";
student.educationList = new ArrayList<>();
student.educationList.add(education);
withMixin(student);
withCustom(student);
}
static void withMixin(Student student) throws Exception {
ObjectMapper studentMapper = new ObjectMapper();
studentMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
studentMapper = studentMapper.addMixIn(Education.class, EducationMixin.class);
System.out.println(studentMapper.writeValueAsString(student));
}
static void withCustom(Student student) throws Exception {
ObjectMapper studentMapper = new ObjectMapper();
studentMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
SimpleModule studentModule = new SimpleModule();
studentModule.addSerializer(Education.class, new EducationSerializer());
studentMapper.registerModule(studentModule);
System.out.println(studentMapper.writeValueAsString(student));
}
static class Education {
String major;
String degreeType;
}
static class Student {
String legalLastName;
String legalFirstName;
List<Education> educationList;
}
abstract class EducationMixin {
#JsonProperty("degree")
String degreeType;
}
static class EducationSerializer extends JsonSerializer<Education> {
#Override
public void serialize(Education education, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeStartObject();
gen.writeStringField("major", education.major);
gen.writeStringField("degree", education.degreeType);
gen.writeEndObject();
}
}
}
I am trying to implement generic Spring trim serializer across application however its doesn't seems to be working.
And if I manually put this serializer #JsonSerialize(using = StringTrimmerSerializer.class) on a particular field it does work not sure what i need to do to make it work throughout application without putting it for all fields individually
import java.io.IOException;
import org.apache.commons.lang.StringUtils;
import org.springframework.boot.jackson.JsonComponent;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
#JsonComponent
public class StringTrimmerSerializer extends JsonSerializer<String> {
#Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (!StringUtils.isEmpty(value)) {
value = value.trim();
}
gen.writeString(value);
}
}
Update:
Tried registering serializer as well but same issue
#Configuration
public class JacksonConfiguration {
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper(); //
//mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
mapper.registerModule(new SimpleModule().addSerializer(String.class, new StringTrimmerSerializer()));
return mapper;
}
/*
* #Bean public Module customSerializer() { SimpleModule module = new
* SimpleModule(); module.addSerializer(String.class, new
* StringTrimmerSerializer()); return module; }
*/
}
Main Class package : com.demo
Serializer Package : com.demo.config
Spring boot Version - 2.2.5.RELEASE
Jackson-databind - 2.10.2
Add constructors in StringTrimmerSerializer
public StringTrimmerSerializer ()
public StringTrimmerSerializer (Class<String> s) {
super(s);
}
I was able to resolve by registering custom serializer to jaskcosn's default object mapper rather than creating a new reference of ObjectMapper.
#Configuration
public class JacksonConfiguration extends WebMvcConfigurationSupport {
#Autowired
private ObjectMapper objectMapper;
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
objectMapper.registerModule(new SimpleModule().addSerializer(String.class, new StringTrimmerSerializer()));
converter.setObjectMapper(objectMapper);
return converter;
}
#Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}
}
I'm trying to figure how to explain Jersey and Jackson how to deserialize a Future that I pass as byte[].
I create my own ContextResolver
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private static ObjectMapper mapper = null;
public ObjectMapperContextResolver() {
mapper = ObjectMapperFactory.getObjectMapper();
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
And the implementation of the ObjectMapper
public static ObjectMapper getObjectMapper() {
ObjectMapper defaultObjectMapper = new ObjectMapper();
SimpleModule futureModule = new SimpleModule("FutureModule");
futureModule.<Future>addDeserializer(Future.class, new FutureDeserializer<String>());
defaultObjectMapper.registerModule(futureModule);
return defaultObjectMapper;
}
And then finally in the implementation of my FutureDeserializer
public class FutureDeserializer<T> extends StdDeserializer<Future<T>>{
public FutureDeserializer() {
super(Future.class);
}
#Override
public Future<T> deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
ObjectMapper mapper=(ObjectMapper)jp.getCodec();
//TODO: Breakpoint never stop here
return null;
}
}
Then I register in my ResourceConfig before start the JerseyTest
ResourceConfig rc = new ResourceConfig();
rc.register(SpringLifecycleListener.class);
rc.register(RequestContextFilter.class);
rc.register(new JacksonFeature());
rc.register(new ObjectMapperContextResolver());
But when I run the test the ObjectMapperContextResolver is invoked and the mapper returned to Jersey, but he never use the FutureDeserializer.
Any idea what I´m doing wrong?
I am using Spring Boot and it has it's own Jackson's default serialization.
It works not correctly in scope of my task.
So I want override Jackson's default serialization with my own custom serializator.
Here is my code:
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
return new Jackson2ObjectMapperBuilder() {
#Override
public void configure(ObjectMapper objectMapper) {
super.configure(objectMapper);
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(ZonedDateTime.class, new ZonedDateTimeCustomSerializer());
objectMapper.registerModule(simpleModule);
objectMapper.configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false);
}
}.serializerByType(ZonedDateTime.class, new JsonSerializer<ZonedDateTime>() {
#Override
public void serialize(ZonedDateTime value,
JsonGenerator gen,
SerializerProvider serializerProvider) throws IOException {
gen.writeString(value.getNano() + "");
}
});
}
private class ZonedDateTimeCustomSerializer extends JsonSerializer<ZonedDateTime> {
#Override
public void serialize(ZonedDateTime value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(value.getNano() + "");
}
}
As you can see I tried some cases such as
registering custom serializator through SimpleModule
overriding Jackson2ObjectMapperBuilder#serialize
Please tip me how to override default Jackson serializator
#Slowpokebreakingnews
If point is to add custom serializers - how about to customize Jackson2ObjectMapperBuilde by Jackson2ObjectMapperBuilderCustomizer:
#Bean
public Jackson2ObjectMapperBuilderCustomizer customizer()
{
return new Jackson2ObjectMapperBuilderCustomizer()
{
#Override
public void customize(Jackson2ObjectMapperBuilder builder)
{
builder.serializerByType(CustomPojo.class,
new CustomSerializator<CustomPojo>());
//affects to all dates in all pojos (I hope :) )
builder.indentOutput(true).dateFormat(new SimpleDateFormat
("yyyy-MM-dd'T'HH:mm:ssXXX"));
}
};
}
For without-Spring-boot-configuration I override configureMessageConverters() :
#Configuration
#EnableWebMvc
...
public class WebConfig extends WebMvcConfigurerAdapter
{
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters)
{
Jackson2ObjectMapperBuilder builder = new CustomObjectMapperBuilder();
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
}
and define my CustomJackson2ObjectMapperBuilder:
public class CustomObjectMapperBuilder extends Jackson2ObjectMapperBuilder
{
#Override
public void configure(ObjectMapper objectMapper)
{
super.configure(objectMapper);
SimpleModule module = new SimpleModule();
module.addSerializer(Selective.class, new SelectiveSerializer());
objectMapper.registerModule(module)
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.configure(JsonParser.Feature.ALLOW_COMMENTS, true)
.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true)
.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true)
.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"));
}
}
For serializing date format you can do something like this
#Component
public class JsonDateSerializer extends JsonSerializer<Date>{
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy");//yuor desired format goes here
#Override
public void serialize(Date date, JsonGenerator gen, SerializerProvider provider)
throws IOException, JsonProcessingException {
String formattedDate = dateFormat.format(date);
gen.writeString(formattedDate);
}
}
You can do like your bean too.
And you have another option doing in your POJO like this
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm a z")
private Date date;
I've got a class that configures a Jackson ObjectMapper. It adds in some custom serializers and deserializers for my object types as follows:
public class JsonMapperFactory {
public static ObjectMapper createObjectMapper() {
final SimpleModule module = new SimpleModule("customerSerializationModule", new Version(1, 0, 0, "static version"));
addCustomDeserializersTo(module);
addCustomSerializersTo(module);
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
return objectMapper;
}
private static void addCustomSerializersTo(final SimpleModule module) {
module.addSerializer(DateTime.class, new DateTimeSerializer());
}
private static void addCustomDeserializersTo(final SimpleModule objectMapper) {
objectMapper.addDeserializer(DateTime.class, new DateTimeDeserializer());
}
}
I've tested my customer serializers within their own test classes, so in my test of this JsonMapperFactory class, I'm trying to simply check that the ObjectMapper created has the expected serializers (or deserializers) This could be achieve by introspecting the ObjectMapper, but it doesn't seem to have any mechanisms to do this.
Does anyone know of a nice way to test that?
For deserializers, I have the following:
private void assertThatObjectMapperUsesCorrectDeserializer(final Class<?> typeClazz, final Class<?> deserializerClazz) throws JsonMappingException {
final DeserializationConfig deserializationConfig = this.objectMapper.getDeserializationConfig();
final JsonDeserializer<Object> deserializer = this.objectMapper.getDeserializerProvider().findTypedValueDeserializer(deserializationConfig, javaTypeFor(typeClazz), null);
assertThat(deserializer, is(instanceOf(deserializerClazz)));
}
private JavaType javaTypeFor(final Class<?> clazz) {
return TypeFactory.type(clazz); //deprecated method :(
}
Which is quite verbose and uses deprecated methods.
I'm yet to find a way to do a similar test for the serializers. So I've currently resorted to serializing an object and check it serializes correctly (essentially duplicating the serializer test)
Any ideas are very welcome.
From the answers & comments provided here, I recently redesigned the class to use builders for both the Module and the ObjectMapper. This allowed me to provide mocks and check that the correct (de)serializers were added to the module and then the module is registered to the object mapper as expected.
Object Mapper Builder:
public class ObjectMapperBuilder {
ObjectMapper mapper;
public ObjectMapperBuilder configure(final ObjectMapper mapper) {
this.mapper = mapper;
return this;
}
public ObjectMapperBuilder withModule(final Module module) {
this.mapper.registerModule(module);
return this;
}
public ObjectMapper build() {
return this.mapper;
}
}
Module Builder:
public class SimpleModuleBuilder {
SimpleModule module;
public SimpleModuleBuilder configure(final SimpleModule module) {
this.module = module;
return this;
}
public <X> SimpleModuleBuilder withSerializer(final Class<X> clazz, final JsonSerializer<X> serializer) {
this.module.addSerializer(clazz, serializer);
return this;
}
public <X> SimpleModuleBuilder withDeserializer(final Class<X> clazz, final JsonDeserializer<X> deserializer) {
this.module.addDeserializer(clazz, deserializer);
return this;
}
public SimpleModule build() {
return this.module;
}
}
And finally, the new JsonMapperFactory:
public class JsonMapperFactory {
public static ObjectMapper configureObjectMapper(final ObjectMapper mapper, final SimpleModule module) {
final SimpleModuleBuilder modulebuilder = new SimpleModuleBuilder();
final SimpleModule configuredModule = modulebuilder.configure(module)
.withSerializer(DateTime.class, new DateTimeSerializer())
.withDeserializer(DateTime.class, new DateTimeDeserializer())
.build();
final ObjectMapperBuilder objectMapperBuilder = new ObjectMapperBuilder();
return objectMapperBuilder.configure(mapper).withModule(configuredModule).build();
}
}
The factory method is still used within Spring configuration, but the configuration now instantiates the blank Module and ObjectMapper before providing them to the factory methods that then configure them.
If JsonDeserializer (and DateTimeDeserializer too) was an interface, you could easily "JMock" it, pass mocked instance to JsonMapperFactory#createObjectMapper and then expect exactly 1 invocation of your custom "serialize" method; e.g.
DateTimeSerializer serializer = context.mock(DateTimeSerializer.class);
DateTimeDeserializer serializer = context.mock(DateTimeDeserializer.class);
ObjectMapper mapper = JacksonMapperFactory.createObjectMapper(deserializer, serializer);
exactly(1).of(jsonDeserializer).serialize(myDateTime,
with(any(JsonGenerator.class),
with(any(SerializerProvider.class)))
Being a concrete class, you can instead define a new (test-scoped) De/Serializer that extends your custom DateTime(De)serializer, and simply count invocation on that:
private static class DateTimeDeserializerWithCounter extends DateTimeDeserializer {
public int counter = 0;
#Override
public DateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
counter++;
return super.deserialize(jsonParser, deserializationContext);
}
}
#Test
public void usageTest(){
//init mapper with the above DateTimeDeserializerWithCounter - see below
mapper.readValue("...", DateTime.class);
Assert.assertEquals(1, deserializer.counter);
}
Below a snapshot of a more "test-oriented" Factory:
//package visibility, to allow passing different De/Serializers while testing
static ObjectMapper createObjectMapper(JsonDeserializer deserializer, JsonSerializer serializer) {
final SimpleModule module = new SimpleModule("customerSerializationModule", new Version(1, 0, 0, "static version"));
module.addDeserializer(DateTime.class, deserializer);
module.addSerializer(DateTime.class, serializer);
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
return objectMapper;
}
//production method: no-args, as in the original version
public static ObjectMapper createObjectMapper() {
return createObjectMapper(new DateTimeDeserializer(), new DateTimeSerializer());
}
Hope that helps.