Disable conversion of scalars to strings when deserializing with Jackson

不羁岁月 提交于 2020-06-12 05:42:06

问题


I want to identify numerical values inserted without quotation marks (as strings) in JSON sent through the request body of a POST request:

For example, this would be the wrong JSON format as the age field does not contain quotation marks:

{
  "Student":{
    "Name": "John",
    "Age":  12
  }
}

The correct JSON format would be:

{
  "Student":{ 
    "Name": "John",
    "Age":  "12"
  }
}

In my code, I've defined the datatype of the age field as a String, hence "12" should be the correct input. However, no error message is thrown, even when 12 is used.

It seems Jackson automatically converts the numerical values into strings. How can I identify numerical values and return a message?

This is what I tried so far to identify these numerical values:

public List<Student> getMultiple(StudentDTO Student) {
    if(Student.getAge().getClass()==String.class) {
        System.out.println("Age entered correctly as String");
    } else{
        System.out.println("Please insert age value inside inverted commas");
    }
}

However, this is not printing "Please insert age value inside inverted commas" to the console when the age is inserted without quotation marks.


回答1:


By default, Jackson converts the scalar values to String when the target field is of String type. The idea is to create a custom deserializer for String type and comment out the conversion part:

package jackson.deserializer;

import java.io.IOException;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.std.StringDeserializer;


public class CustomStringDeserializer extends StringDeserializer 
{

    public final static CustomStringDeserializer instance = new CustomStringDeserializer();

    @Override
    public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        if (p.hasToken(JsonToken.VALUE_STRING)) {
            return p.getText();
        }
        JsonToken t = p.getCurrentToken();
        // [databind#381]
        if (t == JsonToken.START_ARRAY) {
            return _deserializeFromArray(p, ctxt);
        }
        // need to gracefully handle byte[] data, as base64
        if (t == JsonToken.VALUE_EMBEDDED_OBJECT) {
            Object ob = p.getEmbeddedObject();
            if (ob == null) {
                return null;
            }
            if (ob instanceof byte[]) {
                return ctxt.getBase64Variant().encode((byte[]) ob, false);
            }
            // otherwise, try conversion using toString()...
            return ob.toString();
        }
        // allow coercions for other scalar types
        // 17-Jan-2018, tatu: Related to [databind#1853] avoid FIELD_NAME by ensuring it's
        //   "real" scalar
        /*if (t.isScalarValue()) {
            String text = p.getValueAsString();
            if (text != null) {
                return text;
            }
        }*/
        return (String) ctxt.handleUnexpectedToken(_valueClass, p);
    }

}

Now register this deserializer:

@Bean
public Module customStringDeserializer() {
    SimpleModule module = new SimpleModule();
    module.addDeserializer(String.class, CustomStringDeserializer.instance);
    return module;
}

When an integer is send and String is expected, here is the error:

{"timestamp":"2019-04-24T15:15:58.968+0000","status":400,"error":"Bad Request","message":"JSON parse error: Cannot deserialize instance of java.lang.String out of VALUE_NUMBER_INT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of java.lang.String out of VALUE_NUMBER_INT token\n at [Source: (PushbackInputStream); line: 3, column: 13] (through reference chain: org.hello.model.Student[\"age\"])","path":"/hello/echo"}




回答2:


If you're using Spring boot, by default it uses Jackson to parse JSON. There's no configuration option within Jackson to disable this feature, as mentioned within this issue. The solution is to register a custom JsonDeserializer that throws an exception as soon as it encounters any other token than JsonToken.VALUE_STRING

public class StringOnlyDeserializer extends JsonDeserializer<String> {
    @Override
    public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        if (!JsonToken.VALUE_STRING.equals(jsonParser.getCurrentToken())) {
            throw deserializationContext.wrongTokenException(jsonParser, String.class, JsonToken.VALUE_STRING, "No type conversion is allowed, string expected");
        } else {
            return jsonParser.getValueAsString();
        }
    }
}

If you only want to apply this to certain classes or fields, you can annotate those with the @JsonDeserialize annotation. For example:

public class Student {
    private String name;
    @JsonDeserialize(using = StringOnlyDeserializer.class)
    private String age;

    // TODO: Getters + Setters
}

Alternatively, you can register a custom Jackson module by registering a SimpleModule bean that automatically deserializes all strings using the StringOnlyDeserializer. For example:

@Bean
public Module customModule() {
    SimpleModule customModule = new SimpleModule();
    customModule.addDeserializer(String.class, new StringOnlyDeserializer());
    return customModule;
}

This is similar to what Eugen suggested.

If you run your application now, and you pass an invalid age, such as 12, 12.3 or [12]it will throw an exception with a message like:

JSON parse error: Unexpected token (VALUE_NUMBER_FLOAT), expected VALUE_STRING: Not allowed to parse numbers to string; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (VALUE_NUMBER_FLOAT), expected VALUE_STRING: Not allowed to parse numbers to string\n at [Source: (PushbackInputStream); line: 3, column: 9] (through reference chain: com.example.xyz.Student[\"age\"])


来源:https://stackoverflow.com/questions/55832004/disable-conversion-of-scalars-to-strings-when-deserializing-with-jackson

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!