讲师中心 微信公众号
AI工具推荐 视频效率加速

Jackson反序列化:将嵌套JSON字符串解析为List对象

千宇同学_5078

千宇同学_5078

发布时间:2025-11-24 20:18:06

|

896人浏览过

|

来源于php中文网

原创

Jackson反序列化:将嵌套JSON字符串解析为List对象

当json数据中存在一个字段,其值本身是一个表示json数组的字符串时,jackson的默认反序列化机制无法直接将其解析为java的`list`对象。本文将深入探讨这一常见问题,并提供一种健壮的解决方案。我们将通过实现自定义的`jsondeserializer`和`contextualdeserializer`,动态获取目标类型信息,从而实现将嵌套的json字符串正确反序列化为指定类型的`list`集合。

问题场景:嵌套JSON字符串的处理

在处理复杂的JSON数据时,我们有时会遇到一种特殊情况:某个字段的值不是标准的JSON数组或对象,而是一个包含JSON数组或对象内容的“字符串”。例如,考虑以下JSON结构:

{
  "name": "g1",
  "users": "[{\"name\":\"u1\",\"id\":1},{\"name\":\"u2\",\"id\":2}]"
}

在这个JSON中,users字段的值是一个字符串,但该字符串内部实际上是一个标准的JSON数组,包含了两个用户对象。如果我们尝试将其反序列化为如下Java对象:

static class User {
    private String name;
    private Long id;

    // Getters and Setters
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
}

static class Group {
    private String name;
    private List<User> users;

    // Getters and Setters
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public List<User> getUsers() { return users; }
    public void setUsers(List<User> users) { this.users = users; }
}

并使用Jackson的ObjectMapper进行默认反序列化:

ObjectMapper objectMapper = new ObjectMapper();
String jsonString = "{\"name\":\"g1\",\"users\":\"[{\"name\":\"u1\",\"id\":1},{\"name\":\"u2\",\"id\":2}]\"}";
Group group = objectMapper.readValue(jsonString, Group.class);

此时,Jackson会尝试将users字段的整个字符串值作为单个字符串处理,而不是将其解析为List。这会导致反序列化失败或得到一个空列表(取决于Jackson的版本和配置),因为Jackson期望在users字段处直接看到一个JSON数组([),而不是一个字符串(")。

解决方案:自定义JsonDeserializer

为了正确处理这种嵌套的JSON字符串,我们需要编写一个自定义的Jackson反序列化器。这个反序列化器需要能够识别users字段是一个字符串,然后将这个字符串的内容作为独立的JSON数据再次解析。

JsonDeserializer与ContextualDeserializer

Jackson提供了JsonDeserializer接口,允许我们为特定类型T定义自定义的反序列化逻辑。然而,对于像List这样带有泛型参数的类型,仅仅实现JsonDeserializer>是不够的,因为在反序列化器初始化时,我们可能无法知道List内部的具体类型(例如,User)。

这时,ContextualDeserializer接口就派上了用场。通过实现ContextualDeserializer,我们的反序列化器可以在运行时获取到其被应用到的属性(BeanProperty)的上下文信息,包括其完整的泛型类型。这使得我们能够动态地确定需要解析的内部类型。

实现RawJsonDeserializer

以下是一个通用的RawJsonDeserializer实现,它能够处理将字符串形式的JSON内容反序列化为任何指定类型的对象,包括泛型列表:

java-api-doc-generator
java-api-doc-generator

分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。

下载
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.type.SimpleType;

import java.io.IOException;
import java.util.List;

public class RawJsonDeserializer<T> extends JsonDeserializer<T> implements ContextualDeserializer {

    private final JavaType type; // 存储目标类型

    // 默认构造器,用于Jackson初始化
    public RawJsonDeserializer() {
        // 初始类型设置为Object.class,会在createContextual中被更新
        this.type = SimpleType.constructUnsafe(Object.class);
    }

    // 带类型参数的构造器,由createContextual调用
    public RawJsonDeserializer(JavaType type) {
        this.type = type;
    }

    /**
     * createContextual方法是ContextualDeserializer的核心。
     * 它在反序列化器被应用到特定属性时调用,允许我们根据属性的上下文信息创建
     * 一个新的、具有特定类型参数的反序列化器实例。
     */
    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
        // property.getType() 获取当前属性的完整类型信息,例如 List<User>
        // 我们创建一个新的RawJsonDeserializer实例,并将此类型传递给它
        return new RawJsonDeserializer<>(property.getType());
    }

    /**
     * deserialize方法执行实际的反序列化逻辑。
     * 它接收当前JSON字段的JsonParser和DeserializationContext。
     */
    @Override
    public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        // p.getText() 获取当前JSON字段的原始字符串值
        // p.getCodec() 获取当前使用的ObjectMapper实例
        // 然后,我们使用这个ObjectMapper将获取到的字符串再次解析为之前在createContextual中确定的目标类型 (this.type)
        return ((ObjectMapper) p.getCodec()).readValue(p.getText(), type);
    }
}

代码解析:

  1. RawJsonDeserializer() 构造器: 这是一个无参构造器,Jackson在初步加载反序列化器时会调用它。此时我们不知道具体的泛型类型,所以暂时将type设置为Object.class。
  2. RawJsonDeserializer(JavaType type) 构造器: 这是一个带JavaType参数的构造器,用于在createContextual方法中创建新的反序列化器实例,并传入具体的类型信息。
  3. createContextual(DeserializationContext ctxt, BeanProperty property):
    • 这个方法是ContextualDeserializer接口的核心。当Jackson发现某个属性需要使用此自定义反序列化器时,它会调用此方法。
    • property.getType():这是关键所在!它返回当前被反序列化的Java属性的完整类型信息,例如,对于List users;,它将返回一个代表List的JavaType对象。
    • 我们利用这个JavaType创建一个新的RawJsonDeserializer实例,并将其返回。Jackson会使用这个新的实例来处理当前属性的反序列化。
  4. deserialize(JsonParser p, DeserializationContext ctxt):
    • 这是实际执行反序列化逻辑的方法。
    • p.getText():获取当前JSON token(在这里是users字段的字符串值)的文本内容,即"[{\"name\":\"u1\",\"id\":1},{\"name\":\"u2\",\"id\":2}]"。
    • ((ObjectMapper) p.getCodec()):从当前的JsonParser中获取正在使用的ObjectMapper实例。
    • readValue(p.getText(), type):使用获取到的ObjectMapper,将p.getText()(即那个嵌套的JSON字符串)再次解析,这次是解析成在createContextual中确定的type(例如List)。这样,Jackson就能正确地将内部字符串解析为List对象。

应用自定义反序列化器

将上述自定义反序列化器应用到我们的Group类中的users字段上,需要使用@JsonDeserialize注解:

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.util.List;

static class User {
    private String name;
    private Long id;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    @Override
    public String toString() {
        return "User{name='" + name + "', id=" + id + '}';
    }
}

static class Group {
    private String name;
    // 使用@JsonDeserialize注解指定自定义反序列化器
    @JsonDeserialize(using = RawJsonDeserializer.class)
    private List<User> users;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public List<User> getUsers() { return users; }
    public void setUsers(List<User> users) { this.users = users; }

    @Override
    public String toString() {
        return "Group{name='" + name + "', users=" + users + '}';
    }
}

现在,当Jackson反序列化Group对象时,遇到users字段,它会调用RawJsonDeserializer来处理。RawJsonDeserializer会先获取users字段的字符串值,然后将其重新解析为List。

完整示例代码:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.type.SimpleType;

import java.io.IOException;
import java.util.List;

public class JacksonNestedJsonStringToList {

    // ... (RawJsonDeserializer, User, Group classes as defined above) ...

    public static void main(String[] args) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        String jsonString = "{\"name\":\"g1\",\"users\":\"[{\"name\":\"u1\",\"id\":1},{\"name\":\"u2\",\"id\":2}]\"}";

        Group group = objectMapper.readValue(jsonString, Group.class);

        System.out.println("Deserialized Group: " + group);
        if (group != null && group.getUsers() != null) {
            System.out.println("Users list size: " + group.getUsers().size());
            group.getUsers().forEach(user -> System.out.println("  " + user));
        }
    }
}

运行上述代码,将得到以下输出:

Deserialized Group: Group{name='g1', users=[User{name='u1', id=1}, User{name='u2', id=2}]}
Users list size: 2
  User{name='u1', id=1}
  User{name='u2', id=2}

这表明嵌套的JSON字符串已成功反序列化为List对象。

注意事项与最佳实践

  1. 性能开销: 这种方法涉及两次JSON解析:第一次是Jackson解析外部JSON结构时将users字段作为字符串读取,第二次是自定义反序列化器将这个字符串再次解析为内部的List。这会带来一定的性能开销。如果性能是关键考量,并且你有能力修改原始JSON结构,最好将users字段直接修改为JSON数组而不是字符串。
  2. 适用场景: 此方法特别适用于你无法控制JSON源数据格式,但又必须处理这种“JSON字符串中嵌套JSON”的情况。例如,某些遗留系统或第三方API可能返回此类数据。
  3. 错误处理: deserialize方法内部的readValue可能会抛出IOException。在实际应用中,你可能需要添加更健壮的错误处理逻辑,例如捕获异常并转换为JsonMappingException,或者返回一个默认值。
  4. 通用性: RawJsonDeserializer设计得非常通用,它可以用于反序列化任何以字符串形式存在的JSON内容到任何Java类型,不仅仅是List。

总结

通过实现Jackson的JsonDeserializer和ContextualDeserializer接口,我们成功地解决了将嵌套在字符串中的JSON数组反序列化为Java List对象的问题。ContextualDeserializer的强大之处在于其能够在运行时动态获取属性的类型信息,从而使得自定义反序列化器能够适应各种泛型类型。虽然这种方法会引入双重解析的性能开销,但它为处理不规范的JSON数据提供了一个灵活且强大的解决方案。

热门AI工具

更多
火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

Lovart
Lovart Hot

一款面向视觉设计创作的AI设计平台,可通过智能体和画布工作流辅助制作海报、Logo、网页、PPT及其他视觉内容。

小橡皮AI

一款AI工具,主要用于小橡皮一键去AI味,将机器味重的内容改得更有活人感,防止因为 AI 内容被平台打上 AI 标签,以及限流,适合需要提升相关任务效率的用户。

UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

创客贴AI

创客贴AI是一款AI图像与设计工具,AI辅助的智能在线设计工具。

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

2195

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

3462

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

1156

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

3979

2025.09.10

登录token无效
登录token无效

登录token无效解决方法:1、检查token的有效期限,如果token已经过期,需要重新获取一个新的token;2、检查token的签名,如果签名不正确,需要重新获取一个新的token;3、检查密钥的正确性,如果密钥不正确,需要重新获取一个新的token;4、使用HTTPS协议传输token,建议使用HTTPS协议进行传输 ;5、使用双因素认证,双因素认证可以提高账户的安全性。

14326

2023.09.14

登录token无效怎么办
登录token无效怎么办

登录token无效的解决办法有检查Token是否过期、检查Token是否正确、检查Token是否被篡改、检查Token是否与用户匹配、清除缓存或Cookie、检查网络连接和服务器状态、重新登录或请求新的Token、联系技术支持或开发人员等。本专题为大家提供token相关的文章、下载、课程内容,供大家免费下载体验。

1685

2023.09.14

token怎么获取
token怎么获取

获取token值的方法:1、小程序调用“wx.login()”获取 临时登录凭证code,并回传到开发者服务器;2、开发者服务器以code换取,用户唯一标识openid和会话密钥“session_key”。想了解更详细的内容,可以阅读本专题下面的文章。

3957

2023.12.21

token什么意思
token什么意思

token是一种用于表示用户权限、记录交易信息、支付虚拟货币的数字货币。可以用来在特定的网络上进行交易,用来购买或出售特定的虚拟货币,也可以用来支付特定的服务费用。想了解更多token什么意思的相关内容可以访问本专题下面的文章。

6121

2024.03.01

Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

20

2026.10.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
dev.java 官方:Learn Java
dev.java 官方:Learn Java

共0课时 | 0人学习

Java JDBC数据库连接官方教程
Java JDBC数据库连接官方教程

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn