记录一个jackson使用@JsonTypeInfo实现多态反序列化问题

记录一个jackson使用@JsonTypeInfo实现多态反序列化问题:missing type id property '@Clazz'

异常信息

missing type id property ‘@Clazz’ (for POJO property ‘xxx’):

Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class com.clearofchina.uaa.model.SysUserDetail]: missing type id property '@Clazz' (for POJO property 'result')
 at [Source: (PushbackInputStream); line: 1, column: 388] (through reference chain: com.clearofchina.core.model.ResponseModel["result"])
	at com.fasterxml.jackson.databind.exc.InvalidTypeIdException.from(InvalidTypeIdException.java:43)
	at com.fasterxml.jackson.databind.DeserializationContext.missingTypeIdException(DeserializationContext.java:1771)
	at com.fasterxml.jackson.databind.DeserializationContext.handleMissingTypeId(DeserializationContext.java:1300)
	at com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase._handleMissingTypeId(TypeDeserializerBase.java:299)
	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedUsingDefaultImpl(AsPropertyTypeDeserializer.java:164)
	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:105)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithType(BeanDeserializerBase.java:1178)
	at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:138)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:369)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4218)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3267)
	at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:239)
	... 76 more

问题产生背景及模拟代码

在封装统一认证鉴权模块时,要给网关提供微服务调用接口,返回鉴权成功后的用户信息,用户分系统用户、渠道应用、机构用户等不同类型,最终统一封装成ResponseModel对象返回,由于不同的用户信息实现用户信息抽象出来的接口,所以用到多态。同时又有根据用户ID获取用户信息的微服务需要提供,也封装成ResponseModel对象返回。由于ResponseModel中泛型的原因,导致使用@JsonTypeInfo中的“@Class”属性丢失,产生了以上的异常。业务描述比较难懂,上代码。

  • 统一返回对象ResponseModel
package com.clearofchina.uaa.json;

import lombok.Data;

/**
 * 统一返回对象
 * 
 * @author Zhouych
 * @date 2020年5月21日 上午10:42:52
 * @since V1.0.0
 */
@Data
public class ResponseModel<T> {

	private int code;

	private String message;

	private T result;

}
  • 用户对象抽象IUserInfo,使用@JsonTypeInfo来实现反序列化时多态区分
package com.clearofchina.uaa.json;

/**
 * 用户信息抽象
 * 
 * @author Zhouych
 * @date 2020年5月21日 上午10:49:23
 * @since V1.0.0
 */
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,include = JsonTypeInfo.As.PROPERTY,property = "@Clazz")
public interface IUserInfo {

	/**
	 * 获取用户唯一主键
	 * 
	 * @return
	 */
	@JsonIgnore
	String getUserKey();
}
  • 系统用户信息SysUserInfo,实现IUserInfo 接口,继承了@JsonTypeInfo
package com.clearofchina.uaa.json;

import lombok.Data;

/**
 * 系统用户信息
 * 
 * @author   Zhouych
 * @date     2020年5月21日 上午10:52:15
 * @since    V1.0.0
 */
@Data
public class SysUserInfo implements IUserInfo {
	
	private Long userId;
	
	private String username;
	
	private int age;

	@Override
	public String getUserKey() {
		return String.valueOf(this.userId);
	}

}
  • 渠道应用信息:ChlAppInfo,实现IUserInfo 接口,继承了@JsonTypeInfo
package com.clearofchina.uaa.json;

import lombok.Data;

/**
 * 渠道应用信息
 * 
 * @author   Zhouych
 * @date     2020年5月21日 上午10:58:46
 * @since    V1.0.0
 */
@Data
public class ChlAppInfo implements IUserInfo {
	
	private String appKey;
	
	private String appSecret;
	
	private String name;

	@Override
	public String getUserKey() {
		return this.appKey;
	}

}
  • 鉴权成功返回信息 AuthSuccessInfo
/**
 * 鉴权成功返回信息
 * 
 * @author   Zhouych
 * @date     2020年5月21日 下午1:48:27
 * @since    V1.0.0
 */
@Data
public class AuthSuccessInfo {
	
	private String sign;
	
	private IUserInfo userInfo;

}
  • 产生问题模拟类Main
package com.clearofchina.uaa.json;

import java.util.Random;
import java.util.UUID;

import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * 
 * @author Zhouych
 * @date 2020年5月21日 上午11:00:58
 * @since V1.0.0
 */
public class Main {

	public static void main(String[] args) throws Exception {
		// 模拟服务消费端
		Main main = new Main();
		// 请求并解析数据(真实场景为通过openfeign调用api接口)
		String userInfoResponse = main.authService("xxxx");
		ObjectMapper objectMapper = new ObjectMapper();
		ResponseModel<AuthSuccessInfo> response = objectMapper.readValue(userInfoResponse,
				objectMapper.getTypeFactory().constructParametricType(ResponseModel.class, AuthSuccessInfo.class));
		System.out.println(userInfoResponse);
		System.out.println(response);
		//输出正确,没什么问题
		// {"code":200,"message":"SUCCESS","result":{"sign":"e40dfabb-1c4c-41d8-af0a-ec1af884a0c2","userInfo":{"@Clazz":"com.clearofchina.uaa.json.ChlAppInfo","appKey":"123654","appSecret":"888888","name":"微信公众号"}}}
		// ResponseModel(code=200, message=SUCCESS, result=AuthSuccessInfo(sign=e40dfabb-1c4c-41d8-af0a-ec1af884a0c2, userInfo=ChlAppInfo(appKey=123654, appSecret=888888, name=微信公众号)))

		
		// 问题出在另外有微服务要提供根据ID获取用户信息
		String sysUserInfoString = main.getSysUserInfoById(1L);
		System.out.println(sysUserInfoString);
		// {"code":200,"message":"SUCCESS","result":{"userId":1,"username":"zhangsan","age":18}}
		ResponseModel<SysUserInfo> sysUserInfo = objectMapper.readValue(userInfoResponse,
				objectMapper.getTypeFactory().constructParametricType(ResponseModel.class, SysUserInfo.class));
		System.out.println(sysUserInfo);
		// 产生异常
		/*
		 * Exception in thread "main"
		 * com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id
		 * when trying to resolve subtype of [simple type, class
		 * com.clearofchina.uaa.json.SysUserInfo]: missing type id property '@Clazz'
		 * (for POJO property 'result') at [Source:
		 * (String)"{"code":200,"message":"SUCCESS","result":{"sign":"233d310b-0b4f-4131
		 * -91ed-0989a5525546","userInfo":{"@Clazz":"com.clearofchina.uaa.json.
		 * ChlAppInfo","appKey":"123654","appSecret":"888888","name":"微信公众号"}}}"; line:
		 * 1, column: 203] (through reference chain:
		 * com.clearofchina.uaa.json.ResponseModel["result"]) at
		 * com.fasterxml.jackson.databind.exc.InvalidTypeIdException.from(
		 * InvalidTypeIdException.java:43) at
		 * com.fasterxml.jackson.databind.DeserializationContext.missingTypeIdException(
		 * DeserializationContext.java:1771) at
		 * com.fasterxml.jackson.databind.DeserializationContext.handleMissingTypeId(
		 * DeserializationContext.java:1300) at
		 * com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase.
		 * _handleMissingTypeId(TypeDeserializerBase.java:299) at
		 * com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.
		 * _deserializeTypedUsingDefaultImpl(AsPropertyTypeDeserializer.java:164) at
		 * com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.
		 * deserializeTypedFromObject(AsPropertyTypeDeserializer.java:105) at
		 * com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithType
		 * (BeanDeserializerBase.java:1178) at
		 * com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(
		 * MethodProperty.java:138) at
		 * com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(
		 * BeanDeserializer.java:288) at
		 * com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(
		 * BeanDeserializer.java:151) at
		 * com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.
		 * java:4218) at
		 * com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3214)
		 * at com.clearofchina.uaa.json.Main.main(Main.java:38)
		 */
	}

	/**
	 * 模拟服务提供方提供的鉴权服务:模拟http请求响应json数据
	 * 
	 * @param token
	 * @return
	 * @throws Exception
	 */
	public String authService(String token) throws Exception {
		Random random = new Random();
		ObjectMapper objectMapper = new ObjectMapper();
		AuthSuccessInfo result = new AuthSuccessInfo();
		if (random.nextBoolean()) {
			SysUserInfo userInfo = new SysUserInfo();
			userInfo.setAge(18);
			userInfo.setUsername("zhangsan");
			userInfo.setUserId(1L);
			result.setSign(UUID.randomUUID().toString());
			result.setUserInfo(userInfo);
		} else {
			ChlAppInfo userInfo = new ChlAppInfo();
			userInfo.setAppKey("123654");
			userInfo.setAppSecret("888888");
			userInfo.setName("微信公众号");
			result.setSign(UUID.randomUUID().toString());
			result.setUserInfo(userInfo);
		}
		ResponseModel<AuthSuccessInfo> response = new ResponseModel<>();
		response.setCode(200);
		response.setMessage("SUCCESS");
		response.setResult(result);
		return objectMapper.writeValueAsString(response);
	}
	
	/**
	 * 模拟服务提供方提供的查询用户信息服务:模拟http请求响应json数据
	 * 
	 * @param userId
	 * @return
	 * @throws Exception
	 */
	public String getSysUserInfoById(Long userId) throws Exception {
		SysUserInfo userInfo = new SysUserInfo();
		userInfo.setAge(18);
		userInfo.setUsername("zhangsan");
		userInfo.setUserId(userId);
		ObjectMapper objectMapper = new ObjectMapper();
		ResponseModel<SysUserInfo> response = new ResponseModel<>();
		response.setCode(200);
		response.setMessage("SUCCESS");
		response.setResult(userInfo);
		return objectMapper.writeValueAsString(response);
	}

}

解决方法

产生问题的原因是,由于反序列化多态的需要,添加了@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,include = JsonTypeInfo.As.PROPERTY,property = “@Clazz”)这个注解,但由于ResponseModel里T result是泛型,序列化SysUserInfo为json字符串时,“@Clazz”属性丢失,导致反序列化时取@Clazz属性时无法找到。

  1. 解决方法一:既然是序列化时少了“@Clazz”属性,那就添上
    修改的IUserInfo类
package com.clearofchina.uaa.json;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonProperty.Access;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

/**
 * 用户信息抽象
 * 
 * @author Zhouych
 * @date 2020年5月21日 上午10:49:23
 * @since V1.0.0
 */
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@Clazz")
public interface IUserInfo {

	/**
	 * 获取用户唯一主键
	 * 
	 * @return
	 */
	@JsonIgnore
	String getUserKey();

	/**
	 * 强行添加"@Clazz"属性
	 * 
	 * @return
	 */
	@JsonProperty(value = "@Clazz", access = Access.READ_ONLY)
	String getClazz();
}

对应的两个实现类SysUserInfo和ChlAppInfo实现该方法即可:

package com.clearofchina.uaa.json;

import lombok.Data;

/**
 * 渠道应用信息
 * 
 * @author   Zhouych
 * @date     2020年5月21日 上午10:58:46
 * @since    V1.0.0
 */
@Data
public class ChlAppInfo implements IUserInfo {
	
	private String appKey;
	
	private String appSecret;
	
	private String name;

	@Override
	public String getUserKey() {
		return this.appKey;
	}

	@Override
	public String getClazz() {
		return this.getClass().getName();
	}

}



package com.clearofchina.uaa.json;

import lombok.Data;

/**
 1. 系统用户信息
 2. 
 3. @author   Zhouych
 4. @date     2020年5月21日 上午10:52:15
 5. @since    V1.0.0
 */
@Data
public class SysUserInfo implements IUserInfo {
	
	private Long userId;
	
	private String username;
	
	private int age;

	@Override
	public String getUserKey() {
		return String.valueOf(this.userId);
	}

	@Override
	public String getClazz() {
		return this.getClass().getName();
	}
}

此次有个注意的点,就是添加“@Clazz”属性后,反序列化字符串中会把“@Clazz”带到反序列化器,所以要添加@JsonIgnoreProperties(ignoreUnknown = true)注解忽略掉位置属性。

  1. 解决方法二:在具体实现类使用@JsonTypeInfo覆盖掉接口类的@JsonTypeInfo,并添加默认实现属性,如下:
package com.clearofchina.uaa.json;

import com.fasterxml.jackson.annotation.JsonTypeInfo;

import lombok.Data;

/**
 * 系统用户信息
 * 
 * @author   Zhouych
 * @date     2020年5月21日 上午10:52:15
 * @since    V1.0.0
 */
@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, defaultImpl = SysUserInfo.class)
public class SysUserInfo implements IUserInfo {
	
	private Long userId;
	
	private String username;
	
	private int age;

	@Override
	public String getUserKey() {
		return String.valueOf(this.userId);
	}

//	@Override
//	public String getClazz() {
//		return this.getClass().getName();
//	}
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值