JSON Toolkit

JSON do CSV: skąd bierze się nagłówek i dlaczego zmieniają się wartości

Podsumowanie: Dziewięć eksperymentów na prawdziwym konwerterze: strategia „klucze z pierwszego wiersza" cicho gubi pola, null, brak i pusty string stają się wszystkim pustymi komórkami, liczba całkowita o 20 cyfrach traci precyzję już w JSON.parse, a kropka w kluczu sprawia, że dwa różne ścieżki kolidują w tej samej kolumnie. Jak powstaje nagłówek, które wartości się deformują i jak wybrać strategię według odbiorcy danych.

Zaawansowany~6 min czytaniaOpublikowano 2026-11-04Zaktualizowano 2026-11-04
Strategie nagłówków i wierność wartości przy konwersji JSON do CSV

Gdy konwersja JSON do CSV gubi dane, prawie nigdy nie dzieje się to w kroku „konwersji". Strata powstaje w dwóch pytaniach, których nikt nie zadał: kto zdecydował o nagłówku i czy każda komórka pozostaje rozpoznawalna jako ta wartość, którą była?

Pole znika w trzydzieści sekund

Wiele konwerterów — w tym idiomatyczny pipeline jq — bierze nagłówek tylko z pierwszego rekordu. Odtwórz konsekwencje w Pythonie:

data = [{"id": 1, "name": "A"}, {"id": 2, "email": "b@x.com"}]
first_keys = list(data[0].keys())          # ['id', 'name']
rows = [[r.get(k, "") for k in first_keys] for r in data]
# E-mail drugiego rekordu znika bez błędu i ostrzeżenia

Klucza email nie ma w nagłówku: nie ma on nawet prawa stać się pustą komórką — kolumna nigdy nie powstała, a wartość jest cicho wyrzucana. Poprawka to unia kluczy: przejść po wszystkich rekordach i utworzyć kolumnę dla każdego klucza, który pojawi się gdziekolwiek. Te same dane przez unię dają nagłówek id,name,email, z pustą komórką tam, gdzie brakuje name — i nic nie ginie.

Konwerter bezpośredni na naszej stronie /convert/csv stosuje dokładnie tę strategię. Dla wejścia [{"id":1,"name":"A"},{"id":2,"email":"b@x.com"}] daje:

id,name,email
1,A,
2,,b@x.com

Klucze są uporządkowane według pierwszego wystąpienia, a brakujące pola stają się pustymi komórkami. jsonoperations i jsonbeam nazywają obie to „poprawką klasycznego buga" — ale „używa unii" to tylko połowa historii. Unia ma swoją cenę; to następna sekcja.

Trzy sposoby zbudowania nagłówka — i komu który pasuje

Strategia nagłówka Jak powstaje Siła Cena
Klucze z pierwszego wiersza Czytany jest tylko pierwszy rekord Mało kolumn, stabilnie, jedna linijka jq Nowe pola kolejnych rekordów gubią się po cichu
Unia kluczy Czytane są wszystkie rekordy Żadne pole nie ginie Rzadkie kolumny i dużo pustych, gdy formy się różnią
Jawna lista kolumn Zdefiniowana ręcznie lub przez schemat W pełni kontrolowalna, odtwarzalna Wymaga utrzymania; nowe pola wymagają ręcznej aktualizacji

Kryterium wyboru nie brzmi „które jest bardziej wyrafinowane", lecz co odbiorca zrobi z tym CSV:

  • Kolega, który rzuci okiem: klucze z pierwszego wiersza wystarczą, formy rekordów bywają zwykle jednorodne.
  • Raport albo pipeline danych: unia kluczy to minimum — pusta komórka sama jest informacją.
  • Cykliczny automatyczny eksport: jawna lista kolumn. Dryf formy (pole dodane po stronie źródła, zmieniona nazwa klucza) musi głośnie padać, a nie po cichu produkować tabelę, której nikt nie zauważa jako błędnej.

