如何基于queryname属性用Jackson XML反序列化接口/抽象类
解决Jackson XML根据queryname动态反序列化不同Row类型的问题
你遇到的核心问题是:Jackson无法实例化抽象的IRow接口,需要根据queryname属性的值,动态将<row>元素反序列化为Person或User类。以下是两种可行的实现方案:
方案一:自定义Query类反序列化器(直观可控)
这种方式直接在Query的反序列化过程中读取queryname,再决定row的具体类型。
1. 定义基础接口与实体类
public interface IRow {} @JacksonXmlRootElement(localName = "row") public class Person implements IRow { @JacksonXmlProperty(localName = "name") private String name; @JacksonXmlProperty(localName = "age") private int age; // 必须提供无参构造、getter/setter public Person() {} // getter、setter方法省略 } @JacksonXmlRootElement(localName = "row") public class User implements IRow { @JacksonXmlProperty(localName = "username") private String username; @JacksonXmlProperty(localName = "email") private String email; // 必须提供无参构造、getter/setter public User() {} // getter、setter方法省略 }
2. 定义Query类并指定自定义反序列化器
@JsonDeserialize(using = QueryDeserializer.class) public class Query { @JacksonXmlProperty(isAttribute = true) private String queryname; private List<IRow> rows; // getter、setter、无参构造 public Query() {} // getter、setter方法省略 }
3. 实现自定义反序列化器
public class QueryDeserializer extends StdDeserializer<Query> { protected QueryDeserializer() { super(Query.class); } @Override public Query deserialize(XmlParser p, DeserializationContext ctxt) throws IOException { // 读取整个XML节点树 JsonNode node = p.getCodec().readTree(p); String queryname = node.get("@queryname").asText(); Query query = new Query(); query.setQueryname(queryname); // 根据queryname选择row的具体类型 if ("persons".equals(queryname)) { JavaType rowType = ctxt.getTypeFactory().constructCollectionType(List.class, Person.class); List<Person> rows = ctxt.readValue(node.get("row").traverse(p.getCodec()), rowType); query.setRows(new ArrayList<>(rows)); } else if ("users".equals(queryname)) { JavaType rowType = ctxt.getTypeFactory().constructCollectionType(List.class, User.class); List<User> rows = ctxt.readValue(node.get("row").traverse(p.getCodec()), rowType); query.setRows(new ArrayList<>(rows)); } else { throw new IllegalArgumentException("未知queryname类型:" + queryname); } return query; } }
方案二:利用Jackson类型信息注解(更优雅)
通过@JsonTypeInfo和自定义类型解析器,让Jackson根据父节点的queryname自动匹配row类型。
1. 修改IRow接口添加类型注解
// 指定使用外部属性(queryname)作为类型标识 @JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "queryname") @JsonTypeIdResolver(RowTypeResolver.class) public interface IRow {}
2. 实现自定义类型解析器
public class RowTypeResolver extends TypeIdResolverBase { private JavaType baseType; @Override public void init(JavaType baseType) { this.baseType = baseType; } // 序列化时根据实例类型返回对应的queryname @Override public String idFromValue(Object value) { if (value instanceof Person) return "persons"; if (value instanceof User) return "users"; return null; } @Override public String idFromValueAndType(Object value, Class<?> suggestedType) { return idFromValue(value); } // 反序列化时根据queryname返回对应的Java类型 @Override public JavaType typeFromId(DatabindContext context, String id) { if ("persons".equals(id)) { return context.getTypeFactory().constructSpecializedType(baseType, Person.class); } else if ("users".equals(id)) { return context.getTypeFactory().constructSpecializedType(baseType, User.class); } return baseType; } @Override public JsonTypeInfo.Id getMechanism() { return JsonTypeInfo.Id.CUSTOM; } }
3. 定义Query类(无需额外反序列化器)
注意添加@JacksonXmlElementWrapper(useWrapping = false),避免Jackson自动给row列表添加外层包裹标签:
public class Query { @JacksonXmlProperty(isAttribute = true) private String queryname; @JacksonXmlProperty(localName = "row") @JacksonXmlElementWrapper(useWrapping = false) private List<IRow> rows; // 无参构造、getter/setter public Query() {} // getter、setter方法省略 }
测试代码
public class Main { public static void main(String[] args) throws IOException { XmlMapper xmlMapper = new XmlMapper(); // 测试persons XML String personsXml = "<query queryname=\"persons\">\n" + " <row>\n" + " <name>Alice</name>\n" + " <age>30</age>\n" + " </row>\n" + " <row>\n" + " <name>Bob</name>\n" + " <age>25</age>\n" + " </row>\n" + "</query>"; Query personsQuery = xmlMapper.readValue(personsXml, Query.class); System.out.println("Query类型:" + personsQuery.getQueryname()); personsQuery.getRows().forEach(row -> { Person p = (Person) row; System.out.println("Person:" + p.getName() + ",年龄:" + p.getAge()); }); // 测试users XML String usersXml = "<query queryname=\"users\">\n" + " <row>\n" + " <username>john_doe</username>\n" + " <email>john@example.com</email>\n" + " </row>\n" + " <row>\n" + " <username>jane_smith</username>\n" + " <email>jane@example.com</email>\n" + " </row>\n" + "</query>"; Query usersQuery = xmlMapper.readValue(usersXml, Query.class); System.out.println("\nQuery类型:" + usersQuery.getQueryname()); usersQuery.getRows().forEach(row -> { User u = (User) row; System.out.println("User:" + u.getUsername() + ",邮箱:" + u.getEmail()); }); } }
注意事项
- 所有实体类必须提供无参构造方法,Jackson反序列化依赖此构造方法实例化对象。
- 如果XML标签名与实体类字段名不一致,需用
@JacksonXmlProperty指定映射关系。 - 两种方案均兼容Jackson-dataformat-xml 2.13.3版本,无需额外依赖。
内容的提问来源于stack exchange,提问作者Charles
相关产品推荐
相关产品推荐

