接口返回的 JSON 数组在 Java 里通常会被解析成 JSONArray,例如 [{"id":1,"name":"Tom"},{"id":2,"name":"Jerry"}]。如果直接遍历并强制把每个元素转换成 JSONObject,代码虽然能跑,却隐藏着类型不匹配的风险。因为 JSONArray 内部存储的元素类型是 Object,它可以是 JSONObject、JSONArray、字符串、数字、布尔值,甚至是 JSONObject.NULL。真正可靠的做法是先判断每个元素的实际类型,再取出 JSONObject 做字段解析。

一、先厘清 JSONArray 的元素类型和取出方式
JSONArray 在 org.json 和 fastjson 中都是一个有序容器,实现了 Iterable 接口,但内部维护的元素类型声明为 Object。也就是说,当你调用 get(int index) 时,拿到的并不是一个已经确定好的 JSONObject,而是一个可能为任意类型的对象。这个设计本身是合理的,因为 JSON 数组本来就可以包含混合类型,例如 ["hello", 123, {"key":"value"}]。
下面这段代码演示了 JSONArray 中可能同时存在哪些类型。可以看到,同一个数组里既有 JSONObject,也有字符串、数字和 JSONObject.NULL。如果代码不做任何判断直接写 (JSONObject) jsonArray.get(i),在处理到字符串或数字元素时就会抛出 ClassCastException。
import org.json.JSONArray;
import org.json.JSONObject;
public class JsonArrayDemo {
public static void main(String[] args) {
JSONArray jsonArray = new JSONArray();
jsonArray.put(new JSONObject().put("name", "Tom").put("age", 20));
jsonArray.put("plain string");
jsonArray.put(123);
jsonArray.put(JSONObject.NULL);
for (int i = 0; i < jsonArray.length(); i++) {
Object item = jsonArray.get(i);
System.out.println("index=" + i + ", type=" + item.getClass().getName());
}
}
}
因此,逐个处理 JSONArray 的核心不是“如何取出来”,而是“如何安全地判断并取出 JSONObject”。只要在类型判断上做足功夫,后续的字段解析就能避免很多低级错误。不同的 JSON 库在 API 命名上略有差异,比如 fastjson 的 JSONArray 同样提供 getJSONObject(int index) 方法,但它内部并不会自动判断元素类型,遇到非 JSONObject 元素时同样会抛出异常。
二、用 for 循环配合 instanceof 安全提取
最直观也最稳妥的方式是使用普通 for 循环,先通过 get 方法取出 Object,再使用 instanceof 判断它是否为 JSONObject。只有判断通过后才进行强制转换,否则跳过该元素或记录日志。这种方式对运行时的类型变化有很好的容错性,也是大多数后端解析场景的默认选择。
下面的示例读取一个标准的用户 JSON 数组,并在循环内提取每个 JSONObject 的 name 和 age 字段。注意这里使用 optString 和 optInt 而不是 getString 和 getInt,因为 opt 系列方法在字段不存在时返回默认值,而不会抛出 JSONException,这样可以避免因为个别对象缺字段导致整个批次中断。
import org.json.JSONArray;
import org.json.JSONObject;
public class ExtractJsonObjects {
public static void main(String[] args) {
String jsonText = "[{\"name\":\"Tom\",\"age\":20},{\"name\":\"Jerry\",\"age\":25},{\"name\":\"Spike\",\"age\":30}]";
JSONArray jsonArray = new JSONArray(jsonText);
for (int i = 0; i < jsonArray.length(); i++) {
Object item = jsonArray.get(i);
if (item instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) item;
String name = jsonObject.optString("name", "unknown");
int age = jsonObject.optInt("age", 0);
System.out.println("User: " + name + ", Age: " + age);
} else {
System.out.println("Skip non-object item at index " + i);
}
}
}
}
这种写法虽然多了一行类型判断,但可读性很高,团队成员一眼就能看懂边界条件。尤其当 JSON 数据来自外部系统、结构不受控制时,显式的 instanceof 检查就是第一道防线。如果确定数组里的元素全部是 JSONObject,也可以使用 getJSONObject(i) 直接获取,但前提是数据来源足够可靠,否则不建议省略判断。
三、用迭代器和 Stream 让遍历代码更简洁
JSONArray 实现了 Iterable 接口,因此可以直接使用增强 for 循环遍历。增强 for 循环在字节码层面仍然会调用迭代器,但代码更加简洁,适合数组元素数量不大、处理逻辑简单的场景。下面示例先遍历每个元素,判断类型后放入一个专门的 List,再集中处理。
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
public class IterateJsonArray {
public static void main(String[] args) {
JSONArray jsonArray = new JSONArray("[{\"name\":\"Tom\"},{\"name\":\"Jerry\"},123]");
List<JSONObject> objectList = new ArrayList<JSONObject>();
for (Object item : jsonArray) {
if (item instanceof JSONObject) {
objectList.add((JSONObject) item);
}
}
objectList.forEach(obj -> {
String name = obj.optString("name");
System.out.println(name);
});
}
}
如果你习惯使用 Java 8 的函数式风格,还可以借助 StreamSupport 将 JSONArray 转成 Stream,然后通过 filter 和 map 完成类型过滤与转换。这种写法适合在提取之后还要进行排序、分组或映射等链式操作的场景。需要注意的是,org.json 的 JSONArray 本身并没有直接提供 stream() 方法,需要手动构造 Stream。
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
public class StreamJsonArray {
public static void main(String[] args) {
JSONArray jsonArray = new JSONArray("[{\"name\":\"Tom\"},{\"name\":\"Jerry\"},123]");
List<JSONObject> users = StreamSupport.stream(jsonArray.spliterator(), false)
.filter(item -> item instanceof JSONObject)
.map(item -> (JSONObject) item)
.collect(Collectors.toList());
users.forEach(user -> System.out.println(user.optString("name")));
}
}
虽然 Stream 写法看起来更高级,但它会引入额外的对象创建和迭代开销。如果只是简单地逐一处理,增强 for 循环或普通 for 循环已经足够。选择哪种方式,更多取决于团队规范以及处理逻辑的复杂程度,而不是一味追求函数式写法。
四、处理嵌套对象与 NULL 值的几个关键点
实际项目中的 JSON 很少只有一层结构,字段里经常包含嵌套对象、数组,或者直接就是 null。例如一个用户对象里可能包含 address 对象,而 address 里又有 city 字段。如果某一层的对象缺失,直接用 getJSONObject 就可能得到 null,后续继续调用方法会抛出 NullPointerException。因此,取嵌套对象时应当使用 optJSONObject 并判断返回值是否为 null。
import org.json.JSONArray;
import org.json.JSONObject;
public class NestedJsonDemo {
public static void main(String[] args) {
String jsonText = "[{\"user\":{\"name\":\"Tom\",\"address\":{\"city\":\"Beijing\"}},\"score\":null}]";
JSONArray jsonArray = new JSONArray(jsonText);
for (int i = 0; i < jsonArray.length(); i++) {
Object item = jsonArray.get(i);
if (!(item instanceof JSONObject)) {
continue;
}
JSONObject root = (JSONObject) item;
JSONObject user = root.optJSONObject("user");
if (user != null) {
JSONObject address = user.optJSONObject("address");
String city = address == null ? "" : address.optString("city", "");
String score = root.isNull("score") ? "no score" : root.optString("score");
System.out.println("City: " + city + ", Score: " + score);
}
}
}
}
这里还涉及一个容易混淆的概念:JSON 里的 null 对应的是 JSONObject.NULL,它不是 Java 的 null。在 org.json 中,调用 isNull("score") 可以判断该字段是否为 JSON 的 null 值。如果直接使用 optString("score"),当字段为 JSON null 时,返回的可能是字符串 "null",而不是预期的 null 或默认值,这会让后续逻辑产生误判。
对于 fastjson 用户,处理逻辑类似,但 API 略有不同。fastjson 的 JSONArray 提供 getJSONObject 和 getObject,如果元素类型不匹配,前者会抛异常,后者可以配合 instanceof 使用。无论使用哪个库,核心思路都是:先判断元素类型,再安全地向下解析,遇到可能缺失的字段一律走 opt 或判空分支。这样写出来的解析代码才能稳定应对真实世界中结构多变、字段不完备的 JSON 数据。
简而言之,从 JSONArray 中提取 JSONObject 并不是一个复杂操作,难的是让它足够健壮。类型判断、字段容错、嵌套判空,这三件事做到位,批量处理 JSON 数据时就能少踩很多坑。
JSONArrayJSONObject遍历处理修改时间:2026-08-25 23:55:50