Работа с вложенными структурами JSON
Преобразуйте глубоко вложенный JSON в аккуратные таблицы данных для анализа
«Работа с вложенными структурами JSON» — бесплатный урок R Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения 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)fromJSON() с flatten = TRUE
Аргумент 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() раскрывает столбец-список, содержащий таблицы данных, создавая по одной строке для каждого вложенного элемента. Это стандартный подход tidy к распрямлению связей «один ко многим» в данных 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)Обработка null во вложенном JSON
Значения JSON null преобразуются в NULL в R, что создаёт проблемы при построении таблиц данных: значение NULL в списке полностью удаляет элемент. Используйте purrr::map() с аргументом .default или %||%, чтобы безопасно заменять отсутствующие значения.
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)))Полный конвейер: от JSON API к аккуратной таблице данных
Объединим всё вместе: реалистичный конвейер, который разбирает вложенный JSON из API, извлекает поля с помощью purrr, обрабатывает значения null и создаёт аккуратную таблицу данных, готовую к анализу.
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 — ключевые выводы
Для работы с вложенным JSON в R требуется набор инструментов для разных уровней вложенности:
fromJSON(json, flatten = TRUE)— автоматически устранить один уровень вложенностиfromJSON(json, simplifyDataFrame = FALSE)— получить список списков для ручной обработки- Вложенные массивы → столбцы-списки в результирующем датафрейме
purrr::map_chr/dbl/int(list, 'field')— извлечь типизированные значения из каждого элемента- Цепочки вызовов
map()проходят через глубокие уровни вложенности tidyr::unnest(df, cols = col)— развернуть столбцы-списки, содержащие датафреймыjsonlite::flatten(df)— развернуть вложенные столбцы датафрейма после разбора- Всегда проверяйте значение на
NULLс помощьюif (is.null(x)) NA else x
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» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс R Academy, подпишись на CoddyKit PRO. Курс R Academy содержит 4 уроков всего.
Чему я научусь в уроке «Работа с вложенными структурами JSON»?
Преобразуйте глубоко вложенный JSON в аккуратные таблицы данных для анализа Ты практикуешь R Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать R Academy?
Предыдущий опыт не требуется. R Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Работа с вложенными структурами JSON»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке R Academy?
Да. Каждый урок R Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Разбор JSON с помощью jsonlite
- Выполнение HTTP-запросов с помощью httr2
- Работа с REST API в R
- Работа с вложенными структурами JSON