NPE with JsonIdentityReference and serializer modifier

66 views
Skip to first unread message

ta...@transdata.net

unread,
May 17, 2018, 10:20:11 AM5/17/18
to jackson-user
Using 2.7.4 I tried to use a BeanSerializerModifier to avoid serialization of beans meeting some condition.
This works fine. But using also identity references I run into a NullPointerException:

at com.fasterxml.jackson.databind.ser.impl.WritableObjectId.writeAsField(WritableObjectId.java:64)
at com
.fasterxml.jackson.databind.ser.std.BeanSerializerBase._serializeWithObjectId(BeanSerializerBase.java:596)
at com
.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:148)


Code to reproduce:

public class SerializationTest {

   
private static final String REFID_PROPERTY = "ref-id";

   
public static abstract class AbstractSkippable {

       
private String someData;

       
public abstract boolean hasData();

       
public String getSomeData() {
           
return someData;
       
}

       
public void setSomeData(String someData) {
           
this.someData = someData;
       
}

   
}

   
@JsonIdentityReference
   
@JsonIdentityInfo(property = REFID_PROPERTY, generator = ObjectIdGenerators.UUIDGenerator.class)
   
public static class Data extends AbstractSkippable {

       
@Override
       
public boolean hasData() {
           
return true;
       
}

   
}

   
public static class MyData {

       
private Data data1;
       
private Data data2;

       
public Data getData1() {
           
return data1;
       
}

       
public void setData1(Data data1) {
           
this.data1 = data1;
       
}

       
public Data getData2() {
           
return data2;
       
}

       
public void setData2(Data data2) {
           
this.data2 = data2;
       
}

   
}

   
public static class AbstractSkippableSerializer extends JsonSerializer<AbstractSkippable> {

       
private final JsonSerializer<Object> defaultSerializer;

       
public AbstractSkippableSerializer(JsonSerializer<Object> defaultSerializer) {
           
if (defaultSerializer == null) {
               
throw new IllegalArgumentException("defaultSerializer must not be null");
           
}
           
this.defaultSerializer = defaultSerializer;
       
}

       
@Override
       
public void serialize(AbstractSkippable value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            defaultSerializer
.serialize(value, gen, serializers);
       
}

       
@Override
       
public boolean isEmpty(SerializerProvider provider, AbstractSkippable value) {
           
return value == null || !value.hasData();
       
}

   
}

   
public static class SkippableSerializerModifier extends BeanSerializerModifier {

       
@Override
       
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
           
Class<?> beanClass = beanDesc.getBeanClass();
           
if (AbstractSkippable.class.isAssignableFrom(beanClass)) {
               
@SuppressWarnings("unchecked")
               
JsonSerializer<Object> defaultSerializer = (JsonSerializer<Object>) serializer;
               
return new AbstractSkippableSerializer(defaultSerializer);
           
}
           
return serializer;
       
}

   
}

   
@Test
   
public void test() throws JsonProcessingException {
       
JacksonXmlModule mod = new JacksonXmlModule();
        mod
.setSerializerModifier(new SkippableSerializerModifier());
       
XmlMapper mapper = new XmlMapper(mod);
       
MyData myData = new MyData();
       
Data data = new Data();
        data
.setSomeData("data");
        myData
.setData1(data);
        myData
.setData2(data);
       
String xml = mapper.writeValueAsString(data);
       
System.out.println(xml);
   
}

}


Any idea?

--Heiko

Tatu Saloranta

unread,
May 17, 2018, 4:57:33 PM5/17/18
to jackson-user
On Wed, May 16, 2018 at 11:35 PM, <ta...@transdata.net> wrote:
> Using 2.7.4 I tried to use a BeanSerializerModifier to avoid serialization
> of beans meeting some condition.

First things first: as usual, it is good idea to always verify with
the last patch version of the minor version (2.7.9 in this case),
to ensure there are no further fixes. And if possible, later minor versions.

