问题
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 ofjava.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