0Pricing
R Academy · 课时

处理嵌套 JSON 结构

将深层嵌套的 JSON 展平为用于分析的整洁数据框

处理嵌套 JSON 结构 是 CoddyKit 上的免费 R Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 R Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 R Academy 课程共包含 4 节课。

嵌套 JSON 为何难以处理

REST API 经常返回深度嵌套的 JSON,其中一个字段可能包含对象数组,而这些对象又包含更多对象。要将这种结构展开为整洁的数据框,需要理解 jsonlite、purrr 和 tidyr 如何协同工作。

# Example nested JSON from a REST API:
json_str <- '{
  "user": {
    "id": 1,
    "name": "Alice",
    "orders": [
      {"order_id": 101, "total": 59.99, "status": "shipped"},
      {"order_id": 102, "total": 24.50, "status": "pending"}
    ]
  }
}'

# The challenge: 'orders' is an array of objects inside 'user'
cat('Nested JSON loaded as string, length:', nchar(json_str))

fromJSON()——基本解析

jsonlite::fromJSON() 会将 JSON 字符串或文件路径转换为 R 对象。简单的扁平 JSON 会变成列表或数据框。嵌套 JSON 会变成嵌套列表,其中对象数组会变成存储在列表列中的数据框。

library(jsonlite)

# Parse flat JSON
flat_json <- '{"name": "Alice", "age": 30, "score": 95.5}'
result <- fromJSON(flat_json)
cat('Name:', result$name, '\n')
cat('Age: ', result$age,  '\n')

# Parse an array of objects — becomes a data frame
array_json <- '[{"id":1,"val":10},{"id":2,"val":20},{"id":3,"val":30}]'
df <- fromJSON(array_json)
cat('Class:', class(df), '\n')
print(df)

使用 flatten = TRUE 调用 fromJSON()

flatten = TRUE 参数会告诉 fromJSON() 递归地将嵌套数据框展开为列,并使用由点号分隔的名称。它适用于一层嵌套,也是处理中等复杂度嵌套 JSON 的最快方式。

library(jsonlite)

json_str <- '[{
  "id": 1,
  "name": "Alice",
  "address": {"city": "Berlin", "country": "Germany"}
},{
  "id": 2,
  "name": "Bob",
  "address": {"city": "Paris", "country": "France"}
}]'

# Without flatten:
nested_df <- fromJSON(json_str, flatten = FALSE)
cat('address class:', class(nested_df$address), '\n')

# With flatten = TRUE:
flat_df <- fromJSON(json_str, flatten = TRUE)
cat('Columns:', names(flat_df), '\n')
print(flat_df)

嵌套数组会变成列表列

当 JSON 字段包含对象数组时,fromJSON() 会将其存储为数据框中的列表列——每个单元格都包含一个数据框。您必须显式访问或取消嵌套这些数据。

library(jsonlite)

json_str <- '[{
  "user_id": 1,
  "tags": ["R", "Python", "SQL"]
},{
  "user_id": 2,
  "tags": ["Java", "Kotlin"]
}]'

df <- fromJSON(json_str)
cat('tags column class:', class(df$tags), '\n')

# Access tags for user 1:
cat('User 1 tags:', df$tags[[1]], '\n')
cat('User 2 tags:', df$tags[[2]])

purrr::map()——提取嵌套字段

purrr::map() 会对列表中的每个元素应用一个函数。当每个元素都是带名称的列表(解析后的 JSON 对象)时,您可以传入字符串,从每个元素中提取指定名称的字段——这是一种简洁的循环替代方式。

library(jsonlite)
library(purrr)

json_str <- '[{
  "id": 1,
  "meta": {"score": 88, "grade": "B"}
},{
  "id": 2,
  "meta": {"score": 95, "grade": "A"}
},{
  "id": 3,
  "meta": {"score": 72, "grade": "C"}
}]'

records <- fromJSON(json_str, simplifyDataFrame = FALSE)

# Extract 'score' from each record's 'meta' object
scores <- map_dbl(records, function(r) r$meta$score)
grades <- map_chr(records, function(r) r$meta$grade)

cat('Scores:', scores, '\n')
cat('Grades:', grades)

使用字符串简写调用 purrr::map()

purrr::map(list, 'field_name') 是从每个元素中提取指定名称字段的简写形式——等价于 map(list, function(x) x[['field_name']])。使用 map_chr()、map_dbl() 等函数可获得有类型的原子向量,而不是列表。

library(jsonlite)
library(purrr)

json_str <- '[{"name":"Alice","score":90},{"name":"Bob","score":78},{"name":"Carol","score":85}]'

# Parse as list of lists
records <- fromJSON(json_str, simplifyDataFrame = FALSE)

# String shortcut to extract field
names_vec  <- map_chr(records, 'name')
scores_vec <- map_dbl(records, 'score')

cat('Names: ', names_vec, '\n')
cat('Scores:', scores_vec, '\n')

# Build a clean data frame
clean_df <- data.frame(name = names_vec, score = scores_vec)
print(clean_df)

使用 map() 链处理深度嵌套

对于深度嵌套的 JSON,可以串联多个 map() 调用。每次调用都会深入一层。在每一层使用 map(list, 'field'),并在最内层使用带类型的 map_*() 提取最终值。

library(jsonlite)
library(purrr)

json_str <- '[{
  "id": 1,
  "company": {"hq": {"city": "Berlin", "country": "Germany"}}
},{
  "id": 2,
  "company": {"hq": {"city": "Tokyo",  "country": "Japan"}}
}]'

records <- fromJSON(json_str, simplifyDataFrame = FALSE)

# Navigate: records -> company -> hq -> city
cities <- map_chr(records, function(r) r$company$hq$city)
cat('Cities:', cities, '\n')

# Or using nested map shortcut:
ids <- map_int(records, 'id')
cat('IDs:', ids)

tidyr::unnest()——展开列表列

tidyr::unnest() 会展开包含数据框的列表列,为每个嵌套元素创建一行。这是整洁数据处理中展开 JSON 一对多关系的标准方式。

library(jsonlite)
library(tidyr)
library(dplyr)

json_str <- '[{
  "user_id": 1,
  "orders": [{"oid":101,"total":50},{"oid":102,"total":30}]
},{
  "user_id": 2,
  "orders": [{"oid":103,"total":80}]
}]'

df <- fromJSON(json_str)
cat('Before unnest, rows:', nrow(df), '\n')
cat('orders class:', class(df$orders), '\n')

# Unnest expands one row per order
expanded <- unnest(df, cols = orders)
cat('After unnest, rows:', nrow(expanded), '\n')
print(expanded)

对数据框使用 jsonlite::flatten()

jsonlite::flatten() 作用于已经解析的数据框(而不是 JSON 字符串),会递归展开其中嵌套的数据框列,并生成以点号分隔名称的列。在使用 fromJSON(..., flatten = FALSE) 后,如果希望将展开作为后处理步骤,这个函数就很有用。

library(jsonlite)

json_str <- '[{
  "id": 1,
  "profile": {"age": 25, "city": "Rome"}
},{
  "id": 2,
  "profile": {"age": 31, "city": "Oslo"}
}]'

# Parse without auto-flatten
nested <- fromJSON(json_str, flatten = FALSE)
cat('Columns before flatten:', names(nested), '\n')
cat('profile class:', class(nested$profile), '\n')

# Apply flatten() post-hoc
flat <- flatten(nested)
cat('Columns after flatten:', names(flat), '\n')
print(flat)

处理嵌套 JSON 中的空值

JSON 中的 null 值在 R 中会解析为 NULL,这会在构建数据框时造成问题——列表中的 NULL 会使该元素被完全删除。请使用带有 .default 参数的 purrr::map() 或 %||%,以安全地替换缺失值。

library(jsonlite)
library(purrr)

json_str <- '[{"id":1,"email":"alice@example.com"},{"id":2,"email":null},{"id":3,"email":"carol@example.com"}]'

records <- fromJSON(json_str, simplifyDataFrame = FALSE)

# Unsafe: NULL drops the element
# emails <- map_chr(records, 'email')  # ERROR on null

# Safe: provide a default for missing values
emails <- map_chr(records, function(r) {
  if (is.null(r$email)) NA_character_ else r$email
})

cat('Emails:', emails)
cat('NAs:', sum(is.na(emails)))

完整流程:从 API JSON 到整洁数据框

将所学内容结合起来:构建一个符合实际的流程,从 API 解析嵌套 JSON,使用 purrr 提取字段,处理空值,并生成可直接用于分析的整洁数据框。

library(jsonlite)
library(purrr)
library(dplyr)

# Simulated API response
api_json <- '[{
  "id": 1, "name": "Alice",
  "stats": {"score": 92, "rank": 1}
},{
  "id": 2, "name": "Bob",
  "stats": null
},{
  "id": 3, "name": "Carol",
  "stats": {"score": 85, "rank": 3}
}]'

records <- fromJSON(api_json, simplifyDataFrame = FALSE)

result <- tibble(
  id    = map_int(records,  'id'),
  name  = map_chr(records,  'name'),
  score = map_dbl(records,  function(r) if (is.null(r$stats)) NA_real_ else r$stats$score),
  rank  = map_int(records,  function(r) if (is.null(r$stats)) NA_integer_ else r$stats$rank)
)

print(result)

快速检查

您有一个数据框 df,其中的 orders 列是一个列表列,包含每位用户对应的数据框。哪个函数可以将其展开,使每个订单占一行?

嵌套 JSON — 要点总结

在 R 中处理嵌套 JSON 需要分层使用工具:

  • fromJSON(json, flatten = TRUE) — 自动展开一层嵌套
  • fromJSON(json, simplifyDataFrame = FALSE) — 获取列表的列表,以便手动处理
  • 嵌套数组 → 结果数据框中的列表列
  • purrr::map_chr/dbl/int(list, 'field') — 从每个元素中提取指定类型的值
  • 链式调用 map() 可逐层深入多层嵌套
  • tidyr::unnest(df, cols = col) — 展开包含数据框的列表列
  • jsonlite::flatten(df) — 解析后展开嵌套的数据框列
  • 始终使用 if (is.null(x)) NA else x 防范 NULL
library(jsonlite)
library(purrr)

# Quick reference:
json <- '[{"id":1,"info":{"val":10}},{"id":2,"info":null}]'
recs <- fromJSON(json, simplifyDataFrame = FALSE)

# Safe extraction with null guard
vals <- map_dbl(recs, function(r) {
  if (is.null(r$info)) NA_real_ else r$info$val
})

result <- data.frame(id = map_int(recs, 'id'), val = vals)
print(result)

常见问题解答

「处理嵌套 JSON 结构」课时是免费的吗?

是的 — 「处理嵌套 JSON 结构」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 R Academy 课程的其余内容,请升级到 CoddyKit PRO。 R Academy 课程共包含 4 节课。

「处理嵌套 JSON 结构」这节课中我会学到什么?

将深层嵌套的 JSON 展平为用于分析的整洁数据框 你通过在浏览器中直接运行的动手代码来练习 R Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 R Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 R Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「处理嵌套 JSON 结构」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 R Academy 课中编写并运行代码吗?

能。每节 R Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 使用 jsonlite 解析 JSON
  2. 使用 httr2 发起 HTTP 请求
  3. 在 R 中调用 REST API
  4. 处理嵌套 JSON 结构
← 返回 R Academy