eclipse-ee4j / yasson

Eclipse Yasson project
https://projects.eclipse.org/projects/ee4j.yasson
Other
204 stars 96 forks source link

Yasson requires its own/internal JSON-P implementation to work ... ??? #102

Open amoscatelli opened 6 years ago

amoscatelli commented 6 years ago

I am implementing polymorphism mechanism with JSON-B, similar to Jackson one. So I need to scan for a "@type" attribute and then deserialize again with the correct class.

public class IdentifiableSerializer implements JsonbDeserializer<FindCriteriaDTO> {

    private final Reflections reflections;

    public IdentifiableSerializer(Reflections reflections) {
        this.reflections = reflections;
    }

    @Override
    public FindCriteriaDTO deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) {

        JsonObject preview = ctx.deserialize(JsonObject.class, parser);

        if (preview.keySet().contains("@type")){
            Optional<Class<? extends FindCriteriaDTO>> type = reflections.getSubTypesOf(FindCriteriaDTO.class).stream().filter(e -> StringUtils.equals(preview.getString("@type"), e.getSimpleName())).findFirst();

            if (type.isPresent()){

                StringWriter stringWriter = new StringWriter();
                Json.createWriter(stringWriter).writeObject(preview);
                JsonParser newParser = Json.createParser(new StringReader(stringWriter.toString()));

                FindCriteriaDTO deserialize = ctx.deserialize(type.get(), newParser);

                return deserialize;
            }

        }

        return ctx.deserialize(FindCriteriaDTO.class, parser);

    }

If I create a new JsonParser calling the JSON-P method a different implementation is returned (and this should not be an issue! json-b and json-p are different specifications!). But then I see this exception :

org.glassfish.json.JsonParserImpl cannot be cast to org.eclipse.yasson.internal.JsonbParser

This casting is the reason (org.eclipse.yasson.internal.serializer.AbstractContainerDeserializer:58) :

@Override
    public final T deserialize(JsonParser parser, DeserializationContext context, Type rtType) {
        Unmarshaller ctx = (Unmarshaller) context;
        ctx.setCurrent(this);
        deserializeInternal((JsonbParser) parser, ctx);
        ctx.setCurrent(getWrapper());
        return getInstance((Unmarshaller) context);
    }

I don't think this is the proper behavior. Am I wrong ?

m0mus commented 6 years ago

@bravehorsie please help

bravehorsie commented 6 years ago

You should not create it. It is passed in the deserialize method parameter. JsonbParser decorates to Jsonp, doing its business around it.

amoscatelli commented 6 years ago

I NEED to create it. And the spec says the deserializer context should accept a standard JsonParser, not a org.eclipse.yasson.internal.JsonbParser. This is what interfaces stand for.

This is clearly out of specification.

bravehorsie commented 6 years ago

Specification refers to jsonp api, not the RI. JsonParser is what you get in (de)serializer to drive it yourself. To deserialize java object from json you need to create Jsonb runtime instance, and call jsonb.deserialize(json, FindCriteriaDTO.class). You have both in your deserializer implementation code.

amoscatelli commented 6 years ago

Yea and you are not using the JSONP api, but your specific implementation.

JSONP API: javax.json.stream.JsonParser Provides forward, read-only access to JSON data in a streaming way.

JSONB API : javax.json.bind.serializer.DeserializationContext Provides JSONB Mapper functionality on top of JSONP parser

T deserialize(Class clazz, javax.json.stream.JsonParser parser); DeserializationContext deserialize method should work with ANY JsonParser jsonp implementation. Not only RI (org.glassfish.json.JsonParserImpl), not only others (org.eclipse.yasson.internal.JsonbParser). It should work independently of what implementation the API Json.createParser() could return. Regarding the solution/workaround you proposed I do NOT have the reference the jsonb inside a JsonbDeserializer. I could inject it in some way, but I do not think that's the clean way.
amoscatelli commented 6 years ago

Of course creating a NEW Jsonb Object inside the Deserializer should not be a solution also, since it would not be configurated like the original one.

bravehorsie commented 6 years ago

DeserializationContext deserialize method should work with ANY JsonParser jsonp implementation. Not only RI (org.glassfish.json.JsonParserImpl), not only others (org.eclipse.yasson.internal.JsonbParser). It should work independently of what implementation the API Json.createParser() could return.

So it does. You can provide any JSONP implementation to Yasson to work with it. What you do is you substitute running JSONP parser which has a cursor at some point of the document with new parser instance reading new document but you call current runtime on it, that is wrong. If you want to read a new document you should create new runtime with your parser instance.

alessandro-tucci-visiontech commented 3 years ago

So it does. You can provide any JSONP implementation to Yasson to work with it.

@bravehorsie I am not getting how a custom JSONP implementation can be actually provided to Yasson. From what I can see from here: https://github.com/eclipse-ee4j/yasson/blob/f8263f765368af3609e55da568f596a95483cbea/src/main/java/org/eclipse/yasson/internal/serializer/AbstractContainerDeserializer.java#L64 there is an explicit cast to org.eclipse.yasson.internal.JsonbParser of the provided JsonParser parameter. So, from what I see, I would say there is no way to avoid using a parser based on the Yasson-specific interface (JsonbParser), currently. Am I wrong?