You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 14:57:08