Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java support for discriminator attribute when reading objects #1253

Closed
cknowles opened this issue Sep 17, 2015 · 11 comments
Closed

Java support for discriminator attribute when reading objects #1253

cknowles opened this issue Sep 17, 2015 · 11 comments

Comments

@cknowles
Copy link

Currently there doesn't seem to be any support for the swagger v2 discriminator attribute when reading an entity. I think the expectation would be that we first read in the discriminator field, then read the rest of the object based on this field. Not sure where best to put this but have been playing around with some custom JsonDeserializers.

Representing the Test Steps from https://www.runscope.com/docs/api/steps, I have Step/RequestStep/ConditionStep/PauseStep. I found two options so far which are below. Both seem roughly equivalent in terms of complexity and code size.

Option 1

The addition of a custom deserializer:

public class StepDeserializer extends JsonDeserializer<Step> {

    public StepDeserializer() {
        super();
    }

    @Override
    public Step deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        JsonNode node = jp.getCodec().readTree(jp);
        String stepType = node.get("step_type").asText();
        StepTypeEnum stepTypeEnum = StepTypeEnum.valueOf(stepType);

        Class<? extends Step> valueType = null;

        switch (stepTypeEnum) {
            case condition:
                valueType = ConditionStep.class;
                break;
            case pause:
                valueType = PauseStep.class;
                break;
            case request:
                valueType = RequestStep.class;
                break;
        }

        if (valueType != null) {
            return mapper.treeToValue(node, valueType);
        } else {
            throw new JsonParseException("Enumeration value of " + stepType + " not recognised",
                    jp.getCurrentLocation());
        }
    }

}

Then this JsonDeserializer can be added to the generation in two ways I think.

Either to the static initialization in JsonUtil:

SimpleModule module = new SimpleModule();
module.addDeserializer(Step.class, new StepDeserializer());
mapper.registerModule(module);

Or to Step itself (this gives stack overflow, need to work out how to stop subclasses from using the same deserializer perhaps):

@ApiModel(description = "")
@JsonDeserialize(using = StepDeserializer.class)
public class Step  {

Option 2

These annotations on Step itself:

@JsonTypeInfo(use = Id.CUSTOM, property = "step_type", include = As.PROPERTY)
@JsonTypeIdResolver(StepTypeIdResolver.class)

And then this custom type resolver, can't see a way to do it with the built in resolvers:

public class StepTypeIdResolver implements TypeIdResolver {

    private JavaType mBaseType;

    @Override
    public void init(JavaType baseType) {
        mBaseType = baseType;
    }

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

    @Override
    public String idFromValueAndType(Object value, Class<?> suggestedType) {
        if (RequestStep.class.equals(suggestedType)) {
            return Step.StepTypeEnum.request.toString();
        } else if (ConditionStep.class.equals(suggestedType)) {
            return Step.StepTypeEnum.condition.toString();
        } else if (PauseStep.class.equals(suggestedType)) {
            return Step.StepTypeEnum.pause.toString();
        }
        return String.valueOf(value);
    }

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

    @Override
    public JavaType typeFromId(String id) {
        Class<? extends Step> clazz = null;
        if (StepTypeEnum.request.toString().equals(id)) {
            clazz = RequestStep.class;
        } else if (StepTypeEnum.condition.toString().equals(id)) {
            clazz = ConditionStep.class;
        } else if (StepTypeEnum.pause.toString().equals(id)) {
            clazz = PauseStep.class;
        }
        return TypeFactory.defaultInstance().constructSpecializedType(mBaseType, clazz);
    }

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

}
@cknowles
Copy link
Author

I started on some of the code for this, turns out the discriminator isn't supported at all right now in the codegen project.

@fabiankessler
Copy link

To my the generated code work, all I had to do is add these annotations on the base type:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({@JsonSubTypes.Type(value = MySubClassOne.class, name = "MySubClassOne")})

My discriminator field is called "type".

And of course the instance must then have the "type" field set to "MySubClassOne". Or else just hardcode that value in each subclass.

Yes, it would be very useful if codegen would support this advertised feature. It took me a few hours to find my way around the open issues (there are more).

@manuc66
Copy link
Contributor

manuc66 commented Feb 1, 2017

It seems that this pull request : #4607 will solve this with @JsonTypeInfo and @JsonSubTypes

@wing328
Copy link
Contributor

wing328 commented Feb 3, 2017

#4607 has been merged into master. Thanks for the contribution from @eamon316

Please pull the latest master to give it a try.

@cbornet
Copy link
Contributor

cbornet commented Feb 3, 2017

#4607 is for Spring. Has it also been done for Jackson based java clients ?

@wing328
Copy link
Contributor

wing328 commented Feb 3, 2017

@cbornet yes, you're right. I got confused :(

@jeff9finger
Copy link
Contributor

This looks very similar to #4085 and #4226

@cbornet
Copy link
Contributor

cbornet commented Feb 3, 2017

@jeff9finger yes, so java clients now also support it, it seems.

@jeff9finger
Copy link
Contributor

There is a vendor extension x-discriminator-value that can be used along with the Jackson annotations to specify the discriminator value to be used instead of the object name.

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({@JsonSubTypes.Type(value = MySubClassOne.class, name = "my-sub-class-one")})

definitions:
MySubClassOne:
x-discriminator-value: my-sub-class-one
...

@jeff9finger
Copy link
Contributor

It should work on any jaxrs models, server or client

@wing328
Copy link
Contributor

wing328 commented Feb 15, 2017

@c-knowles please pull the latest master to give it another try.

@wing328 wing328 closed this as completed Feb 15, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants