ObjectMapper deserializes int into String

1. Question

The test revealed a bug. When using the API interface for authentication, the incoming request parameter type was wrong, but the authentication was successful.

POST request, body parameters are as follows:

{
    
    
	"username": "test",
	"password": 111111
}

Test expectation: Parameter verification failed, prompting "password" only supports string type.
Actual: The authentication was successful.

The original processing logic is as follows:

public class Test {
    
    

    public static void main(String[] args) throws JsonProcessingException {
    
    
        ObjectMapper mapper = new ObjectMapper();
        Auth auth = mapper.readValue("{\"username\":\"test\",\"password\":111111}", Auth.class);
        System.out.println(auth);
    }

    public static class Auth {
    
    
        private String username;
        private String password;

        public Auth() {
    
    
        }

        public Auth(String username, String password) {
    
    
            this.username = username;
            this.password = password;
        }

        public String getUsername() {
    
    
            return username;
        }

        public void setUsername(String username) {
    
    
            this.username = username;
        }

        public String getPassword() {
    
    
            return password;
        }

        public void setPassword(String password) {
    
    
            this.password = password;
        }

        @Override
        public String toString() {
    
    
            return "Auth{" +
                    "username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    }
}

2. Analysis

Analysis:
Expected, Auth auth = mapper.readValue("{"username":"test","password":111111}", Auth.class); will throw a type cast exception during execution because the password actually passed in The value is of type int, but the password type of Auth is defined as String.
However, the result is not like this. If the deserialization is successful, it means that ObjectMapper has done special processing internally and automatically converted int into String during deserialization.

Analyze ObjectMapper source code,

@JacksonStdImpl
public class StringDeserializer extends StdScalarDeserializer<String> // non-final since 2.9
{
    
    
    .....
    
    @Override
    public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
    {
    
    
        if (p.hasToken(JsonToken.VALUE_STRING)) {
    
    
            return p.getText();
        }
        JsonToken t = p.currentToken();
        // [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();
        }
        // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
        if (t == JsonToken.START_OBJECT) {
    
    
            return ctxt.extractScalarFromObject(p, this, _valueClass);
        }
        // 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);
    }

    ......

As you can see, the key processing logic is here,

        if (t.isScalarValue()) {
    
    
            String text = p.getValueAsString();
            if (text != null) {
    
    
                return text;
            }
        }

When the target value is of type String, the scalar value will be converted to String for processing.
Therefore, you only need to modify this logic without conversion.

3. Solve

solution:

To customize StringDeserizlizer, just override the deserializer method.

public static void main(String[] args) throws JsonProcessingException {
    
    
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new SimpleModule().addDeserializer(String.class, new StringDeserializer() {
    
    
            @Override
            public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
    
    
                if (p.hasToken(JsonToken.VALUE_STRING)) {
    
    
                    return p.getText();
                }
                throw new IllegalArgumentException("Unsupported type," + p.currentName() + " only support String type");
            }
        }));
        Auth auth = mapper.readValue("{\"username\":\"test\",\"password\":111111}", Auth.class);
        System.out.println(auth);
    }

Guess you like

Origin blog.csdn.net/weixin_39651041/article/details/119702214