Jackson How to retrieve parent bean in a custom Serializer/Deserializer - java

In a custom serializer/deserializer, is there a way to retrieve the parent bean of the field?
For example:
public class Foo {
#JsonSerialize(using = MyCustomSerializer.class)
public Bar bar;
}
public class Bar { }
public class MyCustomSerializer extends JsonSerializer<Bar> {
#Override
public void serialize(
Bar value,
JsonGenerator jgen,
SerializerProvider serializers)
throws IOException, JsonProcessingException
{
// get Foo ??
}
}
Here I'd like to get Foo in my serializer without having to have a reference inside Bar.

If you are using Jackson 2.5, it is possible to access parent object via JsonGenerator.getCurrentValue(). Or, further up the hierarchy, going via getOutputContext() (which has getParent() as well as getCurrentValue() method).
This is also available through JsonParser for custom deserializer.

For deserialization, where you don't have access to the JsonGenerator object. The following worked for me:
JsonStreamContext parsingContext = jsonParser.getParsingContext();
JsonStreamContext parent = parsingContext.getParent();
Object currentValue = parent.getCurrentValue();

Note: getCurrentValue will be null if you are using custom serialization
I worked around this by setting the parent object into the child's serializer instance and then accessing it when the child's serializer was called by jackson.

Related

replace field name using #JsonTypeInfo

I would like to know if there is a way to replace the fieldname using #JsonTypeInfo
Here is what I want to achieve
class Tnode<T>{
#JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT, property="type")
T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
I get output as
{
"obj": {
"Foo": {
"name": "xyz"
}
}
}
The whole point is I do not want an extra layer "obj" as field name. I want the "Foo" to be one level above. In the code I am setting generic type to a concrete type. I want concrete class name to show up rather than having it wrapped.
I did try changing to include=As.PROPERTY but it will stil output as "obj".
I did solve using custom serializer. But I have to set every field.
public class CustomSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeObjectField("somename", value);
jgen.writeEndObject();
}
}
But the problem is when ever I have to add a instance variable in Tnode class I have to add that code in the custom serializer. And I want to avoid that.
Any suggestions?
There is no way to do that. Name of the property to contain Object, wrapped in type information, has to be statically known (to locate logical property). It can not vary.

Jackson: serialize non-initialized collection field as empty