Trzecia opcja jest najbardziej zaniedbywana. W automatyzacji „konwersja się powiodła" i „konwersja jest poprawna" to dwa różne zdania — a jawna lista kolumn to jedyny mechanizm, który je rozdziela.

Gdy klucz zawiera kropkę, ścieżki spłaszczania kolidują

Spłaszczanie w notacji kropkowej zamienia {"a": {"b": 2}} w kolumnę a.b. Ale co, gdy klucz sam zawiera kropkę? Sprawdzone na pandas:

import pandas as pd
d = [{"a.b": 1, "a": {"b": 2}}]
pd.json_normalize(d, sep=".")
# Przetrwa tylko jedna kolumna: ['a.b'] — dwa zupełnie różne klucze kolidują na tej samej ścieżce

Dosłowny klucz a.b i zagnieżdżona ścieżka a → b dają tę samą nazwę kolumny, jedna nadpisuje drugą, a dane cicho się psują. To skonstruowany przykład, ale zdarza się na prawdziwych danych: nazwy metryk monitoringu (cpu.usage) i pola logów (error.type) kochają kropki.

Dwa remedia:

  1. Zmień separator. sep="_" albo własny separator — po sprawdzeniu, że oryginalne klucze nigdy go nie zawierają.
  2. Zmień notację ścieżek. JSON Pointer używa / jako separatora (/a/b); konwerter Excel toolvore idzie tą drogą. Ma tę samą klasę niejednoznaczności — klucz z / zapisuje się jako ~1 — ale reguły escapowania są znormalizowane w RFC 6901, a nie wymyślane osobno przez każde narzędzie.

Cokolwiek wybierzesz, zapisz regułę w dokumentacji eksportu. Nagłówek CSV to jedyny „schemat", jaki niosą te dane; czytelnik nigdzie indziej nie znajdzie Twoich reguł spłaszczania.

Tablice: trzy strategie, każda gubi coś innego

Tablice to prawdziwa przeszkoda na drodze JSON do CSV: komórka nie może zawierać wielu wartości. Trzy strategie, z przegranymi zweryfikowanymi w praktyce:

Strategia Kształt wyjścia Co się gubi Idealne dla
JSON w komórce Tablica zserializowana jako string JSON w komórce Narzędzia tabelaryczne nie mogą filtrować ani agregować elementów Wierność najpierw, ręczna inspekcja
Kolumny indeksowane tags.0, tags.1, po jednej kolumnie Liczba kolumn wybucha wraz z najdłuższą tablicą; dużo pustych Małe tablice o stałej długości
Rozwinięte wiersze Jeden wiersz na element, pola rodzica zduplikowane Inflacja wierszy; zachowanie pustych tablic trzeba określić osobno Analiza szczegółów, agregacja po elemencie

Nasze konwertery /convert/csv i /convert/table używają JSON w komórce. Wejście [{"orderId":"A001","items":[{"sku":"X1","qty":2},{"sku":"Y9","qty":1}]}] daje:

orderId,items
A001,"[{""sku"":""X1"",""qty"":2},{""sku"":""Y9"",""qty"":1}]"

Gdy potrzebujesz rozwiniętych wierszy, pandas record_path robi to wprost:

orders = [{"orderId": "A001", "total": 99,
           "items": [{"sku": "X1", "qty": 2}, {"sku": "Y9", "qty": 1}]}]
items = pd.json_normalize(orders, record_path=["items"],
                          meta=["orderId", "total"], sep=".")
# Dwa wiersze szczegółów; orderId i total zduplikowane w każdym

