Default typing with interface and implementation

30 views
Skip to first unread message

itineric itineric

unread,
Jan 30, 2023, 2:53:54 PM1/30/23
to jackson-user
Hi all,

I'm trying to work with interfaces and related bean implementation of that interface. So far, all good.
I then tried to add some Map<String, Object> to the equation and I cannot understand why I get this result.
Short story: I'm trying to get the @class to be set to the interface name using a TypeIdResolver and it work as expected on a direct call but not when the object is inside a Map.
I simplified everything to get an example, here it is :

UserInterface.java
@JsonTypeInfo(use = Id.CLASS)
@JsonTypeIdResolver(UserTypeResolver.class)
public interface UserInterface
{
  String getName();
}


UserImplementation.java
public class UserImplementation implements UserInterface
{
  private String name;

  @Override
  public String getName()
  {
    return this.name;
  }

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


UserTypeResolver.java
public class UserTypeResolver implements TypeIdResolver
{
  private JavaType baseType;

  @Override
  public void init(final JavaType baseType)
  {
    this.baseType = baseType;
  }

  @Override
  public String idFromValue(final Object value)
  {
    return idFromValueAndType(value, value.getClass());
  }

  @Override
  public String idFromValueAndType(final Object value, final Class<?> suggestedType)
  {
    return "com.example.jackson.UserInterface";
  }

  @Override
  public String idFromBaseType()
  {
    return idFromValueAndType(null, this.baseType.getRawClass());
  }

  @Override
  public JavaType typeFromId(final DatabindContext context, final String id)
    throws IOException
  {
    return TypeFactory.defaultInstance().constructSpecializedType(this.baseType, UserImplementation.class);
  }

  @Override
  public String getDescForKnownTypeIds()
  {
    return null;
  }

  @Override
  public Id getMechanism()
  {
    return Id.CLASS;
  }
}


And finally the test I played:

    final UserImplementation user = new UserImplementation();
    user.setName("test");

    final Map<String, Object> parameters = new HashMap<>();
    parameters.put("user", user);

    final ObjectMapper mapper = new ObjectMapper();
    mapper.enableDefaultTyping(DefaultTyping.JAVA_LANG_OBJECT, As.PROPERTY);

    StringWriter writer = new StringWriter();
    mapper.writeValue(writer, user);
    System.out.println(writer);

    writer = new StringWriter();
    mapper.writeValue(writer, parameters);
    System.out.println(writer);


The output is:
{"@class":"com.example.jackson.UserInterface","name":"test"}
{"user":{"@class":"com.example.jackson.UserImplementation","name":"test"}}


The expected output I'd like to see:
{"@class":"com.example.jackson.UserInterface","name":"test"}
{"user":{"@class":"com.example.jackson.UserInterface  ","name":"test"}}


What am I doing wrong? Is that the expected output? What could I do to get the one I expect?

Thank you


Tatu Saloranta

unread,
Jan 30, 2023, 11:39:05 PM1/30/23
to jackso...@googlegroups.com
On Mon, Jan 30, 2023 at 11:53 AM itineric itineric <itin...@gmail.com> wrote:
>
> Hi all,
>
> I'm trying to work with interfaces and related bean implementation of that interface. So far, all good.
> I then tried to add some Map<String, Object> to the equation and I cannot understand why I get this result.
> Short story: I'm trying to get the @class to be set to the interface name using a TypeIdResolver and it work as expected on a direct call but not when the object is inside a Map.

Although I did not read the example in detail, I think you are bumping
into the same problem as countless other developers: Java Type
Erasure.

Meaning that passing root-level values that have generic type will NOT
have their type information available.

Try this by wrapping your `Map` into wrapper like:

public class MapWrapper {
public Map<String, Object> parameters;
}

and serialize that.

More specifically if you pass `Map` directly into ObjectMapper, it is
only seen as `Map<?.?>`
This is problematic because you need the base type of Map values to be
`UserInterface` I think.
So type must be seen as `Map<String, UserInterface>` or so.

If possible maybe you should try creating helper type, something like:

public class UserTypeMap extends HashMap<String, UseInterface> { }

since it would contain necessary type information (being concrete
type, not generic).

-+ 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 view this discussion on the web visit https://groups.google.com/d/msgid/jackson-user/afe8ba30-a9de-4a48-8c6e-2407115694den%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages