class level JsonSerializable vs Method level

157 views
Skip to first unread message

matt Smith

unread,
Dec 20, 2018, 2:22:34 PM12/20/18
to jackson-user

I encountered a case where class level JsonSerializable annotation does not require registration with Simple Module  but method level does


Here is the example I tried

@JsonSerialize(using = ItemRowSerializer.class )
       
private ItemRow tes
tItem(){
           
return new ItemRow("abc", Arrays.asList("item1", "item2", "item3"));
       
}


I test it
out like this


String jsonResult =  new ObjectMapper().writeValueAsString(testItem());
System.out.println(jsonResult);



I noticed that jsonString did not serialize using ItemRowSerializer.

However, when I did this, it gave the expected jsonString

 
SimpleModule module = new SimpleModule()
 
module.addSerializer(ItemRowSerializer.class)
 mapper
.register(module);
 
String jsonResult =  new ObjectMapper().writeValueAsString(testItem());
 
System.out.println(jsonResult);


On the otherhand, if I had my ItemRow class annoated with @JsonSerializable,  there was no need with module at all.
i.e

@JsonSerializable(using = ItemRowSerailizer
public class ItemRow<T> {...}


Since I am passing this serialized object over the wire, I do not prefer to write to mapper and send string representation across.

how would I go about getting method level annotation working without module registration?


for details on the actual serializer implementation, please refer here



Tatu Saloranta

unread,
Dec 20, 2018, 4:25:59 PM12/20/18
to jackson-user
On Thu, Dec 20, 2018 at 11:22 AM matt Smith <matt.smi...@gmail.com> wrote:
>
> I encountered a case where class level JsonSerializable annotation does not require registration with Simple Module but method level does.

Hmmh. This seems odd, let's see.

> Here is the example I tried
>
> @JsonSerialize(using = ItemRowSerializer.class )
> private ItemRow tes
> tItem(){
> return new ItemRow("abc", Arrays.asList("item1", "item2", "item3"));
> }
>
>
> I test it out like this
>
>
> String jsonResult = new ObjectMapper().writeValueAsString(testItem());
> System.out.println(jsonResult);
>
> I noticed that jsonString did not serialize using ItemRowSerializer.

Ah. No, it won't. Jackson does not see you calling the method; Jackson
sees the object you pass, which was returned by the method. There is
no linkage there: Jackson can only introspects classes you pass to it.

> However, when I did this, it gave the expected jsonString
>
>
> SimpleModule module = new SimpleModule()
> module.addSerializer(ItemRowSerializer.class)
> mapper.register(module);
> String jsonResult = new ObjectMapper().writeValueAsString(testItem());
> System.out.println(jsonResult);
>
>
> On the otherhand, if I had my ItemRow class annoated with @JsonSerializable, there was no need with module at all.

I think you mean `@JsonSerialize` here (there is interface type
`JsonSerializable`, but no annotation).

> i.e
>
> @JsonSerializable(using = ItemRowSerailizer
> public class ItemRow<T> {...}
>
>
> Since I am passing this serialized object over the wire, I do not prefer to write to mapper and send string representation across.
>
> how would I go about getting method level annotation working without module registration?

Method level annotations only work when property is accessed via
method (or field) in question.

So if you had something like:

public class Rows
@JsonSerialize(using = .... )
public ItemRow<Type> getRows() { ... }
}

and serialized an instance of `Rows`, serializer would be found. But
for root values, there is no such reference.

On related note, I strongly recommend always using a wrapper type
around generic values, so that root value is never of generic type.
Reason for this is that Java Type Erasure will often cause problems if
attempting to pass an instance of generic type -- so Jackson can not
detect intended type (all it sees is type variable from class
declaration).

So, attempts to serialize, say, Row<T>, will be seen as `Row<?>`, and
possibly (although not always) cause issues in finding handlers for
the type -- for example, if value type has annotations, they may not
be visible.

-+ Tatu +-

matt Smith

unread,
Dec 21, 2018, 3:06:03 AM12/21/18
to jackson-user
so I have made this wrapper and annotated getter with @JsonSerialize. however, I do not see it being applied. what is wrong here?

public class Wrapper {


   
private Map<RecordDomain, ItemRow> foo;


   
public Wrapper(Map<RecordDomain, ItemRow> fooz) {
        foo
= new HashMap<>(fooz);
   
}


   
@JsonSerialize( contentUsing = ItemRowSerializer.class)
   
public Map<RecordDomain, ItemRow> getFoo() {
       
return foo;
   
}


   
public void setFoo(Map<RecordDomain, ItemRow> foo) {
       
this.foo = foo;
   
}
}

Tatu Saloranta

unread,
Dec 21, 2018, 3:49:21 PM12/21/18
to jackson-user
On Fri, Dec 21, 2018 at 12:06 AM matt Smith <matt.smi...@gmail.com> wrote:
>
> so I have made this wrapper and annotated getter with @JsonSerialize. however, I do not see it being applied. what is wrong here?

That looks like it should work. If not, a unit test & issue would make sense.

-+ 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.

matt Smith

unread,
Dec 21, 2018, 4:52:41 PM12/21/18
to jackson-user
even moving annotation to the field did not work.
Note I have annotated class ItemRow class which ofcourse i can't since it is part of legacy code.

matt Smith

unread,
Dec 21, 2018, 5:30:05 PM12/21/18
to jackson-user
In the above case, I called `mapper.writeValueAsString(wrapper.getFoo());`

I changed to the following (moved to annotation to the field level).  when I write using `mapper.writeValueAsString(wrapper)`, I see the expected serialization applied. However, extra node "foo" is added at root. is there a way to not have it?

public class Wrapper {


   
@JsonSerialize(contentUsing = ItemRowSerializer.class)

   
private Map<RecordDomain, ItemRow> foo;


   
public Wrapper(Map<RecordDomain, ItemRow> fooz) {
        foo
= new HashMap<>(fooz);
   
}



   
public Map<RecordDomain, ItemRow> getFoo() {

Tatu Saloranta

unread,
Dec 24, 2018, 7:54:23 PM12/24/18
to jackson-user
On Fri, Dec 21, 2018 at 1:52 PM matt Smith <matt.smi...@gmail.com> wrote:
>
> even moving annotation to the field did not work.
> Note I have annotated class ItemRow class which ofcourse i can't since it is part of legacy code.

Except that you should be able to do that with mix-in annotations.
See for example

https://dzone.com/articles/jackson-mix-in-annotations

-+ Tatu +-

Tatu Saloranta

unread,
Dec 24, 2018, 7:56:27 PM12/24/18
to jackson-user
On Fri, Dec 21, 2018 at 2:30 PM matt Smith <matt.smi...@gmail.com> wrote:
>
> In the above case, I called `mapper.writeValueAsString(wrapper.getFoo());`
>
> I changed to the following (moved to annotation to the field level). when I write using `mapper.writeValueAsString(wrapper)`, I see the expected serialization applied. However, extra node "foo" is added at root. is there a way to not have it?

No, if applied as content serializer, Map serializer will produce wrapping.
You would need custom serializer that just serializes entries and not
enclose them within START_OBJECT / END_OBJECT.

matt Smith

unread,
Jan 2, 2019, 7:09:25 PM1/2/19
to jackson-user
I just added @JsonAnyGetter to the wrapper class and now when I  write mapper.writeValueAsString(wrapper), it gives the expected output. I don't understand why but it just works!

    @JsonAnyGetter

   
@JsonSerialize(contentUsing = ItemRowSerializer.class)
 
private Map<RecordDomain, ItemRow> foo;

Tatu Saloranta

unread,
Jan 2, 2019, 10:44:13 PM1/2/19
to jackson-user
On Wed, Jan 2, 2019 at 4:09 PM matt Smith <matt.smi...@gmail.com> wrote:
I just added @JsonAnyGetter to the wrapper class and now when I  write mapper.writeValueAsString(wrapper), it gives the expected output. I don't understand why but it just works!

    @JsonAnyGetter
   
@JsonSerialize(contentUsing = ItemRowSerializer.class)
 
private Map<RecordDomain, ItemRow> foo;





Good thinking -- such usage does indeed get rid of extra wrapper, as entries from Map are included directly in enclosing JSON Object.
That is actually a neat example of sort of "off-label" usage: something that is supported (it's not accidental), but different from more common
approach of simply using serializer for known type like `String`.

-+ Tatu +-
Reply all
Reply to author
Forward
0 new messages