使用 Jackson 反序列化时禁用标量到字符串的转换

Posted

技术标签:

【中文标题】使用 Jackson 反序列化时禁用标量到字符串的转换【英文标题】:Disable conversion of scalars to strings when deserializing with Jackson 【发布时间】:2019-09-13 20:47:08 【问题描述】:

我想识别通过 POST 请求的请求正文发送的 JSON 中不带引号插入的数值(作为字符串):

例如,这将是错误的 JSON 格式,因为年龄字段不包含引号:


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

正确的 JSON 格式是:


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

在我的代码中,我将age 字段的数据类型定义为String,因此"12" 应该是正确的输入。但是,即使使用 12,也不会引发错误消息。

Jackson 似乎自动将数值转换为字符串。如何识别数值并返回消息?

这是我迄今为止尝试识别这些数值的方法:

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");
    

但是,当年龄不带引号插入时,这不会将"Please insert age value inside inverted commas" 打印到控制台。

【问题讨论】:

提示:查看 JSON 库,例如 Google 的 GSON。 你用的是什么JSON解析库? 这只是一个通过java处理的JSON post请求 反逗号;通常称为引号 【参考方案1】:

如果你使用 Spring boot,默认情况下它使用 Jackson 来解析 JSON。正如this issue 中所述,Jackson 中没有禁用此功能的配置选项。解决方案是注册一个自定义的JsonDeserializer,一旦遇到除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();
        
    

如果您只想将其应用于某些类或字段,您可以使用@JsonDeserialize 注释对其进行注释。例如:

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

    // TODO: Getters + Setters

或者,您可以通过注册 SimpleModule bean 来注册自定义 Jackson 模块,该 bean 使用 StringOnlyDeserializer 自动反序列化所有字符串。例如:

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

这类似于what Eugen suggested。

如果您现在运行您的应用程序,并且您传递了无效的年龄,例如 1212.3[12],它将引发异常并显示如下消息:

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\"])

【讨论】:

【参考方案2】:

默认情况下,当目标字段为String 类型时,Jackson 会将标量值转换为字符串。这个想法是为String类型创建一个自定义反序列化器并注释掉转换部分:

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);
    


现在注册这个反序列化器:

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

当一个整数被发送并且需要字符串时,这里是错误:

"timestamp":"2019-04-24T15:15:58.968+0000","status":400,"error":"Bad Request","message":"JSON 解析错误:无法反序列化 java.lang.String 超出 VALUE_NUMBER_INT 令牌;嵌套异常是 com.fasterxml.jackson.databind.exc.MismatchedInputException:不能 从 VALUE_NUMBER_INT 中反序列化 java.lang.String 的实例 令牌\n 在 [Source: (PushbackInputStream);行:3,列:13] (通过参考链: org.hello.model.Student[\"age\"])","path":"/hello/echo"

【讨论】:

以上是关于使用 Jackson 反序列化时禁用标量到字符串的转换的主要内容,如果未能解决你的问题,请参考以下文章

使用 Jackson 进行 DateTime 反序列化的默认时区(Joda-Time 模块)

使用 lombok @Builder 时通过 Jackson 反序列化“2021-09-24 00:00:00”日期格式

反序列化 null 值以使用 Jackson 枚举

jackson使用setDateFormat后反序列化错误

spring/jackson:实现对保存JSON字符串的字段自动序列化和反序列化

Jackson - 使用泛型类反序列化