简体   繁体   中英

Json deserializer that works across different classes in java

I am trying to create an @JsonDeserializer that will work across classes. I am using JAX-RS and the incoming json string will have fields in snake case. I want to override the json deserialization so that my java objects do not have snake-case fields. Since the creation of the java object is happening within JAX-RS, I am using the @JsonDeserializer annotation on all my request classes. My current implementation has a generic base class, but I need to extend it for all the concrete classes so that I can pass in the actual class I want to create. Is there any way to do this more generically?

For example, I have multiple request objects like this:

    @JsonDeserialize(using = MyRequestDeserializer.class)
    public class MyRequest {
    ....
    }

I have created a generic deserializer like so:

public class GenericRequestDeserializer extends JsonDeserializer<Object> {

private static ObjectMapper objectMapper = new ObjectMapper();

@Override
public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
    return null;
}

protected Object deserializeIt(JsonParser jsonParser, Class cls) {
    try {
        JsonNode node = jsonParser.getCodec().readTree(jsonParser);
        Iterator<String> fieldNames = node.fieldNames();

        Object object = cls.newInstance();

        while(fieldNames.hasNext()) {
            String fieldName = fieldNames.next();
            JsonNode value = node.get(fieldName);

            String newFieldName = convertFieldName(fieldName);

            //TODO: currently failing if I do not find a field, should the exception be swallowed?
            Class returnType = object.getClass().getMethod("get" + newFieldName).getReturnType();
            Method setMethod = object.getClass().getMethod("set" + newFieldName, returnType);

            Object valueToSet = null;
            if(value.isTextual()) {
                valueToSet = value.asText();
            } else if(value.isContainerNode()) {
                valueToSet = objectMapper.readValue(value.toString(), returnType);
            } else if (value.isInt()) {
                valueToSet = value.asInt();
            }

            setMethod.invoke(object, valueToSet);
        }

        return object;

    } catch (Exception e) {
        throw new TokenizationException(GatewayConstants.STATUS_SYSTEM_ERROR,
                "Error in deserializeIt for " + cls.getSimpleName() + " caused by " + e.getMessage(), e);
    }
}

private String convertFieldName(String fieldName) {

    StringBuilder newFieldName = new StringBuilder();

    int length = fieldName.length();
    boolean capitalize = true;  //first character should be capitalized

    for(int i = 0; i < length; i++) {
        char current = fieldName.charAt(i);

        if(current == '_') {
            //remove the underscore and capitalize the next character in line
            capitalize = true;
        } else if(capitalize) {
            newFieldName.append(Character.toUpperCase(current));
            capitalize = false;
        } else {
            newFieldName.append(current);
        }
    }

    return newFieldName.toString();
}

}

But I still need to create a new class per Request in order to pass in the proper class to create:

public class MyRequestDeserializer extends GenericRequestDeserializer {
@Override
public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
    return deserializeIt(jsonParser, MyRequest.class);
}
}

Is there any way to get rid of all the MyRequestDeserializer classes? In other words, can the GenericRequestDeserializer figure out what class it is actually deserializing?

So I found a much better option for changing all my objects to snake case. Instead of using Serializers and Deserializers on each class, I was able to inject an ObjectMapper into the JsonProvider in Spring. ObjectMapper already supports a property that will do the camel-case to snake-case automagically. I just needed to overwrite the getSingletons method in my class that extends Application like so:

public class MyApp extends Application {
....
@Override
public Set<Object> getSingletons() {
    final Set<Object> objects = new LinkedHashSet<Object>();


    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setPropertyNamingStrategy(
            PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

    objects.add(new JacksonJsonProvider(objectMapper));

    return objects;
}
}

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