解决数据交互时,java默认转小驼峰问题及List数据类型接收单个bean

316 阅读4分钟

背景:

在写调用ups第三方接口时,发现其接口文档大部分字段命名遵循大驼峰命名,不符合java常用的小驼峰命名,而在java中,通常会使用一个JavaBean去填充字段,再将该bean放入HttpEntity中,作为后续请求时的泛型参数body,后续RestTemplate在执行execute方法发送http请求,以及接收数据时(如果使用泛型作为接收类型则会转小驼峰数据位空,若是用String接收无问题,我也是采用这种方法,然后手动用ObjectMapper转javaBean)都会转为小驼峰,导致数据异常。 使用的json序列化是jackson相关包

分析:

  1. RestTemplate发送请求体会转成小驼峰命名 默认情况下,Jackson 只使用 public 的字段进行序列化和反序列化。没有 public 字段时,会使用 public 的 getter/setter方法。可以通过 @JsonAutoDetect 自定义这种行为,指定字段、方法的可见性规则。 (通常javaBean的属性字段用private修饰,导致通过getter/setter读取时,无法读取到大驼峰命名的字段)

@JsonAutoDetect

自动检测,(作用在类上)来开启/禁止自动检测。

fieldVisibility:字段的可见级别   

ANY:任何级别的字段都可以自动识别  

NONE:所有字段都不可以自动识别   

NON_PRIVATE:非private)修饰的字段可以自动识别  

PROTECTED_AND_PUBLIC:被protected和public修饰的字段可以被自动识别   

PUBLIC_ONLY:只有被public修饰的字段才可以被自动识别     

DEFAULT:

jackson默认的字段属性发现规则如下:

所有被public修饰的字段->所有被public修饰的getter->所有被public修饰的setter)

@Data
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public static class Student {
    private String Age;
}

@Data
public static class Teacher {
    private String Age;
}



Snipaste_2024-03-14_17-11-16.png 可以采用注解方式同时解决发送&接收或者 使用将javaBean转为 hutool包的new JSONObject(Object source),然后toString()同样不会产生转小驼峰问题

  1. RestTemplate接收数据采用javaBean作为类型&List类型字段接收单个bean以及List数据 RestTemplate在处理response数据时,调用extraData(response)方法处理数据,会遍历所有的HttpMessageConverter<?>并调用read(Class<? extends T> clazz,HttpInputMessage message)方法,将流反序列化成指定的泛型类型。对于json数据类型而言,默认是调用AbstractJacksong2HttpMessageConverter类。其原理同样是调用该类下的ObjectMapper.readValue()方法,此时: 1.该mapper会先调用_addFields读取所有字段(能够读取到private,但是后续是不可见的),默认private字段为不可见invisible,public字段为可见visible,并将其放入_properties中。对应的key为其字段名(不会区分命名,若为A则key为A,注意此时字段是不可见的,后续会在调用_removeUnwantedProperties 因为不可见被移除);

image.png

image.png

然后会调用_addMethods,该方法为命名敏感方法,会读取bean反序列化bean的所有方法,若为getter/setter方法,则会读取其名称,此时会截取方法名中的get/set,保留后面的字符串,若遵循小驼峰命名,则此时getter/setter方法的key,会和属性名保持一致,并放入_properties中,填充到该key的getter/setter方法,且为visible(public修饰的getter/setter);若不遵循小驼峰,则会一直读取,直到读取到第一个小写字母后,才将后续字符串和之前的拼接返回作为key(期间读取到的都转小写,例如set/getABCddeE)会被读取为abcddeE。

因此readValue时,set方法无法正确set值

注:_properties为 public LinkedHashMap<String, POJOPropertyBuilder> _properties;

/**
 * Feature that may be enabled to enforce strict compatibility with
 * Bean name introspection, instead of slightly different mechanism
 * Jackson defaults to.
 * Specific difference is that Jackson always lower cases leading upper-case
 * letters, so "getURL()" becomes "url" property; whereas standard Bean
 * naming <b>only</b> lower-cases the first letter if it is NOT followed by
 * another upper-case letter (so "getURL()" would result in "URL" property).
 *<p>
 * Feature is disabled by default for backwards compatibility purposes: earlier
 * Jackson versions used Jackson's own mechanism.
 *
 * @since 2.5
 */
USE_STD_BEAN_NAMING(false),

而开启List支持接收单bean,有多种方法,例如重写HttpMessageConverter并将其加入RestTemplate中(此时可以直接使用JavaBean对象作为接收对象),简单一点的方法是直接用String接收,然后使用OBjectMapper.readValue(String content, Class valueType),并需要开启ObjectMapper的特性:

mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY,true);