0Pricing
R Academy · Lezione

Gestione di strutture JSON annidate

Appiattisca JSON profondamente annidati trasformandoli in data frame tidy per l'analisi

Gestione di strutture JSON annidate è una lezione R Academy gratuita su CoddyKit. Questa è la lezione 4 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento R Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso R Academy include 4 lezioni in totale.

Perché il JSON annidato è complesso

Le API REST restituiscono spesso JSON profondamente annidati, in cui un singolo campo può contenere un array di oggetti che a loro volta contengono altri oggetti. Per appiattire questa struttura in un data frame ordinato è necessario comprendere come collaborano jsonlite, purrr e 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() — analisi di base

jsonlite::fromJSON() converte una stringa JSON o il percorso di un file in oggetti R. Un JSON semplice e piatto diventa una lista o un data frame. Un JSON annidato diventa una lista annidata: gli array di oggetti diventano data frame memorizzati all'interno di colonne lista.

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

L'argomento flatten = TRUE indica a fromJSON() di decomprimere ricorsivamente i data frame annidati in colonne con nomi separati da punti. Funziona bene per un livello di annidamento ed è il modo più rapido per gestire JSON moderatamente annidati.

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)

Gli array annidati diventano colonne lista

Quando un campo JSON contiene un array di oggetti, fromJSON() lo memorizza come colonna lista nel data frame: ogni cella contiene un data frame. È necessario accedere esplicitamente a questi elementi o decomprimerli.

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() — estrarre campi annidati

purrr::map() applica una funzione a ogni elemento di una lista. Quando ogni elemento è una lista con nome, ovvero un oggetto JSON analizzato, è possibile passare una stringa per estrarre un campo con nome da ogni elemento: una sostituzione concisa di un ciclo.

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 scorciatoia per le stringhe

purrr::map(list, 'field_name') è una forma abbreviata per estrarre un campo con nome da ogni elemento, equivalente a map(list, function(x) x[['field_name']]). Utilizzi map_chr(), map_dbl() e così via per ottenere vettori atomici tipizzati anziché liste.

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)

Annidamento profondo con catene di map()

Per JSON profondamente annidati si concatenano più chiamate a map(). Ogni chiamata scende di un livello. Utilizzi map(list, 'field') a ogni livello e applichi una map_*() tipizzata al livello più interno per estrarre il valore finale.

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() — appiattire le colonne lista

tidyr::unnest() espande una colonna lista che contiene data frame, creando una riga per ogni elemento annidato. Questo è l'approccio tidy standard per appiattire le relazioni uno-a-molti nei dati 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() sui data frame

jsonlite::flatten() opera su un data frame già analizzato, non su una stringa JSON, ed espande ricorsivamente tutte le colonne contenenti data frame annidati in colonne con nomi separati da punti. È utile dopo fromJSON(..., flatten = FALSE), quando si desidera eseguire l'appiattimento in una fase successiva di post-elaborazione.

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)

Gestire i valori null nei JSON annidati

I valori JSON null vengono analizzati come NULL in R, causando problemi nella creazione di data frame: un NULL in una lista elimina completamente l'elemento. Utilizzi purrr::map() con un argomento .default oppure %||% per sostituire in modo sicuro i valori mancanti.

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 completa: dal JSON dell'API a un data frame ordinato

Mettiamo insieme tutti gli elementi: una pipeline realistica che analizza il JSON annidato di un'API, estrae i campi con purrr, gestisce i valori null e produce un data frame ordinato pronto per l'analisi.

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)

Verifica rapida

Dispone di un data frame df in cui la colonna orders è una colonna lista contenente data frame, uno per utente. Quale funzione espande questi dati creando una riga per ogni ordine?

JSON annidato — punti chiave

La gestione di JSON annidati in R richiede un insieme di strumenti organizzato su più livelli:

  • fromJSON(json, flatten = TRUE) — appiattisce automaticamente un livello di annidamento
  • fromJSON(json, simplifyDataFrame = FALSE) — restituisce una lista di liste da elaborare manualmente
  • Gli array annidati → diventano colonne di liste nel data frame risultante
  • purrr::map_chr/dbl/int(list, 'field') — estrae valori tipizzati da ogni elemento
  • Chiamate concatenate a map() consentono di scendere attraverso livelli di annidamento profondi
  • tidyr::unnest(df, cols = col) — espande le colonne di liste contenenti data frame
  • jsonlite::flatten(df) — appiattisce le colonne annidate dei data frame dopo il parsing
  • Gestisca sempre 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)

Domande Frequenti

La lezione «Gestione di strutture JSON annidate» è gratuita?

Sì — il testo completo di «Gestione di strutture JSON annidate» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso R Academy, passa a CoddyKit PRO. Il corso R Academy include 4 lezioni in totale.

Cosa imparerò in «Gestione di strutture JSON annidate»?

Appiattisca JSON profondamente annidati trasformandoli in data frame tidy per l'analisi Eserciti R Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare R Academy?

Non è richiesta alcuna esperienza precedente. R Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.

Quanto tempo richiede la lezione «Gestione di strutture JSON annidate»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione R Academy?

Sì. Ogni lezione R Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Analisi di JSON con jsonlite
  2. Invio di richieste HTTP con httr2
  3. Utilizzo di API REST in R
  4. Gestione di strutture JSON annidate
← Torna a R Academy