Jackson: how to omit field inside nested object - java

I have a pojo-like object with following structure:
#JsonFilter("MyFilter")
public MyDTO {
public int id;
public List<MyNestedDTO> nestedDTO;
public MyNestedDTO {
public int id;
public String name;
...
}
...
}
I want to completely omit name-field from a serialized output. I use SimpleBeanPropertyFilter like this:
ObjectMapper mapper = new ObjectMapper();
PropertyFilter columnFilter = new SimpleBeanPropertyFilter() {
#Override
public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer)
throws Exception {
if (include(writer)) {
if (writer.getName().equalsIgnoreCase("nestedDTO")) {
return;
}
writer.serializeAsField(pojo, jgen, provider);
} else if (!jgen.canOmitFields()) {
writer.serializeAsOmittedField(pojo, jgen, provider);
}
}
...
};
FilterProvider filters = new SimpleFilterProvider().addFilter("MyFilter", columnFilter);
String result = mapper.writer(filters).writeValueAsString(MyDTOObj);
I see that I can't catch the moment of nestedDTO.name field serialization by this serializeAsField() realization. Explain to me how I can get it, please.

If you want to skip serialization/deserialization of a field, just annotate it with #JsonIgnore :
public MyDTO {
public int id;
public List<MyNestedDTO> nestedDTO;
public MyNestedDTO {
public int id;
#JsonIgnore
public String name;
...
}
...
}

Related

Deserialize multiple json fields into single java property

I want to convert a json into Java class by having custom deserializer.
I'm able to serialize ACC_NUM, NAME and any other fields from json but not sure what can be done to convert MOBILE_NUMBER_1,MOBILE_NUMBER_2 like fields into single JSONArray(See AccountInfo class). There can be many more fields like this and count also is not fixed. Example there can be ADDRESS_1, ADDRESS_2 till ADDRESS_20 and so on and all this fields should go in JSONArray of ADDRESS after deserilization.
I have a Map of Map which holds info like this:
{
"accountInfo": {
"ACC_NUM": "1234567890",
"NAME": "John Cena",
"MOBILE_NUMBER_1": "12376534",
"MOBILE_NUMBER_2": "12376534",
"MOBILE_NUMBER_3": "12376534",
"MOBILE_NUMBER_4": "12376534"
},
"someOther": {
//similer to above
}
}
This info I want to convert to the following class CommonInfo:
public class CommonInfo {
private AccountInfo accountInfo;
//other properties...
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class AccountInfo {
#JsonProperty("ACC_NUM")
private FieldValue<BigInteger> accountNum;
#JsonProperty("NAME")
private FieldValue<String> name;
#JsonProperty("MOBILE_NUMBER")
private FieldValue<JSONArray> mobileNumber;
}
//FieldValue class
public interface FieldValue<T> {
T getInitialValue();
void setInitialValue(T initialValue);
T getValue();
void setValue(T value);
}
#JsonInclude(JsonInclude.Include.ALWAYS)
public class FieldValueImpl<T> implements FieldValue<T> {
protected T initialValue;
protected T value;
//getters, setters, cons..
}
My service code takes json/Map and tries to convert it to CommonInfo class
#Service
public class MyService {
private final ObjectMapper jsonMapper = new ObjectMapper();
#PostConstruct
protected void init() {
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(FieldValue.class, new FieldValueSerializer());
simpleModule.addDeserializer(FieldValue.class, new FieldValueDeserializer());
jsonMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
jsonMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
jsonMapper.registerModule(simpleModule);
}
public CommonInfo setPojoResult(Map<String, LinkedHashMap<String, String>> contentAsMap) {
return jsonMapper.convertValue(contentAsMap, CommonInfo.class);
}
}
Serializer and Deserializer looks like this:
public class FieldValueDeserializer extends JsonDeserializer<FieldValue<?>> implements ContextualDeserializer {
private JavaType valueType;
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException {
var deserializer = new FieldValueDeserializer();
if (property == null) {
deserializer.valueType = ctxt.getContextualType().containedType(0);
} else {
var wrapperType = property.getType();
var valueType = wrapperType.containedType(0);
deserializer.valueType = valueType;
}
return deserializer;
}
#Override
public FieldValue<?> deserialize(JsonParser parser, DeserializationContext context) throws IOException {
FieldValueDeserializer deserializer = new FieldValueDeserializer();
deserializer.getKnownPropertyNames();
FieldValue<?> fieldValueImpl = new FieldValueImpl<>();
if (valueType.toString().contains("java.time.LocalDate")) {
JsonNode node = parser.getCodec().readTree(parser);
FieldValue<LocalDate> f1 = new FieldValueImpl<>();
f1.setValue(DateUtils.convertJulianToLocalDate(node.textValue()));
return f1;
} else {
fieldValueImpl.setValue(context.readValue(parser, valueType));
}
return fieldValueImpl;
}
}
//--
public class FieldValueSerializer extends StdSerializer<FieldValue> {
public FieldValueSerializer() {
this(null);
}
public FieldValueSerializer(Class<FieldValue> vc) {
super(vc);
}
#Override
public void serialize(FieldValue value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeString(String.valueOf(value.getCurValue()));
}
}

Serialize nested objects with jackson

I want to have an output like
{ Orga1: [ dep1, dep2], Orga2: [dep88, dep99], ...}
but somehow I fail to get it properly done.
I have the following structure:
#JsonSerialize(using = OrganisationSerializer.class)
public class Organisation {
String name;
private HashMap<String, Department> lstDepartments = new HashMap<>();
public List<Department> getList() {
return lstDepartments.values().stream().collect(Collectors.toList());
}
}
with the nested class
#JsonSerialize(using = DepartmentSerializer.class)
public class Department {
String name;
HashMap<String, Role4Filter> lstRole = new HashMap<>();
public List<Role4Filter> getList() {
return lstRole.values().stream().collect(Collectors.toList());
}
...
}
The major problem is that the HashMap needs to be transferred into a List which needs to be serialized. But somehow I fail to convert into a JSON properly.
My approach with
public class OrganisationSerializer extends JsonSerializer<Organisation> {
#Override
public void serialize(Organisation value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
gen.writeArrayFieldStart(value.name);
for (final Department item : value.getList()) {
gen.writeObject(item);
}
gen.writeEndArray();
gen.writeEndObject();
}
fails with the exception that
com.fasterxml.jackson.databind.JsonMappingException: org..Department cannot be cast to org..Organisation
Any ideas? Or is there some other annotation possible (beside the serializer?)

How to tell to Jackson ObjectWriter ignore from class member during write

I'm using Java.
I have the following class:
class MyClass{
#JsonProperty("test")
String test;
String myPrivateTest;
}
I'm using Jackson ObjectWriter to write my JSON object to file/as string as follow:
ObjectWriter writer = objMapper.writer(new DefaultPrettyPrinter());
writer.writeValue(new File(path), myObject);
I want to write to file only test member and not myPrivateTest, How can I do it?
EDIT
I have been tried with:
class MyClass{
#JsonProperty("test")
String test;
#JsonIgnore
String myPrivateTest;
}
And with
#JsonIgnoreProperties({"myPrivateTest"})
class MyClass{
#JsonProperty("test")
String test;
String myPrivateTest;
}
And It is still write myPrivateTest to the file
Using Jackson 2.9.6
Just adding the #JsonProperty to the fields you want is enough.
public class MyClass {
#JsonProperty("test")
String test;
String myPrivateTest;
public static void main(String[] args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
MyClass c = new MyClass();
c.test = "HELLO";
c.myPrivateTest = "WORLD";
System.out.println(objectMapper.writeValueAsString(c));
}
}
Output:
{"test":"HELLO"}
String myPrivateTest is correctly ignored
You can ignore specific fields with providing PropertyFilter as below :
#JsonFilter("someFilter")
#Data // lombok
#NoArgsConstructor // lombok
class SomeClass {
private String name;
private int someField;
public SomeClass(String name, int sf){
this.name=name;
this.someField = sf;
}
}
#Test
public void test() throws JsonProcessingException {
SomeClass someClass = new SomeClass("test",1);
ObjectMapper mapper = new ObjectMapper();
PropertyFilter theFilter = new SimpleBeanPropertyFilter() {
#Override
public void serializeAsField
(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer)
throws Exception {
if (include(writer)) {
// here you can define the properties to ignore
if (!writer.getName().equals("name")) {
writer.serializeAsField(pojo, jgen, provider);
return;
}
} else if (!jgen.canOmitFields()) {
writer.serializeAsOmittedField(pojo, jgen, provider);
}
}
};
FilterProvider filters = new SimpleFilterProvider()
.addFilter("someFilter", theFilter);
String objAsString = mapper
.writer(filters)
.writeValueAsString(someClass);
assertThat(objAsString).doesNotContain("name");
}
Serialized output is :
{"someField":1}

Generic Enum Json Deserialization

I needed a better hibernate enum mapping and this page served me well (Except I used char type instead of int).
Next question is how can I serialize/deserialize an enum in a generic way?
Think of a Gender enum:
#JsonSerialize(using = PersistentEnumSerializer.class)
#JsonDeserialize(using = PersistentEnumDeserializer.class)
public enum Gender implements PersistentEnum {
MALE("M", "Male"), FEMALE("F", "Female");
private String code;
private String display;
Gender(String code, String display) {
this.code = code;
this.display = display;
}
public String getName() {
return name();
}
public String getCode() {
return code;
}
public String getDisplay() {
return display;
}
public String toString() {
return display;
}
}
which implements getName(), getCode() and getDisplay() methods of PersistentEnum interface. Serializing is easy:
public class PersistentEnumSerializer extends JsonSerializer<PersistentEnum> {
#Override
public void serialize(PersistentEnum object, JsonGenerator generator, SerializerProvider provider) throws IOException, JsonProcessingException {
generator.writeStartObject();
generator.writeFieldName("name");
generator.writeString(object.getName());
generator.writeFieldName("code");
generator.writeString(object.getCode());
generator.writeFieldName("display");
generator.writeString(object.getDisplay());
generator.writeEndObject();
}
}
but how can I deserialize in java 6? In java 8, I would add a static method to PersistentEnum interface.
public class PersistentEnumDeserializer extends JsonDeserializer<PersistentEnum> {
#Override
public PersistentEnum deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
//String value = node.get("name").asText();
//TODO Somehow I need to get Gender.MALE if the json is {"name":"MALE","code":"M","display":"Male"}
return null;
}
}
One possible solution is to add a new method getType() to PersistentEnum which will identify the type of Enum.
#JsonSerialize(using = PersistentEnumSerializer.class)
#JsonDeserialize(using = PersistentEnumDeserializer.class)
public enum Gender implements PersistentEnum {
#Override
public String getType() {
return "gender";
}
}
Serializer should also be modified to include type while serialization.
public class PersistentEnumSerializer extends JsonSerializer<PersistentEnum> {
#Override
public void serialize(PersistentEnum object, JsonGenerator generator, SerializerProvider provider) throws IOException, JsonProcessingException {
generator.writeStartObject();
generator.writeFieldName("name");
generator.writeString(object.getName());
generator.writeFieldName("code");
generator.writeString(object.getCode());
generator.writeFieldName("display");
generator.writeString(object.getDisplay());
generator.writeFieldName("type");
generator.writeString(object.getType());
generator.writeEndObject();
}
}
Deserializer can be written as shown below.
public class PersistentEnumDeserializer extends JsonDeserializer<PersistentEnum> {
#Override
public PersistentEnum deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
return findEnum(node.get("type").asText(), node.get("name").asText());
}
private PersistentEnum findEnum(String type, String name) {
switch (type) {
case "gender":
return Gender.valueOf(name);
// handle other types here.
default:
return null;
}
}
}
While #Justin Jose's solution is not the one I'm looking for (because for each enum we need to add to findEnum method), it gave me a good hint.
If getType is implemented like this:
#Override
public String getType() {
return getClass().getSimpleName();
}
and findEnum like this
private PersistentEnum findEnum(String type, String name) {
Class<?> c = null;
try {
c = Class.forName("enums." + type); //Assuming all PersistentEnum's are in "enums" package
if (PersistentEnum.class.isAssignableFrom(c)) {
Method method = c.getMethod("name");
for (Object object : c.getEnumConstants()) {
Object enumName = method.invoke(object);
if (name.equals(enumName))
return (PersistentEnum) object;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
it may work. Not tested and possibly vulnerable.

Custom serializer for serializing a List<User> in List<String>

I've a Model object Group
public class Group {
String title;
List<User> members;
String createdBy;
}
I'm using Jackson to serialize this Object. Instead of serializing the whole User object in list "members" I want to serializer just the user.getTitle() field.
Basically I want a HashMap to be something like
{
"title" : "sometitle"
"members" : [user1.getTitle(), user2.getTitle()]
}
I've written a custom serializer for this
public class GroupSerializer extends JsonSerializer<Circle> {
#Override
public void serialize(Group value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
if(value != null) {
gen.writeStartObject();
gen.writeStringField("title", value.getTitle());
gen.writeStringField("createdBy", value.getCreatedBy());
gen.writeFieldName("members");
gen.writeStartArray();
for(User user : value.getMembers()) {
gen.writeString(user.getEmail());
}
gen.writeEndArray();
gen.writeEndObject()
}
}
}
But it's not working. How do I serialize only a field of List instead of whole User Object?
I suggest that you look into Jackson's Converter interface, which seems more suited to the task than creating a custom serializer.
One approach it to create a Converter instance and add it to the ObjectMapper, so that it will be used for the serialization of all User instances.
public class UserConverter extends StdConverter<User, String> {
#Override
public String convert(User user) {
return user.getTitle();
}
}
Register it on your ObjectMapper like this:
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(User.class, new StdDelegatingSerializer(new UserConverter()));
ObjectMapper om = new ObjectMapper().registerModule(simpleModule);
Another approach, in case you don't want to convert all User instances to String, is to annotate selected properties with a converter like this:
public class Group {
String title;
#JsonSerialize(converter = ListUserConverter.class)
List<User> members;
String createdBy;
}
And have a corresponding converter that looks something like this:
public class ListUserConverter extends StdConverter<List<User>, List<String>> {
#Override
public List<String> convert(List<User> users) {
return users.stream().map(User::getTitle).collect(Collectors.toList());
}
}
Try like below :
Group:
#JsonIgnoreProperties(ignoreUnknown=true)
public class Group {
#JsonSerialize(using= TitleSerializer.class)
List<User> members;
//getters and setters
}
User:
public class User {
private String title;
//getters and setters
}
Custom Serializer :
public class TitleSerializer extends StdSerializer<List<User>> {
private static List<User> users=new ArrayList<User>();
protected TitleSerializer(Class<List<User>> t) {
super(t);
// TODO Auto-generated constructor stub
}
#SuppressWarnings("unchecked")
public TitleSerializer(){
this((Class<List<User>>) users.getClass());
}
#Override
public void serialize(List<User> users, JsonGenerator paramJsonGenerator,
SerializerProvider paramSerializerProvider) throws IOException {
paramJsonGenerator.writeStartObject();
List<String> titles=new ArrayList<String>(users.size());
for(User user: users){
titles.add(user.getTitle());
}
paramJsonGenerator.writeObjectField("members", titles);
paramJsonGenerator.writeEndObject();
}
}
Test :
Group group=new Group(Arrays.asList(new User("a"),new User("b"),new User("c")));
ObjectMapper mapper = new ObjectMapper();
String serialized = mapper.writeValueAsString(group);
System.out.println("output "+serialized);
Output:
{"members":["a","b","c"]}

Categories