How to Serialize Only the Id of a Child with Jackson

How to serialize only the ID of a child with Jackson

There are couple of ways. First one is to use @JsonIgnoreProperties to remove properties from a child, like so:

public class Parent {
@JsonIgnoreProperties({"name", "description" }) // leave "id" and whatever child has
public Child child; // or use for getter or setter
}

another possibility, if Child object is always serialized as id:

public class Child {
// use value of this property _instead_ of object
@JsonValue
public int id;
}

and one more approach is to use @JsonIdentityInfo

public class Parent {
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
@JsonIdentityReference(alwaysAsId=true) // otherwise first ref as POJO, others as id
public Child child; // or use for getter or setter

// if using 'PropertyGenerator', need to have id as property -- not the only choice
public int id;
}

which would also work for serialization, and ignore properties other than id. Result would not be wrapped as Object however.

Jackson JSON Serialization without field name

From the wiki page it sounds like the @JsonUnwrapped annotation should do what you want.

@JsonUnwrapped: property annotation used to define that value should be "unwrapped" when serialized (and wrapped again when deserializing), resulting in flattening of data structure, compared to POJO structure.

The Javadoc for the class also has an example that looks appropriate.

How to instruct Jackson to serialize a field inside an Object instead of the Object it self?

You need to create and use a custom serializer.

public class ItemTypeSerializer extends JsonSerializer<ItemType> 
{
@Override
public void serialize(ItemType value, JsonGenerator jgen,
SerializerProvider provider)
throws IOException, JsonProcessingException
{
jgen.writeString(value.name);
}

}

@JsonSerialize(using = ItemTypeSerializer.class)
class ItemType
{
String name;
int somethingElse;
}

How to serialize a List content to a flat JSON object with Jackson?

You can use the BeanSerializerModifier to directly modify how a property name and value are written. Using this you could detect if a custom annotation is present, in this case I made one called @FlattenCollection. When the annotation is present the array or collection is not written using the normal method but instead written by a custom property writer (FlattenCollectionPropertyWriter).

This annotation will likely break on 2d arrays or other edge cases, I havent tested those but you could probably code for them without to much trouble, at least throw a meaningful error.

Heres the full working code. Notable points are

  • FlattenCollectionSerializerModifier.changeProperties
  • FlattenCollectionPropertyWriter.serializeAsField
  • The couple TODOs i put in there for you.

Output:

{
"titleCity" : "New York",
"personName_1" : "Foo",
"personAge_1" : 123,
"personName_2" : "Baz",
"personAge_2" : 22
}

Code:

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.ser.*;
import com.fasterxml.jackson.databind.util.NameTransformer;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.*;

public class SO45698499 {

public static void main(String [] args) throws Exception {
ObjectWriter writer = createMapper().writerWithDefaultPrettyPrinter();
String val = writer.writeValueAsString(new City("New York",
Arrays.asList(new Person("Foo", 123), new Person("Baz", 22))));

System.out.println(val);
}

/**
* Constructs our mapper with the serializer modifier in mind
* @return
*/
public static ObjectMapper createMapper() {
FlattenCollectionSerializerModifier modifier = new FlattenCollectionSerializerModifier();
SerializerFactory sf = BeanSerializerFactory.instance.withSerializerModifier(modifier);
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializerFactory(sf);

return mapper;
}

@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface FlattenCollection {
}

/**
* Looks for the FlattenCollection annotation and modifies the bean writer
*/
public static class FlattenCollectionSerializerModifier extends BeanSerializerModifier {

@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
for (int i = 0; i < beanProperties.size(); i++) {
BeanPropertyWriter writer = beanProperties.get(i);
FlattenCollection annotation = writer.getAnnotation(FlattenCollection.class);
if (annotation != null) {
beanProperties.set(i, new FlattenCollectionPropertyWriter(writer));
}
}
return beanProperties;
}
}

/**
* Instead of writing a collection as an array, flatten the objects down into values.
*/
public static class FlattenCollectionPropertyWriter extends BeanPropertyWriter {
private final BeanPropertyWriter writer;

public FlattenCollectionPropertyWriter(BeanPropertyWriter writer) {
super(writer);
this.writer = writer;
}

@Override
public void serializeAsField(Object bean,
JsonGenerator gen,
SerializerProvider prov) throws Exception {
Object arrayValue = writer.get(bean);

// lets try and look for array and collection values
final Iterator iterator;
if(arrayValue != null && arrayValue.getClass().isArray()) {
// deal with array value
iterator = Arrays.stream((Object[])arrayValue).iterator();
} else if(arrayValue != null && Collection.class.isAssignableFrom(arrayValue.getClass())) {
iterator = ((Collection)arrayValue).iterator();
} else {
iterator = null;
}

if(iterator == null) {
// TODO: write null? skip? dunno, you gonna figure this one out
} else {
int index=0;
while(iterator.hasNext()) {
index++;
Object value = iterator.next();
if(value == null) {
// TODO: skip null values and still increment or maybe dont increment? You decide
} else {
// TODO: OP - update your prefix/suffix here, its kinda weird way of making a prefix
final String prefix = value.getClass().getSimpleName().toLowerCase();
final String suffix = "_"+index;
prov.findValueSerializer(value.getClass())
.unwrappingSerializer(new FlattenNameTransformer(prefix, suffix))
.serialize(value, gen, prov);
}
}
}
}
}

public static class FlattenNameTransformer extends NameTransformer {

private final String prefix;
private final String suffix;

public FlattenNameTransformer(String prefix, String suffix) {
this.prefix = prefix;
this.suffix = suffix;
}

@Override
public String transform(String name) {
// captial case the first letter, to prepend the suffix
String transformedName = Character.toUpperCase(name.charAt(0)) + name.substring(1);
return prefix + transformedName + suffix;
}
@Override
public String reverse(String transformed) {
if (transformed.startsWith(prefix)) {
String str = transformed.substring(prefix.length());
if (str.endsWith(suffix)) {
return str.substring(0, str.length() - suffix.length());
}
}
return null;
}
@Override
public String toString() { return "[FlattenNameTransformer('"+prefix+"','"+suffix+"')]"; }
}

/*===============================
* POJOS
===============================*/
public static class Person {
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

public static class City {
private String titleCity;
private List<Person> people;

public City(String title, List<Person> people) {
this.titleCity = title;
this.people = people;
}

public String getTitleCity() {
return titleCity;
}

public void setTitleCity(String titleCity) {
this.titleCity = titleCity;
}

@FlattenCollection
public List<Person> getPeople() {
return people;
}

public void setPeople(List<Person> people) {
this.people = people;
}
}
}


Related Topics



Leave a reply



Submit