How to serialize List within object without annotation? - java

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();
}
}
}

Related

Jackson deserialization: Can I inject a value with an annotation on the field of the to deserializable object?

I have an object like this to deserialize:
public class RelationsInput {
Relation relation1;
Relation relation2;
}
whereas the class Relation looks like this:
public class Relation {
RelationType relationtype;
... (more fields)
}
RelationType is en enum and is not a value which will be deserialized, while all others are.
Is it possible, that I could "inject" the enum value for the field relationType with an annotation on the field in the class RelationInput?
Like the following
public class RelationsInput {
#RelationType(RelationType.OWNER)
Relation owner;
#RelationType(RelationType.TENANT)
Relation tenant;
}
Does Jackson provide something like this?
You can try to implement custom deserialiser with com.fasterxml.jackson.databind.deser.ContextualDeserializer interface. It allows to create deserialiser instance with a context.
See below example:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.json.JsonMapper;
import lombok.Data;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class JsonContextualDeserializerApp {
public static void main(String[] args) throws IOException {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = JsonMapper.builder().build();
RelationsInput info = mapper.readValue(jsonFile, RelationsInput.class);
System.out.println(info.toString());
}
}
#Data
class RelationsInput {
#JsonDeserialize(using = RelationStdDeserializer.class)
#RelationTypeInfo(RelationType.OWNER)
private Relation owner;
#JsonDeserialize(using = RelationStdDeserializer.class)
#RelationTypeInfo(RelationType.TENANT)
private Relation tenant;
}
#Data
class Relation {
private int id;
private RelationType relationtype;
}
enum RelationType {OWNER, TENANT}
#Retention(RetentionPolicy.RUNTIME)
#interface RelationTypeInfo {
RelationType value();
}
class RelationStdDeserializer extends StdDeserializer<Relation> implements ContextualDeserializer {
private RelationType propertyRelationType;
public RelationStdDeserializer() {
this(null);
}
public RelationStdDeserializer(RelationType relationType) {
super(Relation.class);
this.propertyRelationType = relationType;
}
#Override
public Relation deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonDeserializer<Object> deser = ctxt.findRootValueDeserializer(ctxt.getTypeFactory().constructType(Relation.class));
Relation instance = (Relation) deser.deserialize(p, ctxt);
if (this.propertyRelationType != null) {
instance.setRelationtype(this.propertyRelationType);
}
return instance;
}
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
RelationTypeInfo typeInfo = property.getMember().getAllAnnotations().get(RelationTypeInfo.class);
return new RelationStdDeserializer(typeInfo.value());
}
}
Above code for a payload:
{
"owner": {
"id": 1
},
"tenant": {
"id": 2
}
}
prints:
RelationsInput(owner=Relation(id=1, relationtype=OWNER), tenant=Relation(id=2, relationtype=TENANT))
See also:
Deserialize to String or Object using Jackson
How to inject dependency into Jackson Custom deserializer
Jackson - deserialize inner list of objects to list of one higher level
I am afraid there is not such thing. If you want to use something annotation like you could use custom de-serializers. Create first something like:
#RequiredArgsConstructor
public abstract class RelationTypeDeserializer extends JsonDeserializer<Relation> {
private final RelationType relationType;
#Override
public Relation deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
Relation r = p.readValueAs(Relation.class);
r.setRelationtype(relationType);
return r;
}
}
Then implement the actual ones:
public class OwnerDeserializer extends RelationTypeDeserializer {
public OwnerDeserializer() {
super(RelationType.OWNER);
}
}
and
public class TenantDeserializer extends RelationTypeDeserializer {
public TenantDeserializer() {
super(RelationType.TENANT);
}
}
then use those like:
#Getter #Setter
public class RelationsInput {
#JsonDeserialize(using = OwnerDeserializer.class)
private Relation owner;
#JsonDeserialize(using = TenantDeserializer.class)
private Relation tenant;
}
If you do what you ask for, you will always have the same value for the RelationType field. Anyway, one posible solution is using personalized serializer-deserializer like this:
public class RelationTypeJsonSerializer extends JsonSerializer<RelationType> {
#Override
public void serialize(RelationType value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
String string = value.toString();//or something like that, convert the enum to string as you like
gen.writeString(string);
}
}
public class RelationTypeJsonDeserializer extends JsonDeserializer<RelationType> {
#Override
public RelationType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String toString = p.getCodec().readValue(p, String.class);//read the value as string
return RelationType.build(toString);//convert back the value to object, use switch if needed)
}
}
ObjectMapper om = new ObjectMapper();
SimpleModule localDateModule = new SimpleModule("RelationType Module");
localDateModule.addSerializer(RelationType.class, new RelationTypeJsonSerializer());
localDateModule.addDeserializer(RelationType.class, new RelationTypeJsonDeserializer());
om.registerModule(localDateModule);
To convert the enum back and forward I recomend the use of map<String, RelationType>, super simple and work perfect, something like this:
Map<String, RelationType> map = new HashMap<String, RelationType>();
map.put("Some Type", RelationType.SOME_TYPE);
map.put("Some Other Type", RelationType.SOME_OTHER_TYPE);
And use the get(string) to serialize
And get(value) to deserialize (find the key of some value)
This is a general example when you want to serialize-deserialize something that don't have a default serializer-deserializer
Look at this for more info
How I parse Color java class to JSON with Jackson?

Spring boot generic String trim Serialization

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);
}
}

SPRING REST: Removing empty objects from response in JSON format

I don't have option of spring.xml so i went by annotated method.
I have below REST Interfaces in package : com.dpk.cm.impl.ecommerce.rest
and implementation in com.dpk.cm.impl.ecommerce.rest.services
I created one spring config class: but seems like i am still seeing in my JSON response empty objects.
Below is my code :
#Configuration
#ComponentScan(basePackages = "com.dpk.cm.impl.ecommerce.rest")
#EnableWebMvc
public class SpringConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
final ObjectMapper objectMapper = new ObjectMapper();
// objectMapper.setSerializationInclusion(Inclusion.NON_EMPTY);
objectMapper.setSerializationInclusion(Include.NON_EMPTY);
converter.setObjectMapper(objectMapper);
converters.add(converter);
super.configureMessageConverters(converters);
}
}
How to remove the Empty Objects from the JSON Reponse Object.
I had similar requirement, but though I use CXF framework on spring boot, there spring boot was creating ObjectMapper which was overriding configuration. Hence I was manually create ObjectMapper as shown below.
#Bean(name = "objectMapper")
public ObjectMapper getObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(
SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED, false);
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY,
true);
mapper.setSerializationInclusion(Include.NON_NULL);
return mapper;
}
You can create your custom serializer where you can add a condition on serialization of the object.
Model
#JsonSerialize(using = IgnoreEmptyPersonSerializer.class)
public class Person {
private String name;
private String address;
public Person(String name, String address){
this.name = name;
this.address = address;
}
...
//setters and getters
...
}
Custom Serializer
public class IgnoreEmptyPersonSerializer extends JsonSerializer<Person> {
#Override
public void serialize(Person value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
String name = value.getName();
String address = value.getAddress();
//Dont serialize it if it is empty
if((name == null || name.trim().equals("")) &&
(address == null || address.trim().equals(""))){
return;
}
jgen.writeStartObject();
jgen.writeFieldName("name");
jgen.writeString(value.getName());
jgen.writeFieldName("address");
jgen.writeString(value.getAddress());
jgen.writeEndObject();
}
}

jackson serialize class literal

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());
}
}

How to serialize declarative links (jersey) with jackson

I am using declarative linking in my project. My jackson mapper configuration is
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
mapper.configure(MapperFeature.AUTO_DETECT_FIELDS, false);
mapper.configure(MapperFeature.AUTO_DETECT_IS_GETTERS, false);
mapper.configure(MapperFeature.AUTO_DETECT_GETTERS, false);
mapper.configure(MapperFeature.AUTO_DETECT_SETTERS, false);
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
As I have disabled any kind of auto detection, injected links like
#InjectLinks({
#InjectLink(rel = "bookmark", resource = ConnectionsResource.class, style = Style.ABSOLUTE_PATH) })
#JsonProperty("links")
Link[] links;
are serialized to an empty JSON object (because none of the fields in "Link" is annotated with #JsonProperty).
How to enable serialization for Links only for the fields rel and href without changing my global mapper configuration?
So one way to make this work is to use a customer serializer. You would have to add a new module for this serializer to the ObjectMapper, but this should not effect the rest of the configurations.
Here's the serializer
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import javax.ws.rs.core.Link;
public class LinkSerializer extends JsonSerializer<Link>{
#Override
public void serialize(Link link, JsonGenerator jg, SerializerProvider sp)
throws IOException, JsonProcessingException {
jg.writeStartObject();
jg.writeStringField("rel", link.getRel());
jg.writeStringField("href", link.getUri().toString());
jg.writeEndObject();
}
}
Here a test class
public class TestClass {
#JsonProperty("links")
protected List<Link> links;
protected String name;
protected String id;
// getter and setters
}
And the test run
public static void main(String[] args) throws Exception{
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
mapper.configure(MapperFeature.AUTO_DETECT_FIELDS, false);
mapper.configure(MapperFeature.AUTO_DETECT_IS_GETTERS, false);
mapper.configure(MapperFeature.AUTO_DETECT_GETTERS, false);
mapper.configure(MapperFeature.AUTO_DETECT_SETTERS, false);
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Link.class, new LinkSerializer());
mapper.registerModule(simpleModule);
Link link1 = Link.fromUri(URI.create("http://localhost:8080/")).rel("one").build();
Link link2 = Link.fromUri(URI.create("http://localhost:8080/")).rel("two").build();
TestClass test = new TestClass();
test.getLinks().add(link1);
test.getLinks().add(link2);
String json = mapper.writeValueAsString(test);
System.out.println(json);
}
produces this result
{
"links" : [ {
"rel" : "one",
"href" : "http://localhost:8080/"
}, {
"rel" : "two",
"href" : "http://localhost:8080/"
} ]
}
Hope this helps.
Here is an example of using the Jackson mixin annotation for serializing and deserializing the Link object including all the properties:
#JsonAutoDetect(
fieldVisibility = JsonAutoDetect.Visibility.NONE,
getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
#JsonDeserialize(using = LinkMixin.LinkDeserializer.class)
public abstract class LinkMixin extends Link {
private static final String HREF = "href";
#JsonProperty(HREF)
#Override
public abstract URI getUri();
#JsonAnyGetter
public abstract Map<String, String> getParams();
public static class LinkDeserializer extends JsonDeserializer<Link> {
#Override
public Link deserialize(
final JsonParser p,
final DeserializationContext ctxt) throws IOException {
final Map<String, String> params = p.readValueAs(
new TypeReference<Map<String, String>>() {});
if (params == null) {
return null;
}
final String uri = params.remove(HREF);
if (uri == null) {
return null;
}
final Builder builder = Link.fromUri(uri);
params.forEach(builder::param);
return builder.build();
}
}
}

Categories

Resources