如何使用RestTemplate实现JSON响应映射中外层类的泛化?
泛化处理WebService返回的统一结构JSON
我正在开发一系列WebService客户端,发现这些服务返回的JSON结构具有相似性,示例如下:
{ "typex": { "property1" : "value"}, "page": 1, "count": 200, "next_page": 2 }
共有近15个此类WebService,它们的返回结构一致:核心信息位于名为typex这类不同名称的对象中,外层包含page、count、next_page等字段。
目前我需要为每个服务创建至少两个类:一个外层包装类和一个内层信息类,示例代码如下:
public class TypeXWrapper { @JsonProperty TypeX typex; @JsonProperty Integer page; @JsonProperty Integer count; @JsonProperty Integer next_page; } public class TypeX { @JsonProperty String property1; }
这样总共需要创建30个类。
有没有办法对此类场景实现泛化处理?
我曾设想如下实现方式:
public class GenericWrapper<InnerClass> { @JsonProperty InnerClass data; @JsonProperty Integer page; @JsonProperty Integer count; @JsonProperty Integer next_page; } public class TypeX { @JsonProperty String property1; }
但问题在于,内层类对应的JSON根属性名会随WebService端点变化,比如TypeY对应的属性名为typey,示例如下:
{ "typey": { "property2" : "value"}, "page": 1, "count": 200, "next_page": 2 }
我的开发环境为Spring Boot 2.1.18(可按需升级),使用resttemplate并通过ParameterizedTypeReference封装返回对象。
解决方案
方案1:@JsonAlias+泛型包装类(适用于属性名有限的场景)
如果内层JSON属性名是固定的几个集合,可以直接给泛型包装类的data字段添加多个别名,让Jackson自动匹配:
public class GenericWrapper<T> { @JsonAlias({"typex", "typey", "typez"}) // 枚举所有可能的内层属性名 @JsonProperty T data; @JsonProperty Integer page; @JsonProperty Integer count; @JsonProperty("next_page") Integer nextPage; // 省略getter/setter }
使用时保持原有方式:
ParameterizedTypeReference<GenericWrapper<TypeX>> typeRef = new ParameterizedTypeReference<>() {}; GenericWrapper<TypeX> response = restTemplate.exchange(url, HttpMethod.GET, null, typeRef).getBody();
缺点:后续新增属性名时需要修改包装类,扩展性一般。
方案2:自定义注解+Jackson反序列化器(动态适配属性名)
这个方案通过自定义注解标记内层类对应的JSON属性名,配合反序列化器动态解析,扩展性强。
步骤1:定义自定义注解
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface InnerJsonProperty { String value(); // 指定对应的JSON属性名 }
步骤2:编写泛型包装类
@JsonDeserialize(using = GenericWrapperDeserializer.class) public class GenericWrapper<T> { T data; Integer page; Integer count; @JsonProperty("next_page") Integer nextPage; // 省略getter/setter }
步骤3:实现自定义反序列化器
public class GenericWrapperDeserializer extends StdDeserializer<GenericWrapper<?>> { public GenericWrapperDeserializer() { super(GenericWrapper.class); } @Override public GenericWrapper<?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { ObjectNode root = p.readValueAsTree(); GenericWrapper<?> wrapper = new GenericWrapper<>(); // 解析固定外层字段 wrapper.setPage(root.get("page").asInt()); wrapper.setCount(root.get("count").asInt()); wrapper.setNextPage(root.get("next_page").asInt()); // 获取泛型实际类型,读取注解中的属性名 JavaType wrapperType = ctxt.getContextualType(); JavaType innerType = wrapperType.getBindings().getBoundType(0); Class<?> innerClass = innerType.getRawClass(); InnerJsonProperty annotation = innerClass.getAnnotation(InnerJsonProperty.class); if (annotation == null) { throw new IllegalArgumentException(innerClass.getName() + "必须添加@InnerJsonProperty注解"); } String innerFieldName = annotation.value(); // 解析内层数据 JsonNode innerNode = root.get(innerFieldName); if (innerNode != null) { Object innerData = ctxt.readValue(innerNode.traverse(), innerType); wrapper.setData(innerData); } return wrapper; } }
步骤4:给内层类添加注解
@InnerJsonProperty("typex") public class TypeX { @JsonProperty String property1; // 省略getter/setter } @InnerJsonProperty("typey") public class TypeY { @JsonProperty String property2; // 省略getter/setter }
步骤5:使用方式不变
ParameterizedTypeReference<GenericWrapper<TypeX>> typeRef = new ParameterizedTypeReference<>() {}; GenericWrapper<TypeX> response = restTemplate.exchange(url, HttpMethod.GET, null, typeRef).getBody();
方案3:扩展RestTemplate消息转换器(无侵入式适配)
如果不想修改实体类,可以在消息转换器层面动态替换JSON属性名,把动态字段名映射为包装类的data字段。
步骤1:自定义Jackson消息转换器
public class DynamicPropertyNameConverter extends MappingJackson2HttpMessageConverter { private final Map<Class<?>, String> innerClassToFieldMap; public DynamicPropertyNameConverter(Map<Class<?>, String> innerClassToFieldMap) { this.innerClassToFieldMap = innerClassToFieldMap; ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new DynamicPropertyModule(innerClassToFieldMap)); setObjectMapper(objectMapper); } private static class DynamicPropertyModule extends SimpleModule { private final Map<Class<?>, String> innerClassToFieldMap; public DynamicPropertyModule(Map<Class<?>, String> innerClassToFieldMap) { this.innerClassToFieldMap = innerClassToFieldMap; } @Override public void setupModule(SetupContext context) { super.setupModule(context); context.addBeanDeserializerModifier(new BeanDeserializerModifier() { @Override public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) { if (GenericWrapper.class.isAssignableFrom(beanDesc.getBeanClass())) { return new GenericWrapperDynamicDeserializer((JsonDeserializer<Object>) deserializer, innerClassToFieldMap); } return deserializer; } }); } } private static class GenericWrapperDynamicDeserializer extends DelegatingDeserializer { private final Map<Class<?>, String> innerClassToFieldMap; public GenericWrapperDynamicDeserializer(JsonDeserializer<Object> delegate, Map<Class<?>, String> innerClassToFieldMap) { super(delegate); this.innerClassToFieldMap = innerClassToFieldMap; } @Override protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegate) { return new GenericWrapperDynamicDeserializer((JsonDeserializer<Object>) newDelegate, innerClassToFieldMap); } @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { ObjectNode root = p.readValueAsTree(); // 获取泛型类型,匹配对应的属性名 JavaType wrapperType = ctxt.getContextualType(); JavaType innerType = wrapperType.getBindings().getBoundType(0); Class<?> innerClass = innerType.getRawClass(); String fieldName = innerClassToFieldMap.get(innerClass); if (fieldName == null) { throw new IllegalArgumentException("未配置" + innerClass.getName() + "对应的JSON属性名"); } // 替换属性名为固定的"data" JsonNode innerNode = root.get(fieldName); root.remove(fieldName); root.set("data", innerNode); // 继续反序列化 return super.deserialize(root.traverse(), ctxt); } } }
步骤2:配置RestTemplate
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); // 配置内层类与JSON属性名的映射 Map<Class<?>, String> fieldMap = new HashMap<>(); fieldMap.put(TypeX.class, "typex"); fieldMap.put(TypeY.class, "typey"); // 新增服务时只需添加此处映射 DynamicPropertyNameConverter converter = new DynamicPropertyNameConverter(fieldMap); // 替换默认的Jackson转换器 restTemplate.getMessageConverters().removeIf(c -> c instanceof MappingJackson2HttpMessageConverter); restTemplate.getMessageConverters().add(converter); return restTemplate; } }
步骤3:使用泛型包装类
public class GenericWrapper<T> { @JsonProperty T data; @JsonProperty Integer page; @JsonProperty Integer count; @JsonProperty("next_page") Integer nextPage; // 省略getter/setter }
使用方式和原有逻辑完全一致,无需修改实体类。
内容的提问来源于stack exchange,提问作者fsilva
相关产品推荐
相关产品推荐

