简体   繁体   中英

How to write a java custom jackson deserializer to parse json field that could be an empty string or a string array?

I need to manage json structures accpeted/returned by REST APIs, using java classes auto-generated by JAXB using XSDs files provided by the REST service provider. For some reason I don't know but I must accept, in latest version of XSDs files the ENUMs have been converted in complex-type objects that have a "value" String property, and related JSON structures manage those cases using null, empty String or a String array containing one of the accepted values defined into xsd.

So, now I have the following xsd snippet

<xsd:complexType name='SexType'>
    <xsd:sequence>
        <xsd:element name='value' minOccurs='1' maxOccurs='1'>
            <xsd:simpleType>
                <xsd:restriction base='xsd:string'>
                    <xsd:pattern value='(01|02|98){1}'/>
                </xsd:restriction>
            </xsd:simpleType>
        </xsd:element>
    </xsd:sequence>
</xsd:complexType>
<xsd:complexType name='YesNoFixedYesType'>
    <xsd:sequence>
        <xsd:element name='value' minOccurs='1' maxOccurs='1' fixed='1'>
            <xsd:simpleType>
                <xsd:restriction base='xsd:string'>
                    <xsd:pattern value='(1|0){1}'/>
                </xsd:restriction>
            </xsd:simpleType>
        </xsd:element>
    </xsd:sequence>
</xsd:complexType>
...
<xsd:element name = 'sex' minOccurs='1' maxOccurs='1' type='SexType'>
<xsd:element name = 'decision' minOccurs='1' maxOccurs='1' type='YesNoFixedYesType'>

the following JAXB generated classes

Root Class

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "RootClass", propOrder = {
    "decision",
    "name",
    "sex"
})
public class RootClass {
    @XmlElement(name = "decision")
    protected YesNoFixedYesType decision;
    @XmlElement(name = "name")
    protected String name;
    @XmlElement(name = "sex")
    protected SexType sex;
    [other properties ]

    [getter and setter]
}

(ex)ENUMs classes

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "YesNoFixedYesType", propOrder = {
    "value"
})
@JsonDeserialize(using=GenericJsonDeserializer.class)
public class YesNoFixedYesType {

    @XmlElement(required = true)
    protected String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "SexType", propOrder = {
    "value"
})
@JsonDeserialize(using=GenericJsonDeserializer.class)
public class SexType {

    @XmlElement(required = true)
    protected String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

}

And the JSON I have to manage sounds like this

{
    "decision": {
        "value": [
            "1"
        ]
    },
    "name": "test",
    "sex": "",
    [other properties]
}

As you can see, JSON structure uses a String array for the "decision" field, and a simple empty String for "sex" field.

Considering that

  • I can't modify XSDs files
  • I have more than 400 XSDs files that are converted in java classes by JAXB

I'm trying to use

  • a generic Custom Deserializer valid for all (ex)ENUMs classes (and after I will need also a Custom Serializer)
  • "JsonDeserializer" annotation added to "SexType" and "YesNoFixedYesType" classes (in this first stage I manually added that annotation to classes, but I understood that, using jaxb-binding, they should be added by xjc during classes-generation process).

And this is my Custom Deserializer (please, note that during my tests I tried to extend both StdDeserializer and JsonDeserializer, even if Jackson specs say to use only StdDeserializer)

package it.ngede.impl.customizations.jackson.legapp;

import .....

public class GenericJsonDeserializer extends StdDeserializer<Object> implements ContextualDeserializer {
    private Class<?> targetClass;

    public GenericJsonDeserializer() {
        super(Object.class);
    }

    public GenericJsonDeserializer(Class<?> targetClass) {
        super(targetClass);
        this.targetClass = targetClass;
    }

    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        String sourceValue = "";
        try {
            //How can I put the right value into "sourceValue" variable?

            deserializedObject = targetClass.newInstance();
            Method setterMethod = deserializedObject.getClass().getMethod("setValue", String.class);
            setterMethod.invoke(deserializedObject, sourceValue);
        } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException e) {
            e.printStackTrace();
        }
        //Now I have an instance of the annotated class I can populate the fields via reflection
        return deserializedObject;
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
            BeanProperty property) throws JsonMappingException {
        //gets the class type of the annotated class
        targetClass = ctxt.getContextualType().getRawClass();
        //this new JsonApiDeserializer will be cached
        return new GenericJsonDeserializer(targetClass);
    }
}

Now, my question is: how can I extract the right value from the json? I executed many tests using all solutions I found over the internet but none of them worked. Is there someone could help me or point me where I can fine what I need?

Thanks in advance

Just few minutes after I posted my question (and after I waited 3 days to write here), I found a solution and I think it could be helpful who will be in my same trouble.

Here the code of my custom deserializer

@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
    Object deserializedObject = "";
    String sourceValue = "";

    try {
        if (p.getCurrentToken() == JsonToken.START_OBJECT) {
            p.nextToken();
            if (p.getCurrentToken() == JsonToken.FIELD_NAME && "value".equalsIgnoreCase(p.getCurrentName())) {
                System.out.print(p.getCurrentName());
                p.nextToken();
                if (p.getCurrentToken() == JsonToken.START_ARRAY) {
                    boolean stillLoop = true;
                    while(stillLoop) {
                        switch (p.getCurrentToken()) {
                            case END_ARRAY:
                                stillLoop = false;
                                break;
                            case VALUE_STRING:
                                sourceValue = p.getValueAsString();
                                System.out.println(" = " + sourceValue);
                                break;
                            default:
                        }
                        p.nextToken();
                    }
                }
            }
        }

        deserializedObject = targetClass.newInstance();
        Method getterMethod = deserializedObject.getClass().getMethod("getValue");
        if (java.util.List.class.isAssignableFrom(getterMethod.getReturnType())) {
            List<String> list = (List<String>) getterMethod.invoke(deserializedObject);
            list.add(sourceValue);
        } else {
            Method setterMethod = deserializedObject.getClass().getMethod("setValue", String.class);
            setterMethod.invoke(deserializedObject, sourceValue);
        }
    } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException e) {
        e.printStackTrace();
    }
    //Now I have an instance of the annotated class I can populate the fields via reflection
    return deserializedObject;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM