I'm new to Java and Jackson and a lot of other technologies which I try to use, so I'd appreciate a detailed answer.
Is there a way to prevent one or more fields from being serialized using Jackson into a JSON String_like format, but without using any kind of JSON annotations?
Something like: mapper.getSerializationConfig().something(ignore("displayname")) if you know what I mean.
My object is an instance of a class that extends another one, and implements one interface also so on, so the fields come from an hierarchy of classes.
I need the JSON representation for that object but containing only certain fields, so I can send that JSON in a mock request through a POST method.
I'm using Jackson 2.2.2.
If you can't change your classes you can create new abstract class/interface with methods with #JsonIgnore annotation. In this class/interface you can define methods which ObjectMapper should skip during serialization/deserialization process.
Please, see below example:
import java.io.IOException;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonProgram {
public static void main(String[] args) throws IOException {
Person person = new Person();
person.setId(1L);
person.setName("Max");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(Person.class, PersonMixIn.class);
System.out.println(objectMapper.writeValueAsString(person));
}
}
abstract class Entity {
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
interface Namamble {
String getName();
}
class Person extends Entity implements Namamble {
private String name;
#Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
interface PersonMixIn {
#JsonIgnore
String getName();
}
EDIT - answer for the comments
You can create such mixin interface:
public static interface UserInformationMixIn {
#JsonIgnore
String getField3();
}
and configure ObjectMapper in this way:
objectMapper.addMixInAnnotations(UserInformation.class, UserInformationMixIn.class);
In version 2.5 method addMixInAnnotations was deprecated and addMixIn should be used:
objectMapper.addMixIn(UserInformation.class, UserInformationMixIn.class);
Full example source code:
import java.io.IOException;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonProgram {
public static void main(String[] args) throws IOException {
UserInformation userInformation = new UserInformation();
userInformation.setField3("field3");
userInformation.setField4("field4");
userInformation.setField5("field5");
User user = new User();
user.setField1(userInformation);
user.setField2("field2");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(UserInformation.class, UserInformationMixIn.class);
objectMapper.addMixIn(User.class, UserInformationMixIn.class);
System.out.println(objectMapper.writeValueAsString(user));
}
public static abstract class Someclass {
String field5;
public String getField5() {
return field5;
}
public void setField5(String field5) {
this.field5 = field5;
}
}
public static class UserInformation extends Someclass {
String field3;
String field4;
public String getField3() {
return field3;
}
public void setField3(String field3) {
this.field3 = field3;
}
public String getField4() {
return field4;
}
public void setField4(String field4) {
this.field4 = field4;
}
}
public static class User {
UserInformation field1;
String field2;
public UserInformation getField1() {
return field1;
}
public void setField1(UserInformation field1) {
this.field1 = field1;
}
public String getField2() {
return field2;
}
public void setField2(String field2) {
this.field2 = field2;
}
}
public static interface UserInformationMixIn {
#JsonIgnore
String getField3();
#JsonIgnore
String getField2();
#JsonIgnore
String getField5();
}
}
Helpful link:
How can I tell jackson to ignore a property for which I don't have
control over the source code?
Related
I have a need to have two different ObjectMapper in the application.
Pojo I am working with:
public class Student {
private String name;
private Integer age;
#HideThisField
private String grade;
// getters & setters..
}
One is the out of the box configuration based ObjectMapper as below:
#Bean("objectMapper")
public ObjectMapper getRegularObjectMapper() {
//With some configurations
return new ObjectMapper();
}
I need another ObjectMapper that while serializing ignores a few fields for all objects based on an annotation on a field.
#Bean("customObjectMapper")
public ObjectMapper getCustomObjectMapper() {
// This is where i want to ignore the fields with #HideThisField
return new ObjectMapper();
}
Output of the two mappers:
objectMapper.writeValuesAsString(someStudent) prints:
{"name": ""student1", age: 10, "grade": "A+"}
customObjectMapper.writeValuesAsString(someStudent) prints:
{"name": ""student1", age: 10}
JacksonAnnotationIntrospector handles standard Jackson annotations. Overriding the hasIgnoreMarker method, you can make it work according to your own annotation.
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.*;
import java.lang.annotation.*;
public class StudentExample {
public static void main(String[] args) throws JsonProcessingException {
Student student = new Student();
student.setName("Student 1");
student.setAge(10);
student.setGrade("A+");
String st1 = getRegularObjectMapper().writeValueAsString(student);
String st2 = getCustomObjectMapper().writeValueAsString(student);
System.out.println(st1);
System.out.println(st2);
}
public static ObjectMapper getRegularObjectMapper() {
return new ObjectMapper();
}
public static ObjectMapper getCustomObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
#Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
if (_findAnnotation(m, HideThisField.class) != null)
return true;
return false;
}
});
return objectMapper;
}
}
class Student {
private String name;
private Integer age;
#HideThisField
private String grade;
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getGrade() {
return grade;
}
public void setGrade(String grade) {
this.grade = grade;
}
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#interface HideThisField {}
The console output is:
{"name":"Student 1","age":10,"grade":"A+"}
{"name":"Student 1","age":10}
getCustomObjectMapper() don't skips JsonIgnore annotations because you override the standard, if you want, you need to add this to the if block.
I'm create REST api with springFramework. Create products from post api, but rest method does not cast the parameters with TitleCase parameter name.
My Product model:
public class Product extends BaseModel {
private String title;
private String shortDescription;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getShortDescription() {
return shortDescription;
}
public void setShortDescription(String shortDescription) {
this.shortDescription = shortDescription;
}
}
My Product REST Api:
#PostMapping(value = "/product", consumes = {MediaType.APPLICATION_JSON_VALUE})
public ApiResponse Insert(#RequestBody Product model){
ApiResponse response = new ApiResponse();
try{
response.setData(_service.Insert(model));
response.setSuccess(true);
} catch (Exception ex){
response = _service.HandleException(ex);
}
return response;
}
Worked Request Object:
{
"title" : "TEST",
"shortDescription" : "SD",
"longDescription" : "LD"
}
Not Worked Request Object:
{
"Title" : "TEST",
"ShortDescription" : "SD",
"LongDescription" : "LD"
}
I want both options to work. I'm new to Java, so I did not know how to get around to it, so I wanted to ask.
UPDATED
Jackson Config File
#Configuration
#EnableWebMvc
public class JacksonConfiguration {
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
return mapper;
}
}
Thanks.
This works fine:
package com.example;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
public class Test {
private ObjectMapper mapper;
public static class A{
private int test;
public int getTest() {
return test;
}
public void setTest(int test) {
this.test = test;
}
}
#Before
public void setUp(){
mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
}
#Test
public void deserialise() throws Exception {
String json = "{\"test\":\"2\"}";
assertThat(mapper.readValue(json, A.class).getTest(), is(2));
}
#Test
public void deserialiseIgnoringCase() throws Exception {
String json = "{\"Test\":\"2\"}";
assertThat(mapper.readValue(json, A.class).getTest(), is(2));
}
}
Product model does not seem to have longDescription field. That might be the reason why deserialisation will fail (as there is no #JsonIgnore) on Product class. Below snippet works fine:
class Product {
private String title;
private String shortDescription;
private String longDescription;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getShortDescription() {
return shortDescription;
}
public void setShortDescription(String shortDescription) {
this.shortDescription = shortDescription;
}
public String getLongDescription() {
return longDescription;
}
public void setLongDescription(String longDescription) {
this.longDescription = longDescription;
}
}
public static void main(String[] args) throws Exception{
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
Product product = mapper.readValue("{\"Title\" : \"TEST\",\"ShortDescription\" : \"SD\",\"LongDescription\" : \"LD\"}", Product.class);
System.out.println(product.getShortDescription());
}
It will throw an Exception if you comment out longDescription.
I have a RequestModel defined as
public class RequestModel
{
public class Footage
{
public String date;
public String retrievedAt;
public String videoFileName;
public String availableUntil;
public boolean isAvailable;
}
public class People
{
public String first;
public String last;
}
public static final int USER_BLOCKED = 0;
public static final int USER_ACTIVE = 1;
public static final int USER_WAIT_PIN = 2;
public String _id;
public String status;
public String submittedAt;
public Footage footage;
public People teacher;
public People student;
public ArrayList<MessageModel> messages = new ArrayList<MessageModel>();
public boolean isExpanded = false;
public RequestModel()
{
}
My MessageModel is defined as
public class MessageModel
{
public String _id;
public String statusMessage;
public String submittedAt;
public RequestModel request;
public String status;
public String timestamp;
public boolean isExpanded = false;
public MessageModel()
{
}
}
I have an api call that pulls a single "RequestModel" item. However the messages list in that api call has "request" as a String instead of "RequestModel" object.
Is there any way i can make it parse as a different name or omit it entirely to bypass exceptions causing because of different types.
Use the annotation #SerializedName("") before declaring member to give it a substitute name
ex,
if you json looks like this
{
name:"",
age:0,
items:[...]
}
but your model class have the fields,
class User{
String name;
int age;
Data userItems[];
}
The field userItems in model is named items in the json,
you need to use that annotation on the field:
class User{
String name;
int age;
#SerializedName("items")
Data userItems[];
}
this way GSON will map items to userItems.
I use different NoSQL databases and depending on the database I need to name the "id" different. So for example in OrientDB the id is named "#rid"
#JsonProperty("#rid")
private String id;
And for MongoDB the id is named "_id"
#JsonProperty("#_id")
private String id;
I do not know what is wrong with the modern DB developers not just naming the id field "id" ^^. But now I have a problem. How can I dynamically serialize/deserialize the id field in some case as "#rid" and in another case as "_id"?
EDIT:
Based on rmullers suggestion I have tried to use mixins. So I have for example:
public interface IdMixins {
}
public interface MongoIdMixIn extends IdMixins {
#JsonProperty("_id") String getId();
#JsonProperty("_id") void setId(String id);
}
public interface OrientIdMixIn extends IdMixins{
#JsonProperty("#rid") String getId();
#JsonProperty("#rid") void setId(String id);
}
Where IdMixins is a completly empty interface just used to get more controll which interfaces can be passet to the mapper.
Then there is a class:
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="#javaClass")
public abstract class AbstractBean implements Serializable {
private static final long serialVersionUID = -1286900676713424199L;
// #JsonProperty("#rid")
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
But when I run this simple test, the output is still "id":
public class MixinTest {
public static void main(String[] args) throws JsonProcessingException {
Foo f = new Foo();
f.setId("123");
f.setBar("lala");
ObjectMapper mapper = new ObjectMapper();
ObjectMapper m2 = mapper.copy();
m2.addMixInAnnotations(AbstractBean.class, MongoIdMixIn.class);
System.out.println(m2.writeValueAsString(f));
ObjectMapper m3 = mapper.copy();
m3.addMixInAnnotations(AbstractBean.class, OrientIdMixIn.class);
System.out.println(m3.writeValueAsString(f));
}
public static class Foo extends AbstractBean {
private String bar;
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
}
Outputs:
{"#javaClass":"test.MixinTest$Foo","id":"123","bar":"lala","#class":"Foo"}
{"#javaClass":"test.MixinTest$Foo","id":"123","bar":"lala","#class":"Foo"}
Have you tried using http://wiki.fasterxml.com/JacksonMixInAnnotations? Then you can use an OrientDbMixin and a MongoDbMixin with different #JsonProperty configuration.
Update: Working example
public final class JacksonTest {
static final class ExampleBean {
private String id;
private String bar;
#JsonProperty("donotwanttoseethis")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
public interface MongoIdMixIn {
#JsonProperty("_id") String getId();
}
public interface OrientIdMixIn {
#JsonProperty("#rid") String getId();
}
private final static Logger LOG = LoggerFactory.getLogger();
public static void main(String[] args) throws JsonProcessingException {
ExampleBean bean = new ExampleBean();
bean.setId("1234");
bean.setBar("lala");
ObjectMapper m2 = new ObjectMapper();
m2.addMixInAnnotations(ExampleBean.class, MongoIdMixIn.class);
LOG.info(m2.writeValueAsString(bean));
ObjectMapper m3 = new ObjectMapper();
m3.addMixInAnnotations(ExampleBean.class, OrientIdMixIn.class);
LOG.info(m3.writeValueAsString(bean));
}
}
I am using jackson 2.6.x
I am unable to convert the json to java class
public class ParentTest {
#JsonProperty("ParentTest")
public List<Test> getTestList() {
return testList;
}
public void setTestList(List<Test> testList) {
this.testList = testList;
}
#JsonProperty("ParentTest")
List<Test> testList;
public ParentTest(List<Test> testList)
{
this.testList = testList;
}
public ParentTest()
{
super();
testList = new ArrayList<Test>();
}
public String toString()
{
String r = "";
if(testList!=null)
for(Test t : testList)
{
r += "Test:"+t.field1+t.field2;
}
else
r = "null";
return r;
}}
And the class used for list is
#JsonRootName("Test")public class Test {
#JsonProperty("field1")
String field1;
#JsonProperty("field2")
String field2;
public Test(String field1,String field2)
{
this.field1 = field1;
this.field2 = field2;
}
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
public String getField2() {
return field2;
}
public void setField2(String field2) {
this.field2 = field2;
}}
And My Json is
{"ParentTest": [{
"Test":{
"field1":"t1",
"field2":"t2"
}
},
{
"Test":{
"field1":"t3",
"field2":"t4"
}
}]}
And to read it I have used
public final class HandlerJacksonUtils {
private static ObjectMapper m = new ObjectMapper();
private static JsonFactory jf = new JsonFactory();
public static <T> Object fromJson(String jsonAsString, Class<T> pojoClass) throws IOException {
m.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return m.readValue(jsonAsString, pojoClass);
}}
and then
ParentTest pt = (ParentTest) HandlerJacksonUtils.fromJson(json, ParentTest.class);
The ParentTest object pt prints nothing on System.out.print(pt.toString);
#JsonRootName does not work alone, as stated in javadocs.
You should use it in combination with SerializationFeature#WRAP_ROOT_VALUE and/or DeserializationFeature#UNWRAP_ROOT_VALUE.
But it works only for root object that you want to serialize/deserialize. So, it won't work in your case.
Instead, you can make a wrapper class for Test
public class TestWrapper {
#JsonProperty("Test")
private Test test;
public Test getTest() {
return test;
}
public void setTest(Test test) {
this.test = test;
}
}
And use it instead of Test in ParentTest