Vai al contenuto
jsonbeautifiers
Italiano

Appiattire JSON

Trasforma JSON annidato in percorsi di chiave a un solo livello, e viceversa.

Annidato
Piatto

Nulla di ciò che incolli lascia il tuo browser. L’allowlist connect-src ne fa una garanzia del browser anziché una promessa. Verificalo tu stesso

Appiattire trasforma un documento annidato in un unico livello di percorsi puntati: {"a":{"b":1}} diventa {"a.b":1}. L’operazione inversa lo riporta com’era.

È quello che si fa prima di caricare del JSON in qualcosa di rettangolare: un foglio di calcolo, un data frame, uno store di feature flag, un file di environment, un modulo.

L’andata e ritorno, e l’unico caso che lo rompe

Appiattire e poi ricostruire restituisce il documento originale, oggetti e array vuoti compresi, che diverse implementazioni fanno sparire senza dirlo.

C’è esattamente un caso in cui non è possibile: una chiave che contiene il separatore stesso. Dato {"a.b": 1}, il percorso piatto "a.b" è indistinguibile da un {"a":{"b":1}} annidato. Questo strumento lo rileva e ti avvisa invece di produrre qualcosa che non tornerà indietro. Quando succede, scegli un altro separatore.

Array: notazione a indice o a parentesi quadre

La notazione puntata dà tags.0 e tags.1. Quella a parentesi quadre dà tags[0] e tags[1]. Entrambe fanno andata e ritorno qui, e il parser di input le accetta tutte e due.

La notazione puntata è quella che produce json_normalize di pandas ed è quella che si aspettano quasi tutte le pipeline CSV. Quella a parentesi si legge meglio quando una chiave potrebbe plausibilmente essere numerica, perché tags[0] e tags.0 sono ambigui in un modo in cui tags["0"] non lo è.

Dove l’appiattimento perde informazione

Una chiave di oggetto numerica, una volta appiattita, diventa indistinguibile da un indice di array. {"2024": {"total": 1}} diventa "2024.total", e ricostruirlo con il rilevamento degli array attivo produce un array con 2024 posizioni vuote.

Disattiva «chiavi numeriche come array» quando le tue chiavi sono davvero stringhe numeriche, cosa comune per tutto ciò che è indicizzato per anno, per codice di stato HTTP o per ID.

How to do this in code

Appiattire nel codice.

py Python, pandas

record_path è l’argomento che trasforma una relazione uno-a-molti in righe anziché in colonne numerate.

import pandas as pd

# The workhorse. sep defaults to '.'
df = pd.json_normalize(records)

# Explode a nested array into one row per element
df = pd.json_normalize(
    records,
    record_path='items',
    meta=['id', 'created_at'],
)
sh jq
# Every leaf as a dotted path
jq -r 'paths(scalars) as $p | "\($p | join(".")) = \(getpath($p))"' in.json

# A flat object rather than lines
jq '[leaf_paths as $p | {(($p | map(tostring) | join("."))): getpath($p)}] | add' in.json
js JavaScript

Il ramo dei contenitori vuoti è la riga che la maggior parte delle implementazioni omette, ed è il motivo per cui non reggono l’andata e ritorno.

function flatten(value, prefix = '', out = {}) {
  if (value && typeof value === 'object') {
    const entries = Array.isArray(value)
      ? value.map((v, i) => [i, v])
      : Object.entries(value);
    if (entries.length === 0) {
      out[prefix] = value;      // preserve {} and []
      return out;
    }
    for (const [k, v] of entries) {
      flatten(v, prefix ? `${prefix}.${k}` : String(k), out);
    }
    return out;
  }
  out[prefix] = value;
  return out;
}

Domande frequenti

Quale separatore conviene usare?
Un punto, a meno che le tue chiavi non contengano punti. Il trattino basso è la solita seconda scelta, e la barra è utile quando il risultato finisce da qualche parte che già ragiona per percorsi.
Posso appiattire solo una parte del documento?
Imposta un limite di profondità. Tutto ciò che sta oltre resta un valore annidato, che è quello che vuoi quando la parte profonda è un blob opaco che archivi anziché interrogare.
Che fine fa null?
Viene mantenuto per impostazione predefinita, come chiave piatta con valore null. C’è un’opzione per omettere i null: utile per un diff, pericolosa per l’andata e ritorno.