But in this case I suspect problem is that you are not forwarding calls to

`createContextual()` (for `ContextualSerializer`s)
`resolve()` (for `ResolvableSerializer`s)

in your case, you need to forward these to `defaultSerializer`:
"resolve()" does not return anything, it just needs to be called.
But `createContextual()` may create modified instance (as per property
definition overrides), and you may need to construct
a new `AbstractSkippableSerializer`.

-+ Tatu +-
> --
> You received this message because you are subscribed to the Google Groups
> "jackson-user" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to jackson-user...@googlegroups.com.
> To post to this group, send email to jackso...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

ta...@transdata.net

unread,
May 25, 2018, 6:16:32 AM5/25/18
to jackson-user
2.7.9 does not work either :-(
But I am bound to use 2.7.4 anyway.

And implementing ResolvableSerializer and ContextualSerializer in the following way still results in a NPE in WritableObjectId.writeAsField.
BTW, why is the property in createContextual 'null'?

Any other ideas/suggestions?

Thanks in advance.

@Overridepublic void resolve(SerializerProvider serializerProvider) throws JsonMappingException {
   
if (defaultSerializer instanceof ResolvableSerializer) {
       
((ResolvableSerializer) defaultSerializer).resolve(serializerProvider);
   
}
}

@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
   
System.out.println("Property: " + property);
   
return defaultSerializer;
}

Tatu Saloranta