W rozwijaniu są trzy pułapki do rozstrzygnięcia zawczasu: rozwinięcie kilku tablic naraz buduje iloczyn kartezjański; puste tablice domyślnie nie dają żadnego wiersza (Twoja lista klientów traci ludzi); a zduplikowane pola rodzica wymagają deduplikacji przy agregacji po orderId po stronie odbiorcy. Poradnik convertcsvonline wymienia „najpierw ustal granularność" jako pierwszy krok dokładnie dlatego — gdy granularność jest ustalona, każde z trzech pytań ma jedną odpowiedź.

Przetestowane: wartości, które wychodzą z konwersji zmienione

Test wierności wykonany na naszym prawdziwym konwerterze (Node v22.13.1 + papaparse, ta sama implementacja co /convert/csv):

1. null, brak i pusty string stają się jednakowymi pustymi komórkami. Wejście [{"a":null},{"b":""},{"c":1}] — wszystkie trzy komórki wychodzą puste. CSV nie ma trzech stanów, tylko „są znaki" i „nie ma znaków". Po round-trip wszystkie trzy czytają się jako ""; pierwotna semantyka jest nieodwracalna.

2. Zagnieżdżone obiekty i tablice stają się stringami JSON. Cudzysłowy są podwajane według RFC 4180 ({""name"":""张三""}), a po wklejeniu komórki w dowolny parser JSON wartość wraca — dlatego JSON w komórce zachowuje wierność.

3. Liczba całkowita o 20 cyfrach traci precyzję już przy parsowaniu — i winne nie jest CSV. Wejście "id": 9007199254740993; w CSV jest już 9007199254740992. JSON.parse w JavaScript używa double'ów IEEE 754: strata zdarza się przy parsowaniu, przed jakimkolwiek konwerterem CSV. Python json.load zachowuje ją w całości. Pola-identyfikatory (ID snowflake, numery zamówień) powinny przychodzić jako stringi już od producenta. Więcej o precyzji liczb w dlaczego duże liczby tracą precyzję.

4. Zera wiodące w stringach są bezpieczne na poziomie tekstu CSV. "phone": "00123" zostaje 00123 w CSV, bo to string od początku do końca i nic go nie drasnie. Drasnie zera dopiero późniejszy krok, gdy Excel traktuje komórkę jako liczbę — zobacz cztery sposoby konwersji JSON do Excela.

5. Pola z przecinkami, cudzysłowami lub znakami nowej linii. papaparse trzyma się RFC 4180: ująć pole w cudzysłów, podwoić wewnętrzne cudzysłowy. "He said \"hi\", ok" wychodzi jako "He said ""hi"", ok" i przechodzi round-trip bez szwanku. Ręcznie pisane serializatory omijające bibliotekę prawie zawsze zawodzą dokładnie tu.

Round-trip: co zmienia przełącznik wnioskowania

CSV → JSON musi zgadywać typ każdej komórki. Nasz konwerter domyślnie wyłącza wnioskowanie typów i zwraca wszystko jako stringi:

[{ "id": "9007199254740992", "phone": "00123" }]

Z włączonym typeInference id staje się liczbą (precyzja już przepadła), a phone pozostaje stringiem — reguły wnioskowania wykluczają formy z zerem wiodącym jak 010, które nie są legalnymi literałami liczbowymi, więc string przeżywa przez szczęście.

Zapamiętaj tę asymetrię: wyłączone wnioskowanie kosztuje Cię najwyżej typy; włączone może kosztować dane. Kolumny, które potrzebują liczb, konwertuje się jawnymi definicjami kolumn, a nie globalnym wnioskowaniem. Implementacja rowsToRecords z naszego wcześniejszego artykułu JSON, CSV i dane tabelaryczne to dokładnie ta zasada w kodzie.

Wybierz strategię według odbiorcy: tabela decyzyjna

