users@jsonb-spec.java.net

[jsonb-spec users] [jsr367-experts] Re: Re: Re: Java polymorphism support

From: Romain Manni-Bucau <rmannibucau_at_tomitribe.com>
Date: Wed, 23 Mar 2016 12:04:47 +0100

I don't get this part "Jsonb looks at animal field model and gets "toType"
from annotated adapter".

How do you create a Dog in your example instead of an Animal.

Keep in mind you don't know all types from jsonb point of view. The real
use case is more obvious on a collection - at least for me:

public static class Pojo {
    @JsonbTypeAdapter(AnimalAdapter.class)
    public Collection<Animal> animals; // Dog, Cow, Cat, Horse...
}



Romain Manni-Bucau
@rmannibucau
http://www.tomitribe.com
http://rmannibucau.wordpress.com
https://github.com/rmannibucau

2016-03-23 11:51 GMT+01:00 Roman Grigoriadi <roman.grigoriadi_at_oracle.com>:

> You don't instantiate X in adapter you already have it. Here is
> implementation of such adapter:
>
> class TypeAdapter<T> implements JsonbAdapter<T, TypeWrapper<T>> {
>
> @Override
> public TypeWrapper<T> adaptToJson(T obj) throws Exception {
> TypeWrapper<T> wrapper = new TypeWrapper<>();
> wrapper.setClassName(obj.getClass().getName());
> wrapper.setInstance(obj);
> return wrapper;
> }
>
> @Override
> public T adaptFromJson(TypeWrapper<T> obj) throws Exception {
> return obj.getInstance();
> }
> }
>
> class AnimalAdapter extends TypeAdapter<Animal> {}
>
> Than you have a pojo like this:
>
> public static class Pojo {
> @JsonbTypeAdapter(AnimalAdapter.class)
> public Animal animal;
> }
>
> And json like this:
>
> {
> "animal":{"className":"com.foo.Dog",name":"Doggie","dogProperty":"Property of a Dog."}}
> }
>
>
> What happens during deserialization in our implementation is: JsonParser
> generates an event for started object at "animal" property. Jsonb looks at
> animal field model and gets "toType" from annotated adapter.
> Deserialization than happens for TypedWrapper as for common arbitrary user
> Type, but with custom deserializer, reading class property first and
> creating instance from it. After TypedWrapper<Animal> is fully
> deserialized, it is passed to adapter instance, to retrieve an Animal,
> which is finally set to Animal field.
>
> Regards,
> Roman
>
>
> On 03/23/2016 11:20 AM, Romain Manni-Bucau wrote:
>
> Hi Roman,
>
> can you fill some "..." please? typically at the moment "I know the type
> is X". How with an adapter do you instantiate X and then let jsonb populate
> it (without having another Jsonb instance)? That was the main missing part
> of the puzzle.
>
>
> Romain Manni-Bucau
> @rmannibucau
> http://www.tomitribe.com
> http://rmannibucau.wordpress.com
> https://github.com/rmannibucau
>
> 2016-03-23 11:14 GMT+01:00 Roman Grigoriadi <roman.grigoriadi_at_oracle.com>:
>
>> Hi,
>>
>> @Romain - what you and Dmitry suggested with adapters, will actully work
>> in our case. We will have to to add a custom deserializer handler for a "
>> TypedWrapper<T>" (similar to collection / array or reflection
>> deserializers), which will read a class property first and on
>> encountering a json object for T will create a correct type instance. The
>> only condition would be, that class property must appear before inner
>> instance in json object for "TypedWrapper<T>" as Eugen suggested:
>>
>> {"classProperty":"com.foo.Dog","instance":{...}}
>>
>> Adapters will than work in a common way to extract T from TypedWrapper
>> and set it to a class model be it a field or a collection/array.
>>
>> I would also add required enumeration of allowed subtypes into
>> TypedAdapter:
>>
>> class TypedAdapter<T> implements JsonbAdapter<T, TypedWrapper<T>> {
>> TypedAdapter(Class[] allowedSubtypes) {...}
>> ...
>> }
>>
>> So we don't allow loading of any arbitrary class suggested by incoming
>> json in case of not trusted third party.
>>
>> @Romain - adding support for custom serializers / deserializers in
>> fashion of fasterxml with access to JsonGenerator/JsonParser would also
>> solve the problem in similar fashion, but it would be more complicated for
>> a client to implement logic driven by JsonParser events, than by providing
>> predefined adapter.
>>
>> Thanks,
>> Roman
>>
>> On 03/21/2016 09:36 PM, Romain Manni-Bucau wrote:
>>
>> Hi Dmitry,
>>
>> was my thought as well but it doesn't really work cause in adapter you
>> can't relaunch the deserialization chain with the same jsonb instance. The
>> serialization side is the easy one but the deserialization makes adapters
>> API not enough to have a consistent behavior accross the deserialization.
>> Making the adapter "JsonbAware" would work. We can probably use @Inject
>> (CDI if there or just the JSR 330 as an optional dependency if not)
>>
>> Side note: I'd like we enable this impl but we don't provide them in the
>> spec since it is likely dependent on your use case and saw cases where type
>> was not enough and we will likely not handle all possible models.
>>
>>
>>
>> Romain Manni-Bucau
>> @rmannibucau
>> http://www.tomitribe.com
>> http://rmannibucau.wordpress.com
>> https://github.com/rmannibucau
>>
>> 2016-03-21 21:27 GMT+01:00 Dmitry Kornilov < <dmitry.kornilov_at_oracle.com>
>> dmitry.kornilov_at_oracle.com>:
>>
>>> Hi,
>>>
>>> This topic has been discussed already. Current version of the spec is
>>> clear about polymorphism: *“**Deserialization into polymorphic types is
>>> not supported by default mapping. [JSB-3.8-1]”. *The spec is almost
>>> finished and I am strongly against going back and changing some basic
>>> things now.
>>>
>>> On the other hand I understand that this use case exists. There is a
>>> possible workaround to it using adapters. Sorry if there are any mistakes,
>>> I am writing code without checking.
>>>
>>> We can create a generic wrapper to any type holding its type info like
>>> this:
>>>
>>> class TypedWrapper<T> {
>>> String type;
>>> T obj;
>>> }
>>>
>>> We can create a generic adapter which converts type to wrapper and vice
>>> versa. I am not putting a full implementation here, I hope that the idea is
>>> clear:
>>>
>>> class TypedAdapter implements JsonbAdapter<T, TypedWrapper<T>> {
>>> TypedWrapper adaptToJson(T obj) {
>>> TypedWrapper<T> t = new TypedWrapper<T>();
>>> t.type = obj.getClass().getName();
>>> t.obj = obj;
>>> return t;
>>> }
>>>
>>> T adaptFromJson(TypedWrapper<T> t) {
>>> // Use reflection API to create an instance of object based on
>>> t.type field value
>>> }
>>> }
>>>
>>> Now lets imagine that we have the following classes:
>>>
>>> class Animal {
>>> }
>>>
>>> class Dog extends Animal {
>>> }
>>>
>>> class Cat extends Animal {
>>> }
>>>
>>> class Foo {
>>> Animal animal;
>>> }
>>>
>>> To properly handle serialization/deserialization of Foo we just need to
>>> create an adapter which extends TypedAdapter from above and annotate
>>> *animal* field:
>>>
>>> class AnimalAdapter extends TypedAdapter<Animal, TypedWrapper<Animal>> {
>>> }
>>>
>>> class Foo {
>>> @JsonbTypeAdapter(AnimalAdapter.class)
>>> Animal animal;
>>> }
>>>
>>> We can include TypedWrapper and TypedAdapter in reference implementation.
>>>
>>> Thanks,
>>> Dmitry
>>>
>>>
>>>
>>> On 21 Mar 2016, at 19:31, Eugen Cepoi < <cepoi.eugen_at_gmail.com>
>>> cepoi.eugen_at_gmail.com> wrote:
>>>
>>> Not sure if it should or not be part of jsonb. Though it is very likely
>>> people will want that feature. On the other side, it is a good way to have
>>> different impls distinguish them self by providing such advanced features
>>> (I mean as part of the impl and not at all present in the spec). In that
>>> case I would find it fine to have people configure directly the impl api.
>>>
>>> When we think polymorphism, at first it seems enough to handle it only
>>> for json objects, but it can also happen for other types, esp. ones that
>>> are serialized as literals. Then where do you store the type? Same for json
>>> arrays. This is just one aspect of the introduced complexity when one wants
>>> to handle polymorphism. The produced json starts getting more and more ugly.
>>>
>>> Then comes the moment when you want to change the name of the serialized
>>> property or the format you use.
>>>
>>> In some other situations (depending on how it has been implemented) you
>>> might want to have access to a dom structure that holds the properties so
>>> you can extract from them what is related to the type and then "convert"
>>> that structure to the target type. This happens if you allow the type
>>> property to appear in any order.
>>>
>>> There are many other things that one could want for polymorphic support
>>> which would make all that even more complex.
>>>
>>>
>>> In genson I first handled it only for what gets serialized as a json
>>> object and imposed that the type property must appear first in the json (so
>>> we don't need to read the full structure before knowing the target type).
>>> Then recently I added support for all types (literal and array) by wrapping
>>> them in a json object (yeah not very nice, but has the merit of being
>>> relatively simple).
>>>
>>>
>>>
>>> 2016-03-21 2:22 GMT-07:00 Sebastian Daschner <
>>> <java_at_sebastian-daschner.de>java_at_sebastian-daschner.de>:
>>>
>>>> Hi guys,
>>>>
>>>> The idea was that this "javaType" (name is subject to change) wouldn't
>>>> be included in all cases. Default behaviour should be to serialize as small
>>>> / easy as possible. Only if the type information is needed to determine the
>>>> actual type, an @JsonbTypeInfo annotation could be added to the
>>>> corresponding type. Configuring a "forceTypeInfo" boolean for the
>>>> programmatic JSONB config could be possible though.
>>>>
>>>> @Przemyslaw:
>>>> Even if you solely use DTOs for serialization (I won't agree that
>>>> that's a must for all situations) you can still have situations with
>>>> subclasses and more complex hierarchies -- we had that case in several
>>>> projects, also when using transfer objects to map the domain objects.
>>>> "Flattening" the type hierarchies as workaround is not always desired.
>>>>
>>>> But the longer I think about your approach that enums could be used to
>>>> derive the type information (like JPA, yes) the more I like this idea as it
>>>> would be more generic (more difficult / more effort to configure, but
>>>> easier to apply to different technologies). We should investigate further
>>>> (with some examples) what approach could be the best fit for JSONB.
>>>>
>>>> But I highly recommend that some functionality to determine polymorphic
>>>> types should be included in the spec.
>>>>
>>>>
>>>> @Romain:
>>>> The spec should in fact require the impl to check for "suitable" types
>>>> then, i.e. only types to which the actual property type can be assigned to
>>>> should be allowed, etc.
>>>>
>>>> I like your idea to add a "Wrapper" to unwrap instances but let JSONB
>>>> still do the (de-)serialization. This could be helpful for more control how
>>>> the containing type (the List in my example) should be instanciated. But it
>>>> doesn't fully solve the polymorphism example in all cases, as the
>>>> implementation of the actual type (Customer or VipCustomer) can still not
>>>> determined from the nested JSON objects if there is no such thing as a JSON
>>>> meta type information.
>>>>
>>>>
>>>> Cheers,
>>>> Sebastian
>>>> On 03/21/2016 08:32 AM, Przemyslaw Bielicki wrote:
>>>>
>>>> Hi,
>>>>
>>>> I'm not a fan of the original idea (and it's an euphemism). Remember
>>>> that JSONB is used to serialize/deserialize DTOs, not domain objects. You
>>>> should have an additional adapter layer to convert DTOs into application's
>>>> business model/domain objects and back.
>>>> Polymorphism is broken (yeah, it's another topic and more philosophic)
>>>> and I would discourage its application in DTO layer. If you have to, you
>>>> can use an enum to mark what's the target class (check JPA for additional
>>>> inspirations). "//javaType": "x.y.z" is really bad idea IMO - what if you
>>>> use the same JSON to communicate with applications developed in .net,
>>>> python, php, c++ at the same time? (yes, it happens). Would you define a
>>>> type for each platform?
>>>>
>>>> If you use DTOs in business layer, you're doing something wrong :)
>>>>
>>>> @Romain, your idea looks interesting, can you show and example or
>>>> deserialization process where polymorphism is involved?
>>>>
>>>> Cheers,
>>>> Przemyslaw
>>>>
>>>> On Sun, Mar 20, 2016 at 10:52 PM, Romain Manni-Bucau <
>>>> <rmannibucau_at_tomitribe.com>rmannibucau_at_tomitribe.com> wrote:
>>>>
>>>>> Hi guys
>>>>>
>>>>> Why not allowing adapters to wrap and unwrap instances but keep
>>>>> serializer mecanism. For serialization no issue but for deserialization we
>>>>> need to switch the deserialized type and then post process the instance -
>>>>> or do it automatically with an Unwrappable interface?
>>>>>
>>>>> Overall idea for this particular use case would be:
>>>>>
>>>>> X ----TypedWrapper(X)----> JSON
>>>>> JSON----TypedWrapper.class----> TypedWrapped ----unwrap----> X
>>>>>
>>>>> Parts in arrows are adapter/jsonb integration.
>>>>>
>>>>> An alternative is to provide to adapters the stream and jsonp
>>>>> instances to do it themself with probably a mapper reference to
>>>>> re(de)serialise an object directly.
>>>>>
>>>>> Wdyt?
>>>>> ---------- Message transféré ----------
>>>>> De : "Romain Manni-Bucau" < <rmannibucau_at_tomitribe.com>
>>>>> rmannibucau_at_tomitribe.com>
>>>>> Date : 19 mars 2016 21:49
>>>>> Objet : Re: [jsonb-spec users] Java polymorphism support
>>>>> À : < <users_at_jsonb-spec.java.net>users_at_jsonb-spec.java.net>
>>>>> Cc :
>>>>>
>>>>> Hi Sebastian
>>>>>
>>>>> I am not fan of that - Mark knows ;) - and in particular now I'm sure
>>>>> we shouldnt do it: the 0-day vulnerability is still there and you open the
>>>>> door to the same issue or a complicated config adding this feature in
>>>>> something as generic as jsonb.
>>>>> Le 19 mars 2016 21:28, "Sebastian Daschner" <
>>>>> <java_at_sebastian-daschner.de>java_at_sebastian-daschner.de> a écrit :
>>>>>
>>>>>> Hi experts,
>>>>>>
>>>>>> I don't know whether this has been discussed in the mailing list
>>>>>> before
>>>>>> but a needed functionality would be to specify the Java type of
>>>>>> properties in the serialized JSON.
>>>>>>
>>>>>> As JSON doesn't standardize comments or other "attributes" (like XML
>>>>>> does) a "magic property" could be added.
>>>>>>
>>>>>> Please see the proposal (explored by Mark Struberg, Reinhard Sandtner
>>>>>> and myself) on my blog:
>>>>>>
>>>>>> <https://blog.sebastian-daschner.com/entries/json_mapping_polymorphism_support>
>>>>>> https://blog.sebastian-daschner.com/entries/json_mapping_polymorphism_support
>>>>>>
>>>>>> WDYT?
>>>>>>
>>>>>>
>>>>>> Cheers,
>>>>>> Sebastian
>>>>>>
>>>>>>
>>>>
>>>>
>>>
>>>
>>
>>
>
>