Serialize nested objects with jackson - java

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?)

Related

jackson - Change the serializer for a POJO within a POJO being serialized

I have a Jackson serializable object that contains other objects a few layers down. For certain cases (which can be annotated with #JsonSerialize), I want to change the serialization for that internal object. I can't seem to figure out how to do this.
In the example below, Request1 the Foo object throughout is serialized via default serialization, but in Request2 all instances of Foo serialized with a custom serializer.
Request1 JSON:
{
id: "8c88e31c-f6da-4ce5-8c75-3cacd5cb6694",
pojo: {
foo: {
value1: "abc",
value2: "def"
},
bar: {
other: {
value1: "uvw",
value2: "xyz"
}
}
}
}
Request2 JSON:
{
id: "8c88e31c-f6da-4ce5-8c75-3cacd5cb6694",
pojo: {
foo: [
"abc",
"def"
],
bar: {
other: [
"uvw",
"xyz"
]
}
}
}
Request1.java:
public class Request1 {
private UUID id;
private MyPojo pojo;
}
Request2.java:
public class Request2 {
private UUID id;
#JsonSerialize(using = MyPojoSerializer.class)
private MyPojo pojo;
}
MyPojo.java:
public class MyPojo {
private Foo foo;
private Bar bar;
public static class Bar {
private Foo other;
}
}
Foo.java:
public class Foo {
private String value1;
private String value2;
Serializer for MyPojo:
public class MyPojoSerializer extends JsonSerializer<MyPojo> {
#Override
public void serialize(final MyPojo value, final JsonGenerator gen,
final SerializerProvider serializers) throws IOException {
// how to set custom serializer for Foo before serializing the MyPojo object???
gen.writeObject(value);
}
private static class FooSerializer extends JsonSerializer<Foo> {
#Override
public void serialize(final Foo value, final JsonGenerator gen,
final SerializerProvider serializers) throws IOException {
gen.writeStartArray();
gen.writeString(value.getValue1());
gen.writeString(value.getValue2());
gen.writeEndArray();
}
}
}
I'm not sure there is a way to do this where you would actually register a custom serializer mid-serialization, but you can somewhat easily accomplish what you would like with this code:
public class MyPojoSerializer extends JsonSerializer<MyPojo> {
#Override
public void serialize(final MyPojo value, final JsonGenerator gen,
final SerializerProvider serializers) throws IOException {
gen.writeStartObject();
provider.defaultSerializeField("bar", value.getBar(), gen);
gen.writeFieldName("foo");
serializeFoo(value.getFoo(), gen);
gen.writeEndObject();
}
private void serializeFoo(final Foo value, final JsonGenerator gen) throws IOException {
gen.writeStartArray();
gen.writeString(value.getValue1());
gen.writeString(value.getValue2());
gen.writeEndArray();
}
}
It's not ideal because you need to serialize each field manually (though the provider makes each field a one-liner), but maybe it will at least give you a way forward if you are stuck.

How to remove a field from POJO

public class user {
private String planId;
private String eid;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private Collection<String> userIds;
}
I have a pojo like above
and the code which is use for creating the json request object is this :
public UserCollection getUserCollection(final user args) {
Map<String, String> headersMap = new HashMap<>();
ObjectMapper jacksonMapper = new ObjectMapper();
jacksonMapper.disable(MapperFeature.USE_ANNOTATIONS); // this line is creating the userIds field to reflect
//By any code we can remove the userId field from the args object
String responseBody = null;
String responseStatus = null;
String jsonRequestBody = jacksonMapper.writeValueAsString(args);
}
I just want to remove userIds from the args by not removing any of the above code.
Thanks in advance.
I don't know how you should solve this without removing the annotation processing code, you should maybe add a custom serializer. You can read further about the topic at here.
public class HelloWorld {
public static void main(String[] args) throws JsonProcessingException {
User myItem = new User("planId", "eId", List.of("1", "2"));
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(User.class, new UserSerializer());
mapper.registerModule(module);
String serialized = mapper.writeValueAsString(myItem);
}
#Data
#AllArgsConstructor
public static class User {
private String planId;
private String eId;
private Collection<String> userIds;
}
public static class UserSerializer extends StdSerializer<User> {
public UserSerializer() {
this(null);
}
public UserSerializer(Class<User> t) {
super(t);
}
#Override
public void serialize(User value, JsonGenerator gen, SerializerProvider provider) throws IOException{
gen.writeStartObject();
gen.writeStringField("planId", value.planId);
gen.writeStringField("eId", value.eId);
gen.writeEndObject();
}
}
}
If you had to use it without annotations, and cannot add a custom serializer you should map the User class into a more specific class without the field in question and then serialize that one.

Jackson serializes list of objects with empty objects

I'm trying to serialize a very large object graph that uses #JsonView (I don't know if that's relevant or not). What I'm finding is that for a list of objects within the graph, Jackson is serializing a list of empty objects, such as [{},{},{}]. None of the attributes are in the string. All scalar attributes serialize just fine. I'm only having trouble with the lists.
I've verified several times that the attributes are being set in the objects. Part of my POJO looks like so:
public class ProfessionalData implements Serializable {
#JsonProperty("collegeEducation")
#JsonView(Views.Myview.class)
#Valid
private List<CollegeEducation> collegeEducation = new ArrayList<CollegeEducation>();
#JsonProperty("managementCommittee")
#JsonView(Views.Myview.class)
#Valid
private List<ManagementCommittee> managementCommittee = new ArrayList<ManagementCommittee>();
//getters and setters
}
This is the ObjectMapper code:
#Override
public String convertToDatabaseColumn(#NotNull MyPojoItem item) {
try {
//Disable Default_view_Inclusion, so fields without a view annotation wont be included
objectMapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
objectMapper.setDateFormat(df); //Transform the Object to String
String result = objectMapper
.writerWithView(Views.MyView.class)
.writeValueAsString(item);
return result;
} catch (Exception ex) {
LOG.error("Failed to convertToDatabaseColumn: " + ex.getMessage());
return null;
}
}
Can someone tell me what this usually means?
You could do something like this for both lists. A custom serializer to write your objects there from a List. For example, for the CollegeEducation list:
public class CollegeEducationSerializer extends JsonSerializer<List<CollegeEducation>>
{
#Override
public void serialize(List<CollegeEducation> list, JsonGenerator json,
SerializerProvider provider) throws IOException
{
if (list == null || list.isEmpty())
return;
json.writeFieldName("CollegeEducationArray");
json.writeStartArray();
for (CollegeEducation ce : list)
{
json.writeStartObject();
json.writeObjectField("CollegeEducation", ce);
json.writeEndObject();
}
json.writeEndArray();
}
}
And specify it on the annotations:
#JsonSerialize(using = CollegeEducationSerializer.class)
private List<CollegeEducation> collegeEducation = new ArrayList<CollegeEducation>();
In order to read it, the same logic applies (a custom deserializer for both lists).
For something like a generic approach, you could also do something like this:
public class CustomListSerializer extends JsonSerializer<List<? extends ListElement>>
{
#Override
public void serialize(List<? extends ListElement> list, JsonGenerator json,
SerializerProvider provider) throws IOException
{
if (list == null || list.isEmpty())
return;
json.writeFieldName(list.get(0).getElementType()+"Array");
json.writeStartArray();
for (ListElement le : list)
{
json.writeStartObject();
json.writeObjectField(le.getElementType(), le);
json.writeEndObject();
}
json.writeEndArray();
}
}
Then you could use it for all your Lists:
#JsonSerialize(using = CustomListSerializer.class)
private List<CollegeEducation> collegeEducation = new ArrayList<>();
#JsonSerialize(using = CustomListSerializer.class)
private List<ManagementCommittee> managementCommittee = new ArrayList<>();
For this I added a customProperties Map, which may be prone to errors here. So the approach would be writing manually the desired fields, instead of using json.writeObjectField(le.getElementType(), le);
public abstract class ListElement
{
public abstract String getElementType();
public Map<String, String> properties = new HashMap<>();
public final String name;
public final int id;
public ListElement(String name, int id)
{
this.name = name;
this.id = id;
}
}
and, f.e:
public class CollegeEducation extends ListElement
{
protected String location;
protected String director;
public CollegeEducation(String name, int id, String location, String director)
{
super(name,id);
this.director = director;
this.location= location;
properties.put("director", director);
properties.put("location", location);
//...
}
public String getElementType()
{
return "CollegeEducation";
}
//...
}
//...
In the serializer:
json.writeFieldName(list.get(0).getElementType()+"Array");
json.writeStartArray();
for (ListElement le : list)
{
json.writeStartObject();
json.writeFieldName(le.getElementType()+"-"+le.id);
json.writeStartObject();
json.writeStringField("name", le.name);
for(Map.Entry<String,String> kv : le.properties.entrySet())
json.writeStringField(kv.getKey(), kv.getValue());
json.writeEndObject();
json.writeEndObject();
}
json.writeEndArray();
You should get something like:
{
"CollegeEducationArray":
[
{ "CollegeEducation-22" :
{
"name" : "Lauaxeta",
"director" : "AJerk",
"location": "Bilbao"
}
},
{ "CollegeEducation-55" :
{
"name" : "Harvard",
"director" : "OtherJerk",
"location": "Rwanda"
}
}
]
}

Jackson: how to omit field inside nested object

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

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