Kto dostaje CSV Strategia nagłówka Obsługa tablic Typy
Kolega, który otworzy raz Unia kluczy JSON w komórce Bez wnioskowania
Excel / raportowanie Unia kluczy JSON w komórce, albo kolumny indeksowane przy stałej długości Kolumny-identyfikatory wymuszone jako tekst
Import do bazy danych Jawna lista kolumn Osobne tabele master/detale, albo rozwinięcie Jawne definicje kolumn
BI / agregacja Jawna lista kolumn Rozwinięte wiersze, granularność = najmniejsza jednostka analizy Jawne definicje kolumn
Zautomatyzowany pipeline Jawna lista kolumn JSON w komórce, rozwinięcie u odbiorcy Wnioskowanie wyłączone, konwersja kolumna po kolumnie

Wspólna zasada: im bliżej człowieka, tym więcej wolno rozluźnić; im bliżej maszyny, tym więcej musi być jawnego.

Checklista do skopiowania

  • [ ] Nagłówek pochodzi z pełnej unii kluczy czy z jawnej listy? Jeśli nie — potrafisz udowodnić, że żaden rekord nie stracił pola?
  • [ ] Klucze mogą zawierać kropki lub ukośniki? Czy sprawdzono kolizję z separatorem spłaszczania?
  • [ ] Strategia tablic jest udokumentowana? Zachowanie pustych tablic jest rozstrzygnięte?
  • [ ] Dla null, braku i pustego stringa: co czyta odbiorca w każdym przypadku? Wybierz regułę i zamroź ją.
  • [ ] Kolumny-identyfikatory (ID, numery zamówień, telefony) podróżują jako stringi od początku do końca?
  • [ ] Po rozwinięciu liczba wierszy równa się sumie długości tablic? Sprawdzone losowo?
  • [ ] Pola przykładowe z przecinkami, cudzysłowami i nowymi liniami przechodzą round-trip bez szwanku?
  • [ ] Globalne wnioskowanie typów jest wyłączone przy reimporcie, z jawnymi definicjami dla kolumn liczbowych?

Dopiero gdy wszystkie osiem ma jasną odpowiedź, ten CSV jest „poprawny" — a nie tylko „pomyślnie skonwertowany".

FAQ

Dlaczego konwerter nie spłaszcza zagnieżdżonych obiektów do kolumn domyślnie?

Spłaszczanie wyprowadza liczbę kolumn spod kontroli: głębokie zagnieżdżenie produkuje nazwy jak user.address.geo.lat, a w połączeniu z unią kluczy jeden rekord o innej formie dodaje cały stos kolumn. Stringi JSON w komórkach zachowują wierność i stabilną liczbę kolumn; spłaszczaj jawnie, gdy potrzebujesz.

Co zrobić, gdy nagłówek zawiera zduplikowane nazwy kolumn?

Kolizja dwóch różnych kluczy po spłaszczeniu (jak a.b kontra zagnieżdżone a→b) musi zostać rozwiązana po stronie źródła: zmień separator albo przejdź na JSON Pointer. Dla duplikatów wewnątrz tego samego obiektu konwertery zwykle zostawiają ostatnią wartość; duplikaty w nagłówku zależą od implementacji — unparse papaparse nie deduplikuje, a Excel przy otwieraniu cicho scala albo wyrzuca duplikaty. Dlatego jest to w checkliście.

Czy zduplikowane pola rodzica po rozwinięciu to bug?

Nie — to projekt. Rozwinięty wiersz to wiersz szczegółów, a wiersz szczegółów musi dać się doczepić do rekordu rodzica po kluczu. Agreguj po stronie odbiorcy po kluczu rodzica; jeśli rozmiar niepokoi, wyeksportuj plik szczegółów i osobną tabelę master.

Jak sprawdzić, że moja konwersja nie straciła żadnego pola?

Przeprowadź audyt kluczy: policz unię wszystkich kluczy źródłowego JSON, potem zbiór nagłówków kolumn CSV, i zrób różnicę. Niepusta różnica oznacza stracone pola. W pipeline'ach wsadowych zakoduj to jako asercję w testach: każda zmiana konwertera, która zacznie gubić pola, automatycznie upadnie.