Using JsonTypeInfo to generate type info in Map<String, Collection<?>> values

807 views
Skip to first unread message

Mark Gibson

unread,
Sep 11, 2021, 12:02:26 AM9/11/21
to jackso...@googlegroups.com
Hi,

I have a case where a map is used to store many sets of values.  

Map<String, Set<?>>

Each set instance is a set of homogenous data types.  But the different entries in the map hold a heterogenous collection of set types.  Difficult to put in to English, but the example code below is actually quite simple.  The description here is to imply that the set always has to be Set<?> and can't be fully typed.

Obviously, to successfully deserialize such a heterogeneous map, type information must be included in the serialized Json.  This is achievable by configuring the ObjectMapper.  For example,

mapper.enableDefaultTyping( );

means all type information is recorded in the serialized JSON.  However, I'm trying to use @JonTypeInfo attribute so that callers of our library do not need to remember to use a correctly configured ObjectMapper.  JsonTypeInfo seems to work for a simple set, but does not work when applied to the map ...

public class MyTest
{
@JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "@type",
visible = true
)
public enum MyEnum
{
ONE,
TWO,
THREE
}

@JsonAutoDetect( fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE )
public static class MyClass
{
@JsonProperty( )
MyEnum singleEnum;

@JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "@type",
visible = true
)
@JsonProperty( )
Set<?> enumSet;

@JsonProperty( )
@JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "@type",
visible = true
)
Map<String, Set<?>> mapOfEnumSets = new LinkedHashMap<>( );
}

public static void main( String[] args ) throws JsonProcessingException
{
MyClass mc = new MyClass( );
mc.singleEnum = MyEnum.TWO;
mc.enumSet = Stream.of( MyEnum.ONE, MyEnum.TWO, MyEnum.THREE ).collect( Collectors.toSet( ) );
mc.mapOfEnumSets.put( "firstKey", Stream.of( MyEnum.ONE, MyEnum.TWO, MyEnum.THREE ).collect( Collectors.toSet( ) ) );

ObjectMapper om = new ObjectMapper( );

String jsonString = om.writeValueAsString( mc );

System.out.println( jsonString );
}
}

This is the serialized JSON:

{"singleEnum":["MyTest$MyEnum","TWO"],"enumSet":[["MyTest$MyEnum","TWO"],["MyTest$MyEnum","THREE"],["MyTest$MyEnum","ONE"]],"mapOfEnumSets":{"firstKey":["java.util.HashSet",["TWO","THREE","ONE"]]}}
Is it possible to get the type info generated for the contents of the sets in the map using JsonTypeInfo and/or other annotations?  Or are we limited to the direct configuration of the ObjectMapper?

Thanks
Mark

Tatu Saloranta

unread,
Sep 12, 2021, 12:12:22 AM9/12/21
to jackson-user
Not sure I understand the exact question, but couple of notes that may help:

1. Use of `@JsonTypeInfo` on Enum types does not really make sense
(here for MyEnum), since enum types can not be extended. (not a big
problem here, but worth mentioning). Adding @JsonTypeInfo on enum has
no effect on Set, Map, since the declared value base type is not
MyEnum.
2. Use of `@JsonTypeInfo` for structured types (Collections, Maps,
arrays) affects main-level values/elements. So, works fine for
`Set<Object>` (which is what `Set<?>` is from Jackson's perspective),
but not really for multiple levels (Map<String, Set<?>> ) -- in latter
case it'd mean "polymorphic subtype of Set<?>"

I assume (2) is the problem wrt nested Maps: polymorphic handling can
only be enabled for the immediate child value, but not to the
innermost level.

You could, however, declare your own special `Set` type there... something like

@JsonTypeInfo(....)
static class MySet<T> extends HashSet<T> { }

and then use that

> Map<String, MySet<?> mapOfEnumSets = new LinkedHashMap<>( );

in declaration. This would, I think, force inclusion and use of
polymorphic type information.

-+ Tatu +-

Mark Gibson

unread,
Sep 13, 2021, 7:47:38 AM9/13/21
to jackso...@googlegroups.com
Thank you Tatu

It is #2 I'm referring to.  I should have included the output when enableDefaultTyping is on ...

{"singleEnum":"TWO","enumSet":["java.util.HashSet",[["MyTest$MyEnum","TWO"],["MyTest$MyEnum","THREE"],["MyTest$MyEnum","ONE"]]],"mapOfEnumSets":["java.util.LinkedHashMap",{"firstKey":["java.util.HashSet",[["MyTest$MyEnum","TWO"],["MyTest$MyEnum","THREE"],["MyTest$MyEnum","ONE"]]]}]}

 I understand your point on typing the Set implementations, but that becomes a bit awkward considering the map can contain differently typed sets at the same time.

I have the solution with enableDefaultTyping (or it's newer, safer equivalent).  I was just hoping I could achieve the same with annotations, as somebody is bound to forget to configure their mapper correctly :)

Thanks
Mark

--
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 view this discussion on the web visit https://groups.google.com/d/msgid/jackson-user/CAL4a10jGUvqaYVWRnht5TiOEMLM2%2B%3Dc7zYGvS7XuaAFw-SKhqA%40mail.gmail.com.
Reply all
Reply to author
Forward
0 new messages