unread,
May 25, 2018, 11:09:12 AM5/25/18
to jackson-user
On Fri, May 25, 2018 at 3:16 AM, <ta...@transdata.net> wrote:
> 2.7.9 does not work either :-(
> But I am bound to use 2.7.4 anyway.

Too bad wrt 2.7.9, although I am curious as to why you think you had
to use 2.7.4?
Patch releases are API-compatible with each other and there should not
be any reason why you
would need to align patches. This is different from major and minor
releases that should definitely match (major must,
minor versions should, although some of adjacent minor versions may work).

> And implementing ResolvableSerializer and ContextualSerializer in the
> following way still results in a NPE in WritableObjectId.writeAsField.
> BTW, why is the property in createContextual 'null'?

It will be `null` for "root value" serializer: the value given to
mapper which is not referenced by
a property. But since contextualization may still be needed (as it may
differ from serializers used for
values referenced by a property), method is still called.

>
> Any other ideas/suggestions?
>
> Thanks in advance.
>
> @Overridepublic void resolve(SerializerProvider serializerProvider) throws
> JsonMappingException {
> if (defaultSerializer instanceof ResolvableSerializer) {
> ((ResolvableSerializer)
> defaultSerializer).resolve(serializerProvider);
> }
> }
>
> @Override
> public JsonSerializer<?> createContextual(SerializerProvider prov,
> BeanProperty property) throws JsonMappingException {
> System.out.println("Property: " + property);
> return defaultSerializer;
> }

^^^^^^^^^
you would need to delegate this similarly, and this is usually more
important of the two.
Did it not get called at all?

-+ Tatu +-

ta...@transdata.net

unread,
May 28, 2018, 2:23:20 AM5/28/18
to jackson-user

Too bad wrt 2.7.9, although I am curious as to why you think you had
to use 2.7.4?
Patch releases are API-compatible with each other and there should not
be any reason why you
would need to align patches. This is different from major and minor
releases that should definitely match (major must,
minor versions should, although some of adjacent minor versions may work).

You're right, of course. I could talk to the wildfly admin to pull up the version of the module to 2.7.9.

It will be `null` for "root value" serializer: the value given to
mapper which is not referenced by
a property. But since contextualization may still be needed (as it may
differ from serializers used for
values referenced by a property), method is still called.

I see. Thanks for clarification!

you would need to delegate this similarly, and this is usually more
important of the two.
Did it not get called at all?
 
Yeah! That's it! Delegating to the default serializer did the thing.
Thanks a million. You're my hero of the day :-)

-- Heiko

Tatu Saloranta

unread,
May 29, 2018, 2:33:46 PM5/29/18
to jackson-user
Excellent!

-+ Tatu +-
Message has been deleted
Message has been deleted

Tatu Saloranta

unread,
May 21, 2019, 2:57:04 PM5/21/19
to jackson-user
On Tue, May 21, 2019 at 11:20 AM undefined <yohan....@gmail.com> wrote:
 Hello from the future !

I've ran into this issue but I don't understand what you guys meant by "Delegating to the default serializer" ?

That would refer to your custom serializer keeping a reference to the "default serializer", one that you can get when constructing replacement with `BeanSerializerModifier`, and then calling that instance from `serialize()` and `serializeWithType()` to do the actual work (either always, or just in some specific cases).

Sometimes you can alternatively access "default" serializer dynamically from `serialize()` (using `SerializerProvider` which has methods for finding serializers): this works if type you use to find serializer is different from the type you registered custom serializer with, or if custom serializer was found from annotation (`@JsonSerialize(using = ...)`).

-+ Tatu +-
 

Thanks in advance,
Yohan

--
You received this message because you are subscribed to the Google Groups "jackson-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jackson-user...@googlegroups.com.
To post to this group, send email to jackso...@googlegroups.com.
Message has been deleted

Tatu Saloranta

unread,
May 22, 2019, 6:04:27 PM5/22/19
to jackson-user
On Tue, May 21, 2019 at 2:02 PM undefined <yohan....@gmail.com> wrote:
>
> Thank you for your response.
>
> However, are you sure you are referencing the problem described above ?
>
> If you actually are referencing to the post, I don't understand your reply. I do store an instance of the default serializer in my custom serializer.
> My actual problem is the NPE during the inside job of @JsonIdentityInfo. More precisely the method com.fasterxml.jackson.databind.ser.impl.WritableObjectId.writeAsField causing the NPE when trying to access a null serializer.
> How can I pass my default serializer to this class ?

You kept on asking question "I don't understand what you mean by
delegating", so I answered that.

At this point I have wasted enough time to understand the question. I
hope someone else has time to spend on this and help,

-+ Tatu +-

>
>
> Le mardi 21 mai 2019 20:57:04 UTC+2, Tatu Saloranta a écrit :
>>
>> On Tue, May 21, 2019 at 11:20 AM undefined <yohan...@gmail.com> wrote:
>>>
>>> Hello from the future !
>>>
>>> I've ran into this issue but I don't understand what you guys meant by "Delegating to the default serializer" ?
>>
>>
>> That would refer to your custom serializer keeping a reference to the "default serializer", one that you can get when constructing replacement with `BeanSerializerModifier`, and then calling that instance from `serialize()` and `serializeWithType()` to do the actual work (either always, or just in some specific cases).
>>
>> Sometimes you can alternatively access "default" serializer dynamically from `serialize()` (using `SerializerProvider` which has methods for finding serializers): this works if type you use to find serializer is different from the type you registered custom serializer with, or if custom serializer was found from annotation (`@JsonSerialize(using = ...)`).
>>
>> -+ Tatu +-
>>
>>>
>>>
>>> Thanks in advance,
>>> Yohan
>>>
>>> --
>>> You received this message because you are subscribed to the Google Groups "jackson-user" group.
>>> To unsubscribe from this group and stop receiving emails from it, send an email to jackso...@googlegroups.com.
>>> To post to this group, send email to jackso...@googlegroups.com.
>>> To view this discussion on the web visit https://groups.google.com/d/msgid/jackson-user/a0b02f70-8256-414c-834f-60fcbce1fda6%40googlegroups.com.
>>> For more options, visit https://groups.google.com/d/optout.
>
> --
> You received this message because you are subscribed to the Google Groups "jackson-user" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to jackson-user...@googlegroups.com.
> To post to this group, send email to jackso...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/jackson-user/34a1f1f2-62fe-46bd-b208-8840f8035a5e%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages