Java开发教程23-JSON数据处理

xianaldai 2026-08-25 23:44 1

本期是Java开发教程系列的第二十三期,本期介绍 JSON 数据处理:互联网上最通用的数据格式,也是第 22 期那个"一言"接口返回的东西.学完本期,任何接口返回的 JSON 都能变成 Java 对象.若你已经熟悉 Gson 或 Jackson 的基本用法,可以跳过本期内容.


天外音:想要的图在主贴最后了.


版权声明:图片部分来自于网络,如有侵权,可联系删除.


每期叠甲:

时间仓促,水平有限,文中难免存在缺点和错误,恳请广大开发者和从业者指正.




一、JSON 是什么:给数据"塑形"的文本


JSON 的写法,你在第 10 期就见过了:它长得很像 Java 的 Map:


{
"name": "小明",
"score": 92.0,
"passed": true,
"tags": ["勤奋", "细心"]
}

规则只有几条:



  • 花括号 {} 包着一个对象,里面是一串 "键": 值

  • 键必须是双引号字符串

  • 值可以是:字符串、数字、布尔、null、数组 []、另一个对象 {}

  • 数组 [] 和 Java 的 List 对应

    为什么全世界接口都用 JSON?三个理由:文本(任何人能看、任何语言能解析)、结构化(嵌套关系天然表达)、约定简单(就上面四条规则).第 11 期我们自己发明了 姓名,电话 的格式:那就是一个"自制 JSON",可惜只有自己的程序能懂.




二、引入 Gson:一个库搞定互转


Java 没有内置 JSON 处理(标准库只提供基础能力),业界最常用的是 Gson(Google 出品)和 Jackson.本期用 Gson,因为它最小最直白.


怎么引入? 如果你的项目是 Maven 或 Gradle 管理,加一行依赖(Gradle):


implementation 'com.google.code.gson:gson:2.11.0'

没配构建工具的话,去 Maven 中央仓库下载 gson-2.x.jar,放进项目的 lib 目录,在 IDEA 里右键 jar → “Add as Library”.后续教程默认你已经会用 Gradle/Maven 加依赖.


对象 → JSON(序列化,toJson):


import com.google.gson.Gson;

public class Player {
public String name;
public int level;
public double hp;
}

Player p = new Player();
p.name = "Steve";
p.level = 10;
p.hp = 20.5;

Gson gson = new Gson();
String json = gson.toJson(p);
System.out.println(json);
// {"name":"Steve","level":10,"hp":20.5}

JSON → 对象(反序列化,fromJson):


String json = "{\"name\":\"Steve\",\"level\":10,\"hp\":20.5}";
Player p = gson.fromJson(json, Player.class);
System.out.println(p.name + " 等级 " + p.level); // Steve 等级 10

注意:类要有无参构造器(Gson 反射创建对象时要用,第 24 期讲反射),字段名和 JSON 键名一一对应才转得进来;JSON 里没有的字段保持默认值.




三、List / Map 互转:TypeToken 登场


fromJson(json, Player.class) 能转单个对象,那转 List 呢?直觉是:


// 错误写法!编译能过(只会给个 unchecked 警告),但运行期转出来是 List<Object>,
// 取出元素强转成 Player 时抛 ClassCastException
List<Player> list = gson.fromJson(json, List.class);

问题在第 14 期的类型擦除:运行期 List<Player> 就是 List,JVM 根本不知道元素该转成 Player.而 Gson 需要"元素类型"才能正确转换.解法:Gson 提供 TypeToken 包装,把泛型类型保真传进去:


import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;

Type playerListType = new TypeToken<List<Player>>() { }.getType();
List<Player> list = gson.fromJson(json, playerListType);

new TypeToken<List<Player>>() { } 这个写法利用了"匿名子类可以携带泛型信息":你创建一个 TypeToken 的匿名子类,Java 的反射就能从父类的泛型参数里取出 List<Player>.这是一个固定的套路,背下来:



  • 单个对象:fromJson(json, Player.class)

  • 集合对象:new TypeToken<List<Player>>() { }.getType()

    Map 同理:


Type mapType = new TypeToken<Map<String, Object>>() { }.getType();
Map<String, Object> map = gson.fromJson(json, mapType);
System.out.println(map.get("name")); // Steve

从 Map 转回对象:gson.fromJson(gson.toJson(map), Player.class).先转 JSON 字符串,再转对象,Gson 里常见的"绕一下".




四、字段改名与忽略: @SerializedName


有时 JSON 的键名和 Java 字段名对不上(比如接口叫 player_name,你字段叫 playerName).要么字段跟着接口改名(难看),要么用 @SerializedName 指定:


import com.google.gson.annotations.SerializedName;

public class Player {
@SerializedName("player_name") // JSON 里叫这个,Java 里叫下边那个
public String playerName;

@SerializedName("lv")
public int level;
}

toJson 时也会用注解里的名字输出:双向生效.


想忽略某个字段,用 @Expose 配合 GsonBuilder.注意它分两个方向:serialize 管"输出",deserialize 管"读取",都设 false 才是真正双向忽略:


import com.google.gson.annotations.Expose;

public class Player {
public String name;
@Expose(serialize = false) // 输出 JSON 时跳过它;但反序列化时仍会读取
public String password;
}

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

(另外注意:excludeFieldsWithoutExposeAnnotation() 这个开关一开,没标注 @Expose 的字段默认全部不参与序列化.用这个特性前要把所有想保留的字段都标上 @Expose.)


上面例子里,password 只设了 serialize = false,效果是 toJson 不输出密码,但 fromJson 仍会读.适合"登录后从服务器响应读用户信息,但绝不把密码写进响应"的场景.要"读取也不读"(比如服务器端拒绝接收客户端传来的密码),就写 @Expose(serialize = false, deserialize = false).




五、小项目:通讯录导出导入


把第 11 期的通讯录,从"自制 姓名,电话 格式"升级成 JSON:带嵌套、带数组,还能直接拿给任何程序用.


文件 Contact.java:


public class Contact {
public String name;
public String phone;

public Contact() { } // Gson 需要无参构造器

public Contact(String name, String phone) {
this.name = name;
this.phone = phone;
}

@Override
public String toString() {
return name + " " + phone;
}
}

文件 Main.java:


import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.FileReader;
import java.io.FileWriter;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

public class Main {
public static void main(String[] args) throws Exception {
Gson gson = new Gson();

// 导出:List → JSON → 文件
List<Contact> contacts = new ArrayList<>();
contacts.add(new Contact("小明", "13800000000"));
contacts.add(new Contact("小红", "13900000000"));
String json = gson.toJson(contacts);
try (FileWriter writer = new FileWriter("contacts.json")) {
writer.write(json);
}
System.out.println("导出完成: " + json);

// 导入:文件 → JSON 字符串 → List
String loadedJson;
try (FileReader reader = new FileReader("contacts.json")) {
StringBuilder sb = new StringBuilder();
int ch;
while ((ch = reader.read()) != -1) {
sb.append((char) ch);
}
loadedJson = sb.toString();
}

Type listType = new TypeToken<List<Contact>>() { }.getType();
List<Contact> loaded = gson.fromJson(loadedJson, listType);
System.out.println("导入 " + loaded.size() + " 个联系人:");
for (Contact c : loaded) {
System.out.println(c);
}
}
}

打开 contacts.json 看看,和之前 姓名,电话 的纯文本对比:现在它是标准的 JSON 数组,任何语言都能解析.把 Contact 加一个字段(比如 email),JSON 里自动出现.这就是"序列化按类结构自动展开"的威力.




朝花夕拾:第 11 期的 split,今天退休了


第 11 期读通讯录用 line.split(","),当时就埋了个钩子:分隔符一旦出现在数据里(名字叫"小明,小刚"?)解析就崩;字段一多,split 的数组下标就眼花.今天 JSON 把这两个痛点都治了:结构由 {}/[] 表达,解析由 Gson 完成.我们只描述"数据长什么样",剩下的活交给库.这也是后面 Spring Boot里 Jackson 自动把请求体转成对象的雏形,还要专门讲 Jackson 的深入用法.




总结


本期学会了和"接口世界的通用语言"打交道:



  • JSON 语法:{} 对象、[] 数组、四种值类型,和 Map/List 一一对应

  • Gson 互转:toJson 出、fromJson 进;字段名一一对应,类要有无参构造器

  • 集合转换:类型擦除导致 List.class 不行,用 TypeToken<List<Player>>() { }.getType()

  • 改名/忽略:@SerializedName 双向改名,@Expose 跳过敏感字段

  • 通讯录 JSON 导出导入小项目


下一期我们学习反射与注解:今天 Gson 为什么能"看穿"你的类、把字段自动写进 JSON?秘密都在下一期.






最新回复 (3)
  • xianaldai 楼主 08-26 00:04
    1


    Java还是太权威了谁懂啊,直接写个国战插件(cleanroom别人的)

  • ryuki 08-26 00:07
    2

    强强,之前练手就搞了个调飞行速度的mod还搞了半天

  • phaseddd 08-26 00:15
    3

    哭了居然能在论坛看到Java佬 ^-^ ^-^

* 帖子来源Linux.do
返回