Can Jackson work with dynamic proxies?

233 views
Skip to first unread message

Lyubomyr Shaydariv

unread,
Aug 15, 2018, 6:43:01 AM8/15/18
to jackson-user
Hello,

I've faced with an issue where I need to work with dynamic proxies since I find them a very convenient way allowing to reduce the amount of code dramatically. Please consider the following code:

interface IUserDto {

@JsonProperty(value = "name", access = JsonProperty.Access.READ_ONLY)
String getName();

void setName(String name);

interface IUserCreateDto
extends IUserDto {

@JsonProperty(value = "name", access = JsonProperty.Access.WRITE_ONLY, required = true)
void setName(String name);

}

static void main(final String... args)
throws IOException {
final ClassLoader classLoader = IUserDto.class.getClassLoader();
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new SimpleModule()
.addDeserializer(IUserCreateDto.class, new JsonDeserializer<IUserCreateDto>() {
@Override
public IUserCreateDto deserialize(final JsonParser parser, final DeserializationContext context) {
return (IUserCreateDto) Proxy.newProxyInstance(classLoader, new Class[]{ IUserCreateDto.class }, (proxy, method, args) -> {
throw new AssertionError("This never happens");
});
}
})
);
final IUserCreateDto userCreateDto = objectMapper.readValue("{\"name\":\"John\"}", IUserCreateDto.class);
System.out.println(userCreateDto.getClass());
}

}


For some reason, the invocation handler defined for the proxy is never invoked. Hence, the last System.out.println() simply prints `class com.sun.proxy.$Proxy2`, but I would expect the `readValue` method to fail because of the intentional assertion error specified in the invocation handler. My question is: what am I doing wrong and if it's possible to make Jackson work with dynamic proxies?

Here are my Jackson dependencies:

* com.fasterxml.jackson.core:jackson-core:2.6.3
* com.fasterxml.jackson.core:jackson-databind:2.6.3

Thank you!

Tatu Saloranta

unread,
Aug 15, 2018, 6:58:14 AM8/15/18
to jackson-user
As usual, I would suggest trying out:

1. The last path version of minor version you have: 2.6.7; if same behavior
2. Try one of later maintained versions: either 2.8.11 or .29.6

to verify issue still exists, as 2.6.3 is bit old version and there
have been more fixes.

Proxy types are not supported by default for deserialization, but
custom deserializers should work I think.
If later versions do not work (which seem likely), I would file an
issue at github against `jackson-databind`.

-+ Tatu +-

Lyubomyr Shaydariv

unread,
Aug 15, 2018, 8:05:56 AM8/15/18
to jackson-user
Thanks for the reply, Tatu.

Unfortunately, 2.6.7 works exactly the same like 2.6.3 does. However, if I switch to 2.9.6 (and I hope I'll migrate the project I work at too), the output is slightly different: `class com.sun.proxy.$Proxy0`. The assertion error is never thrown though.

Tatu Saloranta

unread,
Aug 15, 2018, 9:45:20 AM8/15/18
to jackson-user
On Wed, Aug 15, 2018 at 5:05 AM, Lyubomyr Shaydariv
<lyubomyr....@gmail.com> wrote:
> Thanks for the reply, Tatu.
>
> Unfortunately, 2.6.7 works exactly the same like 2.6.3 does. However, if I
> switch to 2.9.6 (and I hope I'll migrate the project I work at too), the
> output is slightly different: `class com.sun.proxy.$Proxy0`. The assertion
> error is never thrown though.

Hmmh. Looking at that, although I am not familiar with usage of Proxy
instances, it seems to me that
behavior here makes sense -- nothing in code is triggering any of
accessors so no exception is thrown.
Usage of Proxy here does not actually matter, as databind is not
really (nor needs to be) aware of its use.

Shouldn't code try to call `getName()` to trigger exception or something?
All Jackson does is invoke `deserialize()` method, which invokes
creation of proxied instance. But since
custom deserializes are expected to handle all actual deserialization
logic, including reading of JSON value
and setting values of Java object created, there is no other action.

I am guessing you might be expecting that only instance was created,
and then Jackson would handle content,
call setters, in which case exception would be expected. This is
possible using `ValueInstantiator` handler, although
bit more work. But plain `JsonDeserializer` delegates all work from databind.

I hope this makes 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.

Lyubomyr Shaydariv

unread,
Aug 15, 2018, 10:08:56 AM8/15/18
to jackson-user
Oh, I realized it... Yes, you are totally right and I missed that the whole point that the purpose of JsonDeserializer is returning an already-deserialized object. But for some reason I mixed it up with what should create an instance (i.e. `InstanceCreator` in Gson terms that I'm more familiar with). For example, the following code works and fails as expected:

final ClassLoader classLoader = IUserDto.class.getClassLoader();
final ObjectMapper objectMapper = new ObjectMapper();
final IUserDto.IUserCreateDto userCreateDto = (IUserDto.IUserCreateDto) Proxy.newProxyInstance(classLoader, new Class[]{ IUserDto.IUserCreateDto.class }, (proxy, method, argz) -> {
throw new AssertionError("This never happens");
});
objectMapper.readerForUpdating(userCreateDto).readValue("{\"name\":\"John\"}");
System.out.println(userCreateDto.getClass());

I must learn more about what ValueInstantiator is. This sounds exactly what I'm looking for. Apologies for the wasted time.

Tatu Saloranta

unread,
Aug 15, 2018, 10:29:22 AM8/15/18
to jackson-user
On Wed, Aug 15, 2018 at 7:08 AM, Lyubomyr Shaydariv
<lyubomyr....@gmail.com> wrote:
> Oh, I realized it... Yes, you are totally right and I missed that the whole
> point that the purpose of JsonDeserializer is returning an
> already-deserialized object. But for some reason I mixed it up with what
> should create an instance (i.e. `InstanceCreator` in Gson terms that I'm

Right, instance creation handling is bit simpler and more intuitive in Gson.
With Jackson addition was more along lines of being able to handle
some exotic types
than as general-purpose addition. But maybe handling could be improved
for Jackson 3,
to make cases like this easier to support.

> more familiar with). For example, the following code works and fails as
> expected:
>
> final ClassLoader classLoader = IUserDto.class.getClassLoader();
> final ObjectMapper objectMapper = new ObjectMapper();
> final IUserDto.IUserCreateDto userCreateDto = (IUserDto.IUserCreateDto)
> Proxy.newProxyInstance(classLoader, new Class[]{
> IUserDto.IUserCreateDto.class }, (proxy, method, argz) -> {
> throw new AssertionError("This never happens");
> });
> objectMapper.readerForUpdating(userCreateDto).readValue("{\"name\":\"John\"}");
> System.out.println(userCreateDto.getClass());
>
> I must learn more about what ValueInstantiator is. This sounds exactly what
> I'm looking for. Apologies for the wasted time.

No problem, easy mistake to make. I just got side-tracked by the
reference to Proxy as I know
there are explicit checks wrt Proxy instance types. :)
Reply all
Reply to author
Forward
0 new messages