0Pricing
R Academy · Lección

Gestión de estructuras JSON anidadas

Aplane JSON profundamente anidado en marcos de datos ordenados para su análisis.

Gestión de estructuras JSON anidadas es una lección gratuita de R Academy en CoddyKit. Esta es la lección 4 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de R Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de R Academy incluye 4 lecciones en total.

Por qué el JSON anidado es complicado

Las API REST suelen devolver JSON profundamente anidado, donde un solo campo puede contener una matriz de objetos que, a su vez, contiene más objetos. Aplanar esta estructura para convertirla en un data frame ordenado requiere comprender cómo funcionan conjuntamente jsonlite, purrr y 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() — análisis básico

jsonlite::fromJSON() convierte una cadena JSON o una ruta de archivo en objetos de R. El JSON plano sencillo se convierte en una lista o un data frame. El JSON anidado se convierte en una lista anidada; las matrices de objetos se convierten en data frames almacenados dentro de columnas de listas.

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() con flatten = TRUE

El argumento flatten = TRUE indica a fromJSON() que desempaquete recursivamente los data frames anidados en columnas cuyos nombres están separados por puntos. Funciona bien con un nivel de anidación y es la forma más rápida de gestionar JSON con una anidación moderada.

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)

Las matrices anidadas se convierten en columnas de listas

Cuando un campo JSON contiene una matriz de objetos, fromJSON() la almacena como una columna de listas en el data frame: cada celda contiene un data frame. Debe acceder a estas columnas o desanidarlas explícitamente.

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() — extracción de campos anidados

purrr::map() aplica una función a cada elemento de una lista. Cuando cada elemento es una lista con nombre, como un objeto JSON analizado, puede pasar una cadena para extraer un campo con nombre de cada elemento; es una alternativa concisa a un bucle.

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() con la abreviatura de cadena

purrr::map(list, 'field_name') es una forma abreviada de extraer un campo con nombre de cada elemento, equivalente a map(list, function(x) x[['field_name']]). Use map_chr(), map_dbl(), etc., para obtener vectores atómicos con tipo en lugar de listas.

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)

Anidación profunda con cadenas de map()

Para trabajar con JSON profundamente anidado, encadene varias llamadas a map(). Cada llamada desciende un nivel. Use map(list, 'field') en cada nivel y aplique un map_*() con tipo en el nivel más interno para extraer el valor final.

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() — aplanamiento de columnas de listas

tidyr::unnest() expande una columna de listas que contiene data frames y crea una fila por cada elemento anidado. Es el enfoque tidy estándar para aplanar relaciones de uno a varios en datos 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() en data frames

jsonlite::flatten() funciona sobre un data frame ya analizado, no sobre una cadena JSON, y expande recursivamente cualquier columna que contenga data frames anidados en columnas cuyos nombres están separados por puntos. Resulta útil después de fromJSON(..., flatten = FALSE), cuando desea aplanar los datos como paso de posprocesamiento.

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)

Gestión de valores nulos en JSON anidado

Los valores JSON null se analizan como NULL en R, lo que causa problemas al construir data frames: un NULL dentro de una lista elimina por completo ese elemento. Use purrr::map() con un argumento .default o %||% para sustituir de forma segura los valores ausentes.

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)))

Pipeline completo: de JSON de una API a un data frame ordenado

Un ejemplo que reúne todos los conceptos: un pipeline realista que analiza JSON anidado de una API, extrae campos con purrr, gestiona los valores nulos y produce un data frame ordenado listo para el análisis.

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)

Comprobación rápida

Tiene un data frame df cuya columna orders es una columna de listas que contiene data frames, uno por usuario. ¿Qué función la expande para crear una fila por pedido?

JSON anidado — conclusiones principales

Para trabajar con JSON anidado en R se necesita un conjunto de herramientas por capas:

  • fromJSON(json, flatten = TRUE) — aplana automáticamente un nivel de anidamiento
  • fromJSON(json, simplifyDataFrame = FALSE) — obtiene una lista de listas para procesarla manualmente
  • Los arrays anidados → se convierten en columnas de listas en el data frame resultante
  • purrr::map_chr/dbl/int(list, 'field') — extrae valores tipados de cada elemento
  • Las llamadas encadenadas a map() descienden por niveles de anidamiento profundos
  • tidyr::unnest(df, cols = col) — expande las columnas de listas que contienen data frames
  • jsonlite::flatten(df) — aplana las columnas anidadas de data frames después del análisis
  • Proteja siempre los valores NULL con 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)

Preguntas frecuentes

¿La lección «Gestión de estructuras JSON anidadas» es gratis?

Sí — el texto completo de «Gestión de estructuras JSON anidadas» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de R Academy, actualiza a CoddyKit PRO. El curso de R Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Gestión de estructuras JSON anidadas»?

Aplane JSON profundamente anidado en marcos de datos ordenados para su análisis. Practicas R Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar R Academy?

No se requiere experiencia previa. R Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 4 de 4.

¿Cuánto tiempo toma la lección «Gestión de estructuras JSON anidadas»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de R Academy?

Sí. Cada lección de R Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Análisis de JSON con jsonlite
  2. Realización de solicitudes HTTP con httr2
  3. Consumo de API REST en R
  4. Gestión de estructuras JSON anidadas
← Volver a R Academy