I have a POJO with a field or property, containing collection of objects, something like this:
public class Box {
public List<Items> items;
}
By default, value of items is null, and I do not want to initialize it with empty list.
Now, if I try to serialize it with Jackson, I get NullPointerException. Is there a simple way to make Jackson not break on such value and serialize it as an empty collection: [ ]?
Note. This class is just a simplified example. In reality, there are a hundred of classes and a number of fields with different names in each of them, which are occasionally set to null sometimes somewhere in the code, breaking serialization in runtime.
If you do not want to change the contract of your POJO class, think about the possibility to define custom Jackson serializer / deserializer which extend JsonSerializer<T> and JsonDeserializer<T> respectively. E.g.:
public class CountryDeserializer extends JsonDeserializer<CountryCode> {
#Override
public CountryCode deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException {
return CountryCode.getByCode(jp.getText());
}
}
and then
#JsonDeserialize(using=CountryDeserializer.class)
private CountryCode country;
You can check whether your field is null and act accordingly, in both directions (serialization / deserialization).
Have you considered making this class a JavaBean?
In that case, you would be able to give a default value in the getter:
public class Box {
private List<Items> items;
public List<Items> getItems() {
if(null == items) {
return Collections.emptyList();
}
return this.items;
}
//Setter here
}
This approach would prevent a lot of trouble related to Jackson's assumptions.
Update: Based on clarification... You could implement a custom serializer for the list type (and/or any other desired customization). Please note that :
public class ListSerializer extends JsonSerializer<List> {
#Override
public void serialize(List value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
JsonProcessingException {
if (null == value) {
provider.defaultSerializeValue(new ArrayList<Object>(), jgen);
} else {
provider.defaultSerializeValue(value, jgen);
}
}
}
//Then your code could set the serializer on the object mapper as follows:
objectMapper.addSerializer(List.class, new ListSerializer());
Repeat for all such customization.
Code was inspired by this article: http://www.baeldung.com/jackson-custom-serialization

Jackson custom JsonSerializer - conditionally call default serializer

What I want is to use default BeanSerializer conditionally for my class's objects:
class MyCustomSerializer extends StdSerializer<AbstractEntity> {
public MyCustomSerializer() {
super(AbstractEntity.class);
}
#Override
public void serialize(AbstractEntity o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (someCondition()) {
serializeNormalWay(); //how?
} else {
//custom serialization
}
}
}
I've tried to do something like that:
serializerProvider.defaultSerializeValue(o, jsonGenerator);
but this calls MyCustomSerializer's method and I have never-ending recursion.
How can I get appropriate Serializer object, that I could use for ordinary bean Serialization?
This requires bit more complicated setup: instead of directly overriding serializer to use, you need to let Jackson create one, then take over.
This may be done by registering BeanSerializerModifier (via Module), method modifySerializer(...). You will be given default serializer that would be used, and you can construct custom one, passing that default one.

How to make Jackson serializer more DRY?

I'm using ember.js as the client side framework. Out of the box, this framework expects a certain format of JSON. I'm trying to make Jackson output that format. This is not important for answering this question, but mentioned it and tagged it because it may help more users in the same situation.
Basically, I want that every referenced object (not the root ones) be outputted as their id. I'll give you quick example.
These classes:
public abstract class BaseEntity{
protected Long id;
}
public class Resource{
private String name;
private AnotherResource subResource;
private List<AnotherResource> subResources;
//getters and setters
}
public class SubResource{
private String value;
//getters and setters
}
with these example instances:
// Sub resources
SubResource sr1 = new SubResource();
sr1.setId(2);
sr1.setValue("some string");
SubResource sr2 = new SubResource();
sr2.setId(3);
sr2.setValue("some string");
SubResource sr3 = new SubResource();
sr3.setId(4);
sr3.setValue("some string");
// resource
Resource r = new Resource();
r.setId(1);
r.setName("bla");
r.setSubResource(sr1);
ArrayList<SubResource> list = new ArrayList<SubResource>();
list.add(sr1);
list.add(sr2);
list.add(sr3);
r.setSubResources(list);
serializing r should output:
{
"resource":{
"id": 1,
"name": "bla",
"sub_resource_id": 2,
"sub_resource_ids": [
1,
2,
3
]
}
}
We can notice a couple of things here:
key names are concatenated with "_id" or "_ids", depending if it is a referenced object or a collection of referenced objects
only the id of referenced objects is serialized
in case of a collection of referenced objects, an array of their ids is serialized
Regarding property names (1), I've already sorted this out with #JsonProperty annotation.
As for the rest, I wrote the following serializer:
public class BaseEntityIdSerializer extends JsonSerializer<BaseEntity> implements ContextualSerializer {
public void serialize(BaseEntity value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
}
#Override
public void serializeWithType(BaseEntity value, JsonGenerator jgen,
SerializerProvider provider, TypeSerializer typeSer)
throws IOException, JsonProcessingException {
serialize(value, jgen, provider);
}
#Override
public JsonSerializer<?> createContextual(SerializerProvider prov,
BeanProperty property) throws JsonMappingException {
if(property.getType().isCollectionLikeType()){
return new BaseEntityIdCollectionSerializer();
} else {
return new BaseEntityIdSimpleSerializer();
}
}
public class BaseEntityIdSimpleSerializer extends StdSerializer<BaseEntity>{
public BaseEntityIdSimpleSerializer(){
super(BaseEntity.class);
}
#Override
public void serialize(BaseEntity value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonGenerationException {
jgen.writeNumber(value.getId());
}
#Override
public void serializeWithType(BaseEntity value, JsonGenerator jgen,
SerializerProvider provider, TypeSerializer typeSer)
throws IOException, JsonProcessingException {
serialize(value, jgen, provider);
}
}
public class BaseEntityIdCollectionSerializer extends StdSerializer<Collection<? extends BaseEntity>>{
public BaseEntityIdCollectionSerializer(){
super(Collection.class, false);
}
#Override
public void serialize(Collection<? extends BaseEntity> value,
JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException {
jgen.writeStartArray();
for(BaseEntity b:value){
jgen.writeNumber(b.getId());
}
jgen.writeEndArray();
}
#Override
public void serializeWithType(Collection<? extends BaseEntity> value, JsonGenerator jgen,
SerializerProvider provider, TypeSerializer typeSer)
throws IOException, JsonProcessingException {
serialize(value, jgen, provider);
}
}
}
and then used #JsonSerialize(using=BaseEntityIdSerializer.class)
This does the job. Outputs the correct JSON. However I feel like I'm repeating a lot of code. For example, I'm writing different serializer classes for collections and single objects. I would expect to use the single serializer multiple times. Something more composable.
And obviously I'm using the wrong class to resolve the serializers (I'm forced to implement serialize but I do nothing).
What are your insights? How can I improve this serializer?
Also, is it possible to handle the property names (concatenate "_id") in the serializer? This way I could go without the #JsonProperty annotation.
Thanks.
I am not sure you actually need to define custom serializer at all: wouldn't value serializer work just fine? Jackson can indeed compose these automatically (and cover matching array serializer). So for collection case, return this; should work for contextual case.
Property renaming would not work at value serializer level, because it is up to POJO serializer (BeanSerializer) to do that. That is, value serializer does not write property name (it has already been called if necessary, for JSON Objects; or omitted, for JSON Arrays, root-level values).
This is one part of design where different structuring for Jackson 1.0 might have made sense (make value serializer have 2 methods; one for "simple" value for array elements, root level; second one for "named" property values), but it is too late to change that.
However: you may be able to handle renaming outside of serializer, perhaps by:
Custom JacksonAnnotationIntrospector that uses type information to modify name -- either for serialization or deserialization, or both (separate calls are made)
Via BeanSerializerModifier, renaming properties similarly on one of callbacks.

Serialize one class in two different ways with Jackson

In one of our projects we use a java webapp talking to a MongoDB instance. In the database, we use DBRefs to keep track of some object relations. We (de)serialize with POJO objects using jackson (using mongodb-jackson-mapper).
However, we use the same POJOs to then (de)serialize to the outside world, where our front end deals with presenting the JSON.
Now, we need a way for the serialization for the outside world to contain the referenced object from a DBRef (so that the UI can present the full object), while we obviously want to have the DBRef written to the database, and not the whole object.
Right now I wrote some untested static nested class code:
public static class FooReference {
public DBRef<Foo> foo;
// FIXME how to ensure that this doesn't go into the database?
public Foo getFoo() {
return foo.fetch();
}
}
Ideally I would like a way to annotate this so that I could (de)serialize it either with or without the getFoo() result, probably depending on some configuration object. Is this possible? Do you see a better way of going about doing this?
From looking at options, it seems you can annotate properties to only be shown if a given View is passed to the ObjectMapper used for serialization. You could thus edit the class:
public static class FooReference {
public DBRef<Foo> foo;
#JsonView(Views.WebView.class)
public Foo getFoo() {
return foo.fetch();
}
}
and provide:
class Views {
static class WebView { }
}
and then serialize after creating a configuration with the correct view:
SerializationConfig conf = objectMapper.getSerializationConfig().withView(Views.WebView.class);
objectMapper.setSerializationConfig(conf);
Which would then serialize it. Not specifying the view when serializing with the MongoDB wrapper would mean the method would be ignored. Properties without a JsonView annotation are serialized by default, a behaviour you can change by specifying:
objectMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
More info is available on the Jackson Wiki.
There are still other alternatives, too, it turns out: there are Jackson MixIns which would let you override (de)serialization behaviour of parts of a class without modifying the class itself, and as of Jackson 2.0 (very recent release) there are filters, too.
Use a custom JSONSerializer and apply your logic in the serialize method:
public static class FooReference {
public DBRef<Foo> foo;
#JsonSerialize(using = CustomSerializer.class)
public Foo getFoo() {
return foo.fetch();
}
}
public class CustomSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
// jgen.writeObjectField ...
}
}

Categories