[{"data":1,"prerenderedAt":2553},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-an-excel-sheet-to-json-with-python":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-an-excel-sheet-to-json-with-python":2545},{"id":4,"title":5,"body":6,"dateModified":2522,"datePublished":2522,"description":2523,"extension":2524,"faq":2525,"meta":2536,"navigation":220,"path":2537,"seo":2538,"slug":2541,"stem":2542,"type":2543,"__hash__":2544},"docs\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-an-excel-sheet-to-json-with-python\u002Findex.md","Convert an Excel Sheet to JSON with Python",{"type":7,"value":8,"toc":2504},"minimark",[9,24,150,155,182,186,192,294,304,308,314,461,481,485,505,611,641,645,650,747,807,886,890,1075,1085,1089,1092,1336,1346,1350,1353,1428,1431,1496,1499,1503,1506,1789,1796,1800,1803,1909,1931,1934,2069,2121,2125,2128,2280,2283,2287,2357,2361,2378,2382,2391,2395,2412,2421,2444,2454,2460,2464,2500],[10,11,12,13,17,18,23],"p",{},"Sending spreadsheet data to an API, a JavaScript front end or a message queue means converting it to JSON, and the conversion is where the data's quiet problems become loud: dates turn into epoch numbers, empty cells become ",[14,15,16],"code",{},"NaN"," that no JSON parser accepts, and numeric-looking identifiers lose their leading zeros. This guide converts a sheet properly — clean types, ISO dates, real nulls — then covers nested output, whole workbooks, and the streaming format for files too big for one array. It belongs to ",[19,20,22],"a",{"href":21},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002F","Handling Excel File Formats and Conversions",".",[25,26,34,35,34,39,34,43,34,50,34,57,34,62,34,71,34,77,34,80,34,84,34,87,34,91,34,95,34,100,34,102,34,105,34,107,34,111,34,119,34,124,34,131,34,137,34,141,34,145],"svg",{"viewBox":27,"role":28,"ariaLabelledBy":29,"xmlns":32,"style":33},"0 0 760 226","img",[30,31],"js-t","js-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:760px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[36,37,38],"title",{"id":30},"A row of cells becoming a JSON object",[40,41,42],"desc",{"id":31},"Each column header becomes a key and each cell becomes a value, with dates rendered as ISO strings and empty cells as null rather than NaN.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","226","#ffffff",[51,52,56],"text",{"x":53,"y":54,"style":55},"196","28","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","worksheet row",[51,58,61],{"x":59,"y":54,"style":60},"566","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","JSON object",[44,63],{"x":64,"y":65,"width":66,"height":67,"rx":68,"fill":69,"stroke":70},"30","46","106","40","6","#ebebfd","var(--line,#cdd5e6)",[51,72,76],{"x":73,"y":74,"style":75},"83","71","font-size:11.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","order_id",[44,78],{"x":79,"y":65,"width":66,"height":67,"rx":68,"fill":69,"stroke":70},"140",[51,81,83],{"x":82,"y":74,"style":75},"193","ordered",[44,85],{"x":86,"y":65,"width":66,"height":67,"rx":68,"fill":69,"stroke":70},"250",[51,88,90],{"x":89,"y":74,"style":75},"303","note",[44,92],{"x":64,"y":93,"width":66,"height":67,"rx":68,"fill":94,"stroke":70},"92","#f0f2f5",[51,96,99],{"x":73,"y":97,"style":98},"117","font-size:11.5px;fill:var(--text,#172033);text-anchor:middle","00417",[44,101],{"x":79,"y":93,"width":66,"height":67,"rx":68,"fill":94,"stroke":70},[51,103,104],{"x":82,"y":97,"style":98},"45900",[44,106],{"x":86,"y":93,"width":66,"height":67,"rx":68,"fill":94,"stroke":70},[51,108,110],{"x":89,"y":97,"style":109},"font-size:11.5px;fill:var(--muted,#5b6780);text-anchor:middle","(empty)",[112,113],"line",{"x1":114,"y1":115,"x2":116,"y2":115,"stroke":117,"style":118},"356","90","400","var(--brand,#5b5cf0)","stroke-width:2px",[120,121],"polygon",{"points":122,"fill":123},"400,90 390,85 390,95","#5b5cf0",[44,125],{"x":126,"y":65,"width":127,"height":128,"rx":129,"fill":130,"stroke":70},"404","326","120","10","#d9f4f1",[51,132,136],{"x":133,"y":134,"style":135},"567","76","font-size:12px;fill:var(--teal-ink,#0b6157);text-anchor:middle","\"order_id\": \"00417\"",[51,138,140],{"x":133,"y":139,"style":135},"104","\"ordered\": \"2025-09-14\"",[51,142,144],{"x":133,"y":143,"style":135},"132","\"note\": null",[51,146,149],{"x":147,"y":148,"style":109},"380","200","Three conversions that go wrong by default: text ids, serial dates, empty cells",[151,152,154],"h2",{"id":153},"prerequisites","Prerequisites",[156,157,162],"pre",{"className":158,"code":159,"language":160,"meta":161,"style":161},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,163,164],{"__ignoreMap":161},[165,166,168,172,176,179],"span",{"class":112,"line":167},1,[165,169,171],{"class":170},"sMTad","pip",[165,173,175],{"class":174},"srMev"," install",[165,177,178],{"class":174}," pandas",[165,180,181],{"class":174}," openpyxl\n",[151,183,185],{"id":184},"the-straightforward-conversion","The straightforward conversion",[10,187,188,191],{},[14,189,190],{},"to_json"," handles the whole job when the types are already right:",[156,193,197],{"className":194,"code":195,"language":196,"meta":161,"style":161},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ndf = pd.read_excel(\"orders.xlsx\", engine=\"openpyxl\")\ndf.to_json(\"orders.json\", orient=\"records\", indent=2, date_format=\"iso\")\n","python",[14,198,199,215,222,252],{"__ignoreMap":161},[165,200,201,205,209,212],{"class":112,"line":167},[165,202,204],{"class":203},"s-kum","import",[165,206,208],{"class":207},"skGVy"," pandas ",[165,210,211],{"class":203},"as",[165,213,214],{"class":207}," pd\n",[165,216,218],{"class":112,"line":217},2,[165,219,221],{"emptyLinePlaceholder":220},true,"\n",[165,223,225,228,231,234,237,240,244,246,249],{"class":112,"line":224},3,[165,226,227],{"class":207},"df ",[165,229,230],{"class":203},"=",[165,232,233],{"class":207}," pd.read_excel(",[165,235,236],{"class":174},"\"orders.xlsx\"",[165,238,239],{"class":207},", ",[165,241,243],{"class":242},"sa561","engine",[165,245,230],{"class":203},[165,247,248],{"class":174},"\"openpyxl\"",[165,250,251],{"class":207},")\n",[165,253,255,258,261,263,266,268,271,273,276,278,282,284,287,289,292],{"class":112,"line":254},4,[165,256,257],{"class":207},"df.to_json(",[165,259,260],{"class":174},"\"orders.json\"",[165,262,239],{"class":207},[165,264,265],{"class":242},"orient",[165,267,230],{"class":203},[165,269,270],{"class":174},"\"records\"",[165,272,239],{"class":207},[165,274,275],{"class":242},"indent",[165,277,230],{"class":203},[165,279,281],{"class":280},"sP0c6","2",[165,283,239],{"class":207},[165,285,286],{"class":242},"date_format",[165,288,230],{"class":203},[165,290,291],{"class":174},"\"iso\"",[165,293,251],{"class":207},[10,295,296,299,300,303],{},[14,297,298],{},"orient=\"records\""," produces a list of objects — one per row — which is what an API or a front end expects. ",[14,301,302],{},"date_format=\"iso\""," is the argument that stops datetimes becoming epoch milliseconds, and it should be in essentially every call.",[151,305,307],{"id":306},"get-the-types-right-before-converting","Get the types right before converting",[10,309,310,311,313],{},"JSON has no way to record that ",[14,312,99],{}," was meant to be text, so the fix belongs at the read:",[156,315,317],{"className":194,"code":316,"language":196,"meta":161,"style":161},"import pandas as pd\n\ndf = pd.read_excel(\n    \"orders.xlsx\",\n    engine=\"openpyxl\",\n    dtype={\"order_id\": \"string\", \"postcode\": \"string\"},   # keep leading zeros\n    parse_dates=[\"ordered\"],\n)\ndf[\"total\"] = pd.to_numeric(df[\"total\"], errors=\"coerce\")\nprint(df.dtypes)\n",[14,318,319,329,333,342,350,362,398,415,420,452],{"__ignoreMap":161},[165,320,321,323,325,327],{"class":112,"line":167},[165,322,204],{"class":203},[165,324,208],{"class":207},[165,326,211],{"class":203},[165,328,214],{"class":207},[165,330,331],{"class":112,"line":217},[165,332,221],{"emptyLinePlaceholder":220},[165,334,335,337,339],{"class":112,"line":224},[165,336,227],{"class":207},[165,338,230],{"class":203},[165,340,341],{"class":207}," pd.read_excel(\n",[165,343,344,347],{"class":112,"line":254},[165,345,346],{"class":174},"    \"orders.xlsx\"",[165,348,349],{"class":207},",\n",[165,351,353,356,358,360],{"class":112,"line":352},5,[165,354,355],{"class":242},"    engine",[165,357,230],{"class":203},[165,359,248],{"class":174},[165,361,349],{"class":207},[165,363,365,368,370,373,376,379,382,384,387,389,391,394],{"class":112,"line":364},6,[165,366,367],{"class":242},"    dtype",[165,369,230],{"class":203},[165,371,372],{"class":207},"{",[165,374,375],{"class":174},"\"order_id\"",[165,377,378],{"class":207},": ",[165,380,381],{"class":174},"\"string\"",[165,383,239],{"class":207},[165,385,386],{"class":174},"\"postcode\"",[165,388,378],{"class":207},[165,390,381],{"class":174},[165,392,393],{"class":207},"},   ",[165,395,397],{"class":396},"s-wDw","# keep leading zeros\n",[165,399,401,404,406,409,412],{"class":112,"line":400},7,[165,402,403],{"class":242},"    parse_dates",[165,405,230],{"class":203},[165,407,408],{"class":207},"[",[165,410,411],{"class":174},"\"ordered\"",[165,413,414],{"class":207},"],\n",[165,416,418],{"class":112,"line":417},8,[165,419,251],{"class":207},[165,421,423,426,429,432,434,437,439,442,445,447,450],{"class":112,"line":422},9,[165,424,425],{"class":207},"df[",[165,427,428],{"class":174},"\"total\"",[165,430,431],{"class":207},"] ",[165,433,230],{"class":203},[165,435,436],{"class":207}," pd.to_numeric(df[",[165,438,428],{"class":174},[165,440,441],{"class":207},"], ",[165,443,444],{"class":242},"errors",[165,446,230],{"class":203},[165,448,449],{"class":174},"\"coerce\"",[165,451,251],{"class":207},[165,453,455,458],{"class":112,"line":454},10,[165,456,457],{"class":280},"print",[165,459,460],{"class":207},"(df.dtypes)\n",[10,462,463,464,239,467,469,470,473,474,476,477,23],{},"Without ",[14,465,466],{},"dtype",[14,468,99],{}," becomes the number ",[14,471,472],{},"417"," and the JSON says ",[14,475,472],{}," — a value that will never match the source system's records again. The wider problem is covered in ",[19,478,480],{"href":479},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-excel-text-columns-to-numbers-with-pandas\u002F","Convert Excel text columns to numbers with pandas",[151,482,484],{"id":483},"deal-with-empty-cells","Deal with empty cells",[10,486,487,488,490,491,493,494,497,498,501,502,504],{},"Empty cells become ",[14,489,16],{},", which is a float, not a null. ",[14,492,190],{}," renders it as ",[14,495,496],{},"null"," correctly, but the moment you build the structure yourself, ",[14,499,500],{},"json.dumps"," writes the literal ",[14,503,16],{}," — which is not valid JSON and will be rejected by strict parsers:",[156,506,508],{"className":194,"code":507,"language":196,"meta":161,"style":161},"import json\n\nimport pandas as pd\n\ndf = pd.read_excel(\"orders.xlsx\")\n\nrecords = df.astype(object).where(df.notna(), None).to_dict(orient=\"records\")\nprint(json.dumps(records[:2], indent=2, default=str))\n",[14,509,510,517,521,531,535,547,551,581],{"__ignoreMap":161},[165,511,512,514],{"class":112,"line":167},[165,513,204],{"class":203},[165,515,516],{"class":207}," json\n",[165,518,519],{"class":112,"line":217},[165,520,221],{"emptyLinePlaceholder":220},[165,522,523,525,527,529],{"class":112,"line":224},[165,524,204],{"class":203},[165,526,208],{"class":207},[165,528,211],{"class":203},[165,530,214],{"class":207},[165,532,533],{"class":112,"line":254},[165,534,221],{"emptyLinePlaceholder":220},[165,536,537,539,541,543,545],{"class":112,"line":352},[165,538,227],{"class":207},[165,540,230],{"class":203},[165,542,233],{"class":207},[165,544,236],{"class":174},[165,546,251],{"class":207},[165,548,549],{"class":112,"line":364},[165,550,221],{"emptyLinePlaceholder":220},[165,552,553,556,558,561,564,567,570,573,575,577,579],{"class":112,"line":400},[165,554,555],{"class":207},"records ",[165,557,230],{"class":203},[165,559,560],{"class":207}," df.astype(",[165,562,563],{"class":280},"object",[165,565,566],{"class":207},").where(df.notna(), ",[165,568,569],{"class":280},"None",[165,571,572],{"class":207},").to_dict(",[165,574,265],{"class":242},[165,576,230],{"class":203},[165,578,270],{"class":174},[165,580,251],{"class":207},[165,582,583,585,588,590,592,594,596,598,600,603,605,608],{"class":112,"line":417},[165,584,457],{"class":280},[165,586,587],{"class":207},"(json.dumps(records[:",[165,589,281],{"class":280},[165,591,441],{"class":207},[165,593,275],{"class":242},[165,595,230],{"class":203},[165,597,281],{"class":280},[165,599,239],{"class":207},[165,601,602],{"class":242},"default",[165,604,230],{"class":203},[165,606,607],{"class":280},"str",[165,609,610],{"class":207},"))\n",[10,612,613,616,617,619,620,622,623,626,627,630,631,634,635,634,638,23],{},[14,614,615],{},"where(df.notna(), None)"," is the idiom worth memorising: it replaces every missing value with ",[14,618,569],{},", which serialises to ",[14,621,496],{},". ",[14,624,625],{},"default=str"," catches anything else ",[14,628,629],{},"json"," cannot handle — a ",[14,632,633],{},"Decimal",", a ",[14,636,637],{},"Timestamp",[14,639,640],{},"date",[151,642,644],{"id":643},"choose-the-shape-the-consumer-wants","Choose the shape the consumer wants",[10,646,647,649],{},[14,648,265],{}," decides the structure, and the right choice depends entirely on what reads it:",[651,652,653,668],"table",{},[654,655,656],"thead",{},[657,658,659,662,665],"tr",{},[660,661,265],"th",{},[660,663,664],{},"Shape",[660,666,667],{},"Use for",[669,670,671,687,702,717,732],"tbody",{},[657,672,673,679,684],{},[674,675,676],"td",{},[14,677,678],{},"records",[674,680,681],{},[14,682,683],{},"[{col: val}, …]",[674,685,686],{},"APIs, front ends, message payloads",[657,688,689,694,699],{},[674,690,691],{},[14,692,693],{},"columns",[674,695,696],{},[14,697,698],{},"{col: {idx: val}}",[674,700,701],{},"Reconstructing a DataFrame",[657,703,704,709,714],{},[674,705,706],{},[14,707,708],{},"split",[674,710,711],{},[14,712,713],{},"{index, columns, data}",[674,715,716],{},"Compact round trips",[657,718,719,724,729],{},[674,720,721],{},[14,722,723],{},"index",[674,725,726],{},[14,727,728],{},"{idx: {col: val}}",[674,730,731],{},"Row labels that carry meaning",[657,733,734,739,744],{},[674,735,736],{},[14,737,738],{},"values",[674,740,741],{},[14,742,743],{},"[[val, …], …]",[674,745,746],{},"Plain matrices, no headers",[156,748,750],{"className":194,"code":749,"language":196,"meta":161,"style":161},"print(df.head(2).to_json(orient=\"records\", date_format=\"iso\"))\nprint(df.head(2).to_json(orient=\"split\", date_format=\"iso\"))\n",[14,751,752,780],{"__ignoreMap":161},[165,753,754,756,759,761,764,766,768,770,772,774,776,778],{"class":112,"line":167},[165,755,457],{"class":280},[165,757,758],{"class":207},"(df.head(",[165,760,281],{"class":280},[165,762,763],{"class":207},").to_json(",[165,765,265],{"class":242},[165,767,230],{"class":203},[165,769,270],{"class":174},[165,771,239],{"class":207},[165,773,286],{"class":242},[165,775,230],{"class":203},[165,777,291],{"class":174},[165,779,610],{"class":207},[165,781,782,784,786,788,790,792,794,797,799,801,803,805],{"class":112,"line":217},[165,783,457],{"class":280},[165,785,758],{"class":207},[165,787,281],{"class":280},[165,789,763],{"class":207},[165,791,265],{"class":242},[165,793,230],{"class":203},[165,795,796],{"class":174},"\"split\"",[165,798,239],{"class":207},[165,800,286],{"class":242},[165,802,230],{"class":203},[165,804,291],{"class":174},[165,806,610],{"class":207},[25,808,34,813,34,816,34,819,34,825,34,830,34,835,34,838,34,841,34,845,34,850,34,853,34,857,34,860,34,863,34,867,34,872,34,876,34,879,34,882],{"viewBox":809,"role":28,"ariaLabelledBy":810,"xmlns":32,"style":33},"14 -3 732 187",[811,812],"js2-t","js2-d",[36,814,815],{"id":811},"The same table in three JSON shapes",[40,817,818],{"id":812},"Records repeats the keys per row, columns groups values under each column name, and split stores columns and data separately for a compact round trip.",[44,820],{"x":821,"y":822,"width":823,"height":824,"fill":49},"14","-3","732","187",[51,826,829],{"x":147,"y":827,"style":828},"26","font-size:13px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","Pick by consumer, not by taste",[44,831],{"x":64,"y":65,"width":832,"height":833,"rx":834,"fill":130,"stroke":70},"222","122","12",[51,836,678],{"x":837,"y":134,"style":60},"141",[51,839,840],{"x":837,"y":66,"style":98},"[ {\"region\": \"North\",",[51,842,844],{"x":837,"y":843,"style":98},"128","\"revenue\": 128400.5} ]",[51,846,849],{"x":837,"y":847,"style":848},"152","font-size:11.5px;fill:var(--teal-ink,#0b6157);text-anchor:middle","for APIs and front ends",[44,851],{"x":852,"y":65,"width":832,"height":833,"rx":834,"fill":69,"stroke":70},"268",[51,854,693],{"x":855,"y":134,"style":856},"379","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[51,858,859],{"x":855,"y":66,"style":98},"{\"region\": {\"0\": \"North\"},",[51,861,862],{"x":855,"y":843,"style":98},"\"revenue\": {\"0\": 128400.5}}",[51,864,866],{"x":855,"y":847,"style":865},"font-size:11.5px;fill:var(--brand-strong,#4338ca);text-anchor:middle","for rebuilding a frame",[44,868],{"x":869,"y":65,"width":870,"height":833,"rx":834,"fill":871,"stroke":70},"506","224","#fdefd8",[51,873,708],{"x":874,"y":134,"style":875},"618","font-size:12.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[51,877,878],{"x":874,"y":66,"style":98},"{\"columns\": [...],",[51,880,881],{"x":874,"y":843,"style":98},"\"data\": [[...]]}",[51,883,885],{"x":874,"y":847,"style":884},"font-size:11.5px;fill:var(--gold-ink,#7a4e06);text-anchor:middle","smallest on the wire",[151,887,889],{"id":888},"convert-every-sheet-at-once","Convert every sheet at once",[156,891,893],{"className":194,"code":892,"language":196,"meta":161,"style":161},"import json\n\nimport pandas as pd\n\nsheets = pd.read_excel(\"workbook.xlsx\", sheet_name=None, engine=\"openpyxl\")\npayload = {\n    name: frame.astype(object).where(frame.notna(), None).to_dict(orient=\"records\")\n    for name, frame in sheets.items()\n}\n\nwith open(\"workbook.json\", \"w\", encoding=\"utf-8\") as fh:\n    json.dump(payload, fh, indent=2, default=str, ensure_ascii=False)\n",[14,894,895,901,905,915,919,950,960,982,996,1001,1005,1043],{"__ignoreMap":161},[165,896,897,899],{"class":112,"line":167},[165,898,204],{"class":203},[165,900,516],{"class":207},[165,902,903],{"class":112,"line":217},[165,904,221],{"emptyLinePlaceholder":220},[165,906,907,909,911,913],{"class":112,"line":224},[165,908,204],{"class":203},[165,910,208],{"class":207},[165,912,211],{"class":203},[165,914,214],{"class":207},[165,916,917],{"class":112,"line":254},[165,918,221],{"emptyLinePlaceholder":220},[165,920,921,924,926,928,931,933,936,938,940,942,944,946,948],{"class":112,"line":352},[165,922,923],{"class":207},"sheets ",[165,925,230],{"class":203},[165,927,233],{"class":207},[165,929,930],{"class":174},"\"workbook.xlsx\"",[165,932,239],{"class":207},[165,934,935],{"class":242},"sheet_name",[165,937,230],{"class":203},[165,939,569],{"class":280},[165,941,239],{"class":207},[165,943,243],{"class":242},[165,945,230],{"class":203},[165,947,248],{"class":174},[165,949,251],{"class":207},[165,951,952,955,957],{"class":112,"line":364},[165,953,954],{"class":207},"payload ",[165,956,230],{"class":203},[165,958,959],{"class":207}," {\n",[165,961,962,965,967,970,972,974,976,978,980],{"class":112,"line":400},[165,963,964],{"class":207},"    name: frame.astype(",[165,966,563],{"class":280},[165,968,969],{"class":207},").where(frame.notna(), ",[165,971,569],{"class":280},[165,973,572],{"class":207},[165,975,265],{"class":242},[165,977,230],{"class":203},[165,979,270],{"class":174},[165,981,251],{"class":207},[165,983,984,987,990,993],{"class":112,"line":417},[165,985,986],{"class":203},"    for",[165,988,989],{"class":207}," name, frame ",[165,991,992],{"class":203},"in",[165,994,995],{"class":207}," sheets.items()\n",[165,997,998],{"class":112,"line":422},[165,999,1000],{"class":207},"}\n",[165,1002,1003],{"class":112,"line":454},[165,1004,221],{"emptyLinePlaceholder":220},[165,1006,1008,1011,1014,1017,1020,1022,1025,1027,1030,1032,1035,1038,1040],{"class":112,"line":1007},11,[165,1009,1010],{"class":203},"with",[165,1012,1013],{"class":280}," open",[165,1015,1016],{"class":207},"(",[165,1018,1019],{"class":174},"\"workbook.json\"",[165,1021,239],{"class":207},[165,1023,1024],{"class":174},"\"w\"",[165,1026,239],{"class":207},[165,1028,1029],{"class":242},"encoding",[165,1031,230],{"class":203},[165,1033,1034],{"class":174},"\"utf-8\"",[165,1036,1037],{"class":207},") ",[165,1039,211],{"class":203},[165,1041,1042],{"class":207}," fh:\n",[165,1044,1046,1049,1051,1053,1055,1057,1059,1061,1063,1065,1068,1070,1073],{"class":112,"line":1045},12,[165,1047,1048],{"class":207},"    json.dump(payload, fh, ",[165,1050,275],{"class":242},[165,1052,230],{"class":203},[165,1054,281],{"class":280},[165,1056,239],{"class":207},[165,1058,602],{"class":242},[165,1060,230],{"class":203},[165,1062,607],{"class":280},[165,1064,239],{"class":207},[165,1066,1067],{"class":242},"ensure_ascii",[165,1069,230],{"class":203},[165,1071,1072],{"class":280},"False",[165,1074,251],{"class":207},[10,1076,1077,1080,1081,23],{},[14,1078,1079],{},"ensure_ascii=False"," keeps accented characters and symbols readable instead of escaping them, which matters when a person will look at the output. Reading every sheet is covered in ",[19,1082,1084],{"href":1083},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fread-all-sheets-from-an-excel-file-into-dataframes\u002F","Read all sheets from an Excel file into DataFrames",[151,1086,1088],{"id":1087},"build-a-nested-structure","Build a nested structure",[10,1090,1091],{},"Flat rows rarely match an API's schema. Group first, then shape:",[156,1093,1095],{"className":194,"code":1094,"language":196,"meta":161,"style":161},"import json\n\nimport pandas as pd\n\ndf = pd.read_excel(\"orders.xlsx\", parse_dates=[\"ordered\"])\n\npayload = [\n    {\n        \"region\": region,\n        \"order_count\": len(group),\n        \"revenue\": round(float(group[\"total\"].sum()), 2),\n        \"orders\": [\n            {\"id\": row.order_id, \"ordered\": row.ordered.date().isoformat(),\n             \"total\": float(row.total)}\n            for row in group.itertuples()\n        ],\n    }\n    for region, group in df.groupby(\"region\", sort=True)\n]\n\nprint(json.dumps(payload[:1], indent=2))\n",[14,1096,1097,1103,1107,1117,1121,1145,1149,1158,1163,1171,1184,1212,1220,1237,1250,1264,1270,1276,1304,1310,1315],{"__ignoreMap":161},[165,1098,1099,1101],{"class":112,"line":167},[165,1100,204],{"class":203},[165,1102,516],{"class":207},[165,1104,1105],{"class":112,"line":217},[165,1106,221],{"emptyLinePlaceholder":220},[165,1108,1109,1111,1113,1115],{"class":112,"line":224},[165,1110,204],{"class":203},[165,1112,208],{"class":207},[165,1114,211],{"class":203},[165,1116,214],{"class":207},[165,1118,1119],{"class":112,"line":254},[165,1120,221],{"emptyLinePlaceholder":220},[165,1122,1123,1125,1127,1129,1131,1133,1136,1138,1140,1142],{"class":112,"line":352},[165,1124,227],{"class":207},[165,1126,230],{"class":203},[165,1128,233],{"class":207},[165,1130,236],{"class":174},[165,1132,239],{"class":207},[165,1134,1135],{"class":242},"parse_dates",[165,1137,230],{"class":203},[165,1139,408],{"class":207},[165,1141,411],{"class":174},[165,1143,1144],{"class":207},"])\n",[165,1146,1147],{"class":112,"line":364},[165,1148,221],{"emptyLinePlaceholder":220},[165,1150,1151,1153,1155],{"class":112,"line":400},[165,1152,954],{"class":207},[165,1154,230],{"class":203},[165,1156,1157],{"class":207}," [\n",[165,1159,1160],{"class":112,"line":417},[165,1161,1162],{"class":207},"    {\n",[165,1164,1165,1168],{"class":112,"line":422},[165,1166,1167],{"class":174},"        \"region\"",[165,1169,1170],{"class":207},": region,\n",[165,1172,1173,1176,1178,1181],{"class":112,"line":454},[165,1174,1175],{"class":174},"        \"order_count\"",[165,1177,378],{"class":207},[165,1179,1180],{"class":280},"len",[165,1182,1183],{"class":207},"(group),\n",[165,1185,1186,1189,1191,1194,1196,1199,1202,1204,1207,1209],{"class":112,"line":1007},[165,1187,1188],{"class":174},"        \"revenue\"",[165,1190,378],{"class":207},[165,1192,1193],{"class":280},"round",[165,1195,1016],{"class":207},[165,1197,1198],{"class":280},"float",[165,1200,1201],{"class":207},"(group[",[165,1203,428],{"class":174},[165,1205,1206],{"class":207},"].sum()), ",[165,1208,281],{"class":280},[165,1210,1211],{"class":207},"),\n",[165,1213,1214,1217],{"class":112,"line":1045},[165,1215,1216],{"class":174},"        \"orders\"",[165,1218,1219],{"class":207},": [\n",[165,1221,1223,1226,1229,1232,1234],{"class":112,"line":1222},13,[165,1224,1225],{"class":207},"            {",[165,1227,1228],{"class":174},"\"id\"",[165,1230,1231],{"class":207},": row.order_id, ",[165,1233,411],{"class":174},[165,1235,1236],{"class":207},": row.ordered.date().isoformat(),\n",[165,1238,1240,1243,1245,1247],{"class":112,"line":1239},14,[165,1241,1242],{"class":174},"             \"total\"",[165,1244,378],{"class":207},[165,1246,1198],{"class":280},[165,1248,1249],{"class":207},"(row.total)}\n",[165,1251,1253,1256,1259,1261],{"class":112,"line":1252},15,[165,1254,1255],{"class":203},"            for",[165,1257,1258],{"class":207}," row ",[165,1260,992],{"class":203},[165,1262,1263],{"class":207}," group.itertuples()\n",[165,1265,1267],{"class":112,"line":1266},16,[165,1268,1269],{"class":207},"        ],\n",[165,1271,1273],{"class":112,"line":1272},17,[165,1274,1275],{"class":207},"    }\n",[165,1277,1279,1281,1284,1286,1289,1292,1294,1297,1299,1302],{"class":112,"line":1278},18,[165,1280,986],{"class":203},[165,1282,1283],{"class":207}," region, group ",[165,1285,992],{"class":203},[165,1287,1288],{"class":207}," df.groupby(",[165,1290,1291],{"class":174},"\"region\"",[165,1293,239],{"class":207},[165,1295,1296],{"class":242},"sort",[165,1298,230],{"class":203},[165,1300,1301],{"class":280},"True",[165,1303,251],{"class":207},[165,1305,1307],{"class":112,"line":1306},19,[165,1308,1309],{"class":207},"]\n",[165,1311,1313],{"class":112,"line":1312},20,[165,1314,221],{"emptyLinePlaceholder":220},[165,1316,1318,1320,1323,1326,1328,1330,1332,1334],{"class":112,"line":1317},21,[165,1319,457],{"class":280},[165,1321,1322],{"class":207},"(json.dumps(payload[:",[165,1324,1325],{"class":280},"1",[165,1327,441],{"class":207},[165,1329,275],{"class":242},[165,1331,230],{"class":203},[165,1333,281],{"class":280},[165,1335,610],{"class":207},[10,1337,1338,1341,1342,1345],{},[14,1339,1340],{},"itertuples()"," is the fast iteration path — several times quicker than ",[14,1343,1344],{},"iterrows()"," — and matters once the sheet has more than a few thousand rows.",[151,1347,1349],{"id":1348},"stream-large-sheets-as-json-lines","Stream large sheets as JSON Lines",[10,1351,1352],{},"One giant array must be parsed in full before anything can be read. JSON Lines — one object per line — streams, appends and splits:",[156,1354,1356],{"className":194,"code":1355,"language":196,"meta":161,"style":161},"import pandas as pd\n\ndf = pd.read_excel(\"large.xlsx\", engine=\"calamine\")\ndf.to_json(\"large.jsonl\", orient=\"records\", lines=True, date_format=\"iso\")\n",[14,1357,1358,1368,1372,1394],{"__ignoreMap":161},[165,1359,1360,1362,1364,1366],{"class":112,"line":167},[165,1361,204],{"class":203},[165,1363,208],{"class":207},[165,1365,211],{"class":203},[165,1367,214],{"class":207},[165,1369,1370],{"class":112,"line":217},[165,1371,221],{"emptyLinePlaceholder":220},[165,1373,1374,1376,1378,1380,1383,1385,1387,1389,1392],{"class":112,"line":224},[165,1375,227],{"class":207},[165,1377,230],{"class":203},[165,1379,233],{"class":207},[165,1381,1382],{"class":174},"\"large.xlsx\"",[165,1384,239],{"class":207},[165,1386,243],{"class":242},[165,1388,230],{"class":203},[165,1390,1391],{"class":174},"\"calamine\"",[165,1393,251],{"class":207},[165,1395,1396,1398,1401,1403,1405,1407,1409,1411,1414,1416,1418,1420,1422,1424,1426],{"class":112,"line":254},[165,1397,257],{"class":207},[165,1399,1400],{"class":174},"\"large.jsonl\"",[165,1402,239],{"class":207},[165,1404,265],{"class":242},[165,1406,230],{"class":203},[165,1408,270],{"class":174},[165,1410,239],{"class":207},[165,1412,1413],{"class":242},"lines",[165,1415,230],{"class":203},[165,1417,1301],{"class":280},[165,1419,239],{"class":207},[165,1421,286],{"class":242},[165,1423,230],{"class":203},[165,1425,291],{"class":174},[165,1427,251],{"class":207},[10,1429,1430],{},"Read it back a line at a time, so memory stays flat regardless of file size:",[156,1432,1434],{"className":194,"code":1433,"language":196,"meta":161,"style":161},"import json\n\nwith open(\"large.jsonl\", encoding=\"utf-8\") as fh:\n    for line in fh:\n        record = json.loads(line)\n        handle(record)\n",[14,1435,1436,1442,1446,1470,1481,1491],{"__ignoreMap":161},[165,1437,1438,1440],{"class":112,"line":167},[165,1439,204],{"class":203},[165,1441,516],{"class":207},[165,1443,1444],{"class":112,"line":217},[165,1445,221],{"emptyLinePlaceholder":220},[165,1447,1448,1450,1452,1454,1456,1458,1460,1462,1464,1466,1468],{"class":112,"line":224},[165,1449,1010],{"class":203},[165,1451,1013],{"class":280},[165,1453,1016],{"class":207},[165,1455,1400],{"class":174},[165,1457,239],{"class":207},[165,1459,1029],{"class":242},[165,1461,230],{"class":203},[165,1463,1034],{"class":174},[165,1465,1037],{"class":207},[165,1467,211],{"class":203},[165,1469,1042],{"class":207},[165,1471,1472,1474,1477,1479],{"class":112,"line":254},[165,1473,986],{"class":203},[165,1475,1476],{"class":207}," line ",[165,1478,992],{"class":203},[165,1480,1042],{"class":207},[165,1482,1483,1486,1488],{"class":112,"line":352},[165,1484,1485],{"class":207},"        record ",[165,1487,230],{"class":203},[165,1489,1490],{"class":207}," json.loads(line)\n",[165,1492,1493],{"class":112,"line":364},[165,1494,1495],{"class":207},"        handle(record)\n",[10,1497,1498],{},"Every mainstream data tool reads this format, and it is what most log and event pipelines expect.",[151,1500,1502],{"id":1501},"validate-the-output-before-shipping-it","Validate the output before shipping it",[10,1504,1505],{},"If the JSON feeds an API, check it against the contract rather than discovering the mismatch downstream:",[156,1507,1509],{"className":194,"code":1508,"language":196,"meta":161,"style":161},"from datetime import date\n\nREQUIRED = {\"order_id\": str, \"ordered\": str, \"total\": float}\n\ndef validate(records: list[dict]) -> None:\n    for i, record in enumerate(records):\n        missing = REQUIRED.keys() - record.keys()\n        if missing:\n            raise ValueError(f\"row {i}: missing {sorted(missing)}\")\n        for key, kind in REQUIRED.items():\n            value = record[key]\n            if value is not None and not isinstance(value, kind):\n                raise TypeError(f\"row {i}: {key} is {type(value).__name__}, expected {kind.__name__}\")\n",[14,1510,1511,1524,1528,1563,1567,1590,1605,1624,1632,1675,1690,1700,1728],{"__ignoreMap":161},[165,1512,1513,1516,1519,1521],{"class":112,"line":167},[165,1514,1515],{"class":203},"from",[165,1517,1518],{"class":207}," datetime ",[165,1520,204],{"class":203},[165,1522,1523],{"class":207}," date\n",[165,1525,1526],{"class":112,"line":217},[165,1527,221],{"emptyLinePlaceholder":220},[165,1529,1530,1533,1536,1539,1541,1543,1545,1547,1549,1551,1553,1555,1557,1559,1561],{"class":112,"line":224},[165,1531,1532],{"class":280},"REQUIRED",[165,1534,1535],{"class":203}," =",[165,1537,1538],{"class":207}," {",[165,1540,375],{"class":174},[165,1542,378],{"class":207},[165,1544,607],{"class":280},[165,1546,239],{"class":207},[165,1548,411],{"class":174},[165,1550,378],{"class":207},[165,1552,607],{"class":280},[165,1554,239],{"class":207},[165,1556,428],{"class":174},[165,1558,378],{"class":207},[165,1560,1198],{"class":280},[165,1562,1000],{"class":207},[165,1564,1565],{"class":112,"line":254},[165,1566,221],{"emptyLinePlaceholder":220},[165,1568,1569,1572,1576,1579,1582,1585,1587],{"class":112,"line":352},[165,1570,1571],{"class":203},"def",[165,1573,1575],{"class":1574},"s_Opv"," validate",[165,1577,1578],{"class":207},"(records: list[",[165,1580,1581],{"class":280},"dict",[165,1583,1584],{"class":207},"]) -> ",[165,1586,569],{"class":280},[165,1588,1589],{"class":207},":\n",[165,1591,1592,1594,1597,1599,1602],{"class":112,"line":364},[165,1593,986],{"class":203},[165,1595,1596],{"class":207}," i, record ",[165,1598,992],{"class":203},[165,1600,1601],{"class":280}," enumerate",[165,1603,1604],{"class":207},"(records):\n",[165,1606,1607,1610,1612,1615,1618,1621],{"class":112,"line":400},[165,1608,1609],{"class":207},"        missing ",[165,1611,230],{"class":203},[165,1613,1614],{"class":280}," REQUIRED",[165,1616,1617],{"class":207},".keys() ",[165,1619,1620],{"class":203},"-",[165,1622,1623],{"class":207}," record.keys()\n",[165,1625,1626,1629],{"class":112,"line":417},[165,1627,1628],{"class":203},"        if",[165,1630,1631],{"class":207}," missing:\n",[165,1633,1634,1637,1640,1642,1645,1648,1651,1654,1657,1660,1662,1665,1668,1670,1673],{"class":112,"line":422},[165,1635,1636],{"class":203},"            raise",[165,1638,1639],{"class":280}," ValueError",[165,1641,1016],{"class":207},[165,1643,1644],{"class":203},"f",[165,1646,1647],{"class":174},"\"row ",[165,1649,372],{"class":1650},"sSjpA",[165,1652,1653],{"class":207},"i",[165,1655,1656],{"class":1650},"}",[165,1658,1659],{"class":174},": missing ",[165,1661,372],{"class":1650},[165,1663,1664],{"class":280},"sorted",[165,1666,1667],{"class":207},"(missing)",[165,1669,1656],{"class":1650},[165,1671,1672],{"class":174},"\"",[165,1674,251],{"class":207},[165,1676,1677,1680,1683,1685,1687],{"class":112,"line":454},[165,1678,1679],{"class":203},"        for",[165,1681,1682],{"class":207}," key, kind ",[165,1684,992],{"class":203},[165,1686,1614],{"class":280},[165,1688,1689],{"class":207},".items():\n",[165,1691,1692,1695,1697],{"class":112,"line":1007},[165,1693,1694],{"class":207},"            value ",[165,1696,230],{"class":203},[165,1698,1699],{"class":207}," record[key]\n",[165,1701,1702,1705,1708,1711,1714,1717,1720,1722,1725],{"class":112,"line":1045},[165,1703,1704],{"class":203},"            if",[165,1706,1707],{"class":207}," value ",[165,1709,1710],{"class":203},"is",[165,1712,1713],{"class":203}," not",[165,1715,1716],{"class":280}," None",[165,1718,1719],{"class":203}," and",[165,1721,1713],{"class":203},[165,1723,1724],{"class":280}," isinstance",[165,1726,1727],{"class":207},"(value, kind):\n",[165,1729,1730,1733,1736,1738,1740,1742,1744,1746,1748,1750,1752,1755,1757,1760,1762,1765,1768,1771,1773,1776,1778,1781,1783,1785,1787],{"class":112,"line":1222},[165,1731,1732],{"class":203},"                raise",[165,1734,1735],{"class":280}," TypeError",[165,1737,1016],{"class":207},[165,1739,1644],{"class":203},[165,1741,1647],{"class":174},[165,1743,372],{"class":1650},[165,1745,1653],{"class":207},[165,1747,1656],{"class":1650},[165,1749,378],{"class":174},[165,1751,372],{"class":1650},[165,1753,1754],{"class":207},"key",[165,1756,1656],{"class":1650},[165,1758,1759],{"class":174}," is ",[165,1761,372],{"class":1650},[165,1763,1764],{"class":280},"type",[165,1766,1767],{"class":207},"(value).",[165,1769,1770],{"class":280},"__name__",[165,1772,1656],{"class":1650},[165,1774,1775],{"class":174},", expected ",[165,1777,372],{"class":1650},[165,1779,1780],{"class":207},"kind.",[165,1782,1770],{"class":280},[165,1784,1656],{"class":1650},[165,1786,1672],{"class":174},[165,1788,251],{"class":207},[10,1790,1791,1792,23],{},"For anything more elaborate, a schema library does this properly — see ",[19,1793,1795],{"href":1794},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fvalidate-excel-data-with-pandera-schemas\u002F","Validate Excel data with pandera schemas",[151,1797,1799],{"id":1798},"convert-back-and-check-the-round-trip","Convert back, and check the round trip",[10,1801,1802],{},"The reverse direction matters as much, because a service that accepts JSON and returns a workbook is the same pipeline pointed the other way:",[156,1804,1806],{"className":194,"code":1805,"language":196,"meta":161,"style":161},"import json\n\nimport pandas as pd\n\nwith open(\"orders.json\", encoding=\"utf-8\") as fh:\n    records = json.load(fh)\n\ndf = pd.json_normalize(records)          # flattens nested objects into columns\ndf.to_excel(\"orders_roundtrip.xlsx\", index=False, engine=\"xlsxwriter\")\n",[14,1807,1808,1814,1818,1828,1832,1856,1866,1870,1882],{"__ignoreMap":161},[165,1809,1810,1812],{"class":112,"line":167},[165,1811,204],{"class":203},[165,1813,516],{"class":207},[165,1815,1816],{"class":112,"line":217},[165,1817,221],{"emptyLinePlaceholder":220},[165,1819,1820,1822,1824,1826],{"class":112,"line":224},[165,1821,204],{"class":203},[165,1823,208],{"class":207},[165,1825,211],{"class":203},[165,1827,214],{"class":207},[165,1829,1830],{"class":112,"line":254},[165,1831,221],{"emptyLinePlaceholder":220},[165,1833,1834,1836,1838,1840,1842,1844,1846,1848,1850,1852,1854],{"class":112,"line":352},[165,1835,1010],{"class":203},[165,1837,1013],{"class":280},[165,1839,1016],{"class":207},[165,1841,260],{"class":174},[165,1843,239],{"class":207},[165,1845,1029],{"class":242},[165,1847,230],{"class":203},[165,1849,1034],{"class":174},[165,1851,1037],{"class":207},[165,1853,211],{"class":203},[165,1855,1042],{"class":207},[165,1857,1858,1861,1863],{"class":112,"line":364},[165,1859,1860],{"class":207},"    records ",[165,1862,230],{"class":203},[165,1864,1865],{"class":207}," json.load(fh)\n",[165,1867,1868],{"class":112,"line":400},[165,1869,221],{"emptyLinePlaceholder":220},[165,1871,1872,1874,1876,1879],{"class":112,"line":417},[165,1873,227],{"class":207},[165,1875,230],{"class":203},[165,1877,1878],{"class":207}," pd.json_normalize(records)          ",[165,1880,1881],{"class":396},"# flattens nested objects into columns\n",[165,1883,1884,1887,1890,1892,1894,1896,1898,1900,1902,1904,1907],{"class":112,"line":422},[165,1885,1886],{"class":207},"df.to_excel(",[165,1888,1889],{"class":174},"\"orders_roundtrip.xlsx\"",[165,1891,239],{"class":207},[165,1893,723],{"class":242},[165,1895,230],{"class":203},[165,1897,1072],{"class":280},[165,1899,239],{"class":207},[165,1901,243],{"class":242},[165,1903,230],{"class":203},[165,1905,1906],{"class":174},"\"xlsxwriter\"",[165,1908,251],{"class":207},[10,1910,1911,1914,1915,1918,1919,1922,1923,1926,1927,1930],{},[14,1912,1913],{},"json_normalize"," is the piece that handles nesting: an object like ",[14,1916,1917],{},"{\"customer\": {\"name\": \"Acme\"}}"," becomes a ",[14,1920,1921],{},"customer.name"," column rather than a cell containing a Python dictionary repr. Pass ",[14,1924,1925],{},"record_path"," and ",[14,1928,1929],{},"meta"," when the payload has a list inside each object — for example orders inside regions — and it explodes them into rows while carrying the parent fields along.",[10,1932,1933],{},"A round-trip check is the cheapest way to prove the conversion is lossless for the fields you care about:",[156,1935,1937],{"className":194,"code":1936,"language":196,"meta":161,"style":161},"original = pd.read_excel(\"orders.xlsx\", dtype={\"order_id\": \"string\"})\nrestored = pd.read_excel(\"orders_roundtrip.xlsx\", dtype={\"order_id\": \"string\"})\n\nassert list(original.columns) == list(restored.columns)\nassert original[\"order_id\"].tolist() == restored[\"order_id\"].tolist()\nassert abs(original[\"total\"].sum() - restored[\"total\"].sum()) \u003C 0.01\n",[14,1938,1939,1967,1994,1998,2017,2039],{"__ignoreMap":161},[165,1940,1941,1944,1946,1948,1950,1952,1954,1956,1958,1960,1962,1964],{"class":112,"line":167},[165,1942,1943],{"class":207},"original ",[165,1945,230],{"class":203},[165,1947,233],{"class":207},[165,1949,236],{"class":174},[165,1951,239],{"class":207},[165,1953,466],{"class":242},[165,1955,230],{"class":203},[165,1957,372],{"class":207},[165,1959,375],{"class":174},[165,1961,378],{"class":207},[165,1963,381],{"class":174},[165,1965,1966],{"class":207},"})\n",[165,1968,1969,1972,1974,1976,1978,1980,1982,1984,1986,1988,1990,1992],{"class":112,"line":217},[165,1970,1971],{"class":207},"restored ",[165,1973,230],{"class":203},[165,1975,233],{"class":207},[165,1977,1889],{"class":174},[165,1979,239],{"class":207},[165,1981,466],{"class":242},[165,1983,230],{"class":203},[165,1985,372],{"class":207},[165,1987,375],{"class":174},[165,1989,378],{"class":207},[165,1991,381],{"class":174},[165,1993,1966],{"class":207},[165,1995,1996],{"class":112,"line":224},[165,1997,221],{"emptyLinePlaceholder":220},[165,1999,2000,2003,2006,2009,2012,2014],{"class":112,"line":254},[165,2001,2002],{"class":203},"assert",[165,2004,2005],{"class":280}," list",[165,2007,2008],{"class":207},"(original.columns) ",[165,2010,2011],{"class":203},"==",[165,2013,2005],{"class":280},[165,2015,2016],{"class":207},"(restored.columns)\n",[165,2018,2019,2021,2024,2026,2029,2031,2034,2036],{"class":112,"line":352},[165,2020,2002],{"class":203},[165,2022,2023],{"class":207}," original[",[165,2025,375],{"class":174},[165,2027,2028],{"class":207},"].tolist() ",[165,2030,2011],{"class":203},[165,2032,2033],{"class":207}," restored[",[165,2035,375],{"class":174},[165,2037,2038],{"class":207},"].tolist()\n",[165,2040,2041,2043,2046,2049,2051,2054,2056,2058,2060,2063,2066],{"class":112,"line":364},[165,2042,2002],{"class":203},[165,2044,2045],{"class":280}," abs",[165,2047,2048],{"class":207},"(original[",[165,2050,428],{"class":174},[165,2052,2053],{"class":207},"].sum() ",[165,2055,1620],{"class":203},[165,2057,2033],{"class":207},[165,2059,428],{"class":174},[165,2061,2062],{"class":207},"].sum()) ",[165,2064,2065],{"class":203},"\u003C",[165,2067,2068],{"class":280}," 0.01\n",[25,2070,34,2074,34,2077,34,2080,34,2082,34,2085,34,2089,34,2093,34,2096,34,2099,34,2103,34,2107,34,2112,34,2114,34,2117],{"viewBox":809,"role":28,"ariaLabelledBy":2071,"xmlns":32,"style":33},[2072,2073],"js3-t","js3-d",[36,2075,2076],{"id":2072},"What survives a workbook to JSON round trip",[40,2078,2079],{"id":2073},"Values, column names and declared types survive the trip, while cell formatting, formulas, charts and merged regions have no JSON representation and are lost.",[44,2081],{"x":821,"y":822,"width":823,"height":824,"fill":49},[51,2083,2084],{"x":53,"y":54,"style":60},"survives",[51,2086,2088],{"x":59,"y":54,"style":2087},"font-size:12.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","lost",[44,2090],{"x":64,"y":65,"width":2091,"height":67,"rx":2092,"fill":130,"stroke":70},"332","8",[51,2094,2095],{"x":53,"y":74,"style":135},"cell values and column names",[44,2097],{"x":64,"y":2098,"width":2091,"height":67,"rx":2092,"fill":130,"stroke":70},"94",[51,2100,2102],{"x":53,"y":2101,"style":135},"119","types you declared at the read",[44,2104],{"x":2105,"y":65,"width":2091,"height":67,"rx":2092,"fill":2106,"stroke":70},"398","#fee8f2",[51,2108,2111],{"x":2109,"y":74,"style":2110},"564","font-size:12px;fill:var(--accent-ink,#be185d);text-anchor:middle","formats, colours, column widths",[44,2113],{"x":2105,"y":2098,"width":2091,"height":67,"rx":2092,"fill":2106,"stroke":70},[51,2115,2116],{"x":2109,"y":2101,"style":2110},"formulas, charts, merged cells",[51,2118,2120],{"x":147,"y":2119,"style":109},"172","JSON carries data, not presentation — rebuild the styling on the way out",[151,2122,2124],{"id":2123},"keep-column-names-stable","Keep column names stable",[10,2126,2127],{},"JSON keys are an interface. A header that reads \"Total (GBP)\" in the spreadsheet becomes an awkward key, and it changes the moment somebody edits the header text. Map the sheet's headers to fixed key names rather than passing them through:",[156,2129,2131],{"className":194,"code":2130,"language":196,"meta":161,"style":161},"KEYS = {\n    \"Order ID\": \"order_id\",\n    \"Ordered\": \"ordered\",\n    \"Total (GBP)\": \"total\",\n    \"Customer name\": \"customer_name\",\n}\n\nmissing = KEYS.keys() - set(df.columns)\nif missing:\n    raise ValueError(f\"sheet is missing expected headers: {sorted(missing)}\")\n\ndf = df.rename(columns=KEYS)[list(KEYS.values())]\n",[14,2132,2133,2142,2153,2164,2175,2187,2191,2195,2215,2222,2248,2252],{"__ignoreMap":161},[165,2134,2135,2138,2140],{"class":112,"line":167},[165,2136,2137],{"class":280},"KEYS",[165,2139,1535],{"class":203},[165,2141,959],{"class":207},[165,2143,2144,2147,2149,2151],{"class":112,"line":217},[165,2145,2146],{"class":174},"    \"Order ID\"",[165,2148,378],{"class":207},[165,2150,375],{"class":174},[165,2152,349],{"class":207},[165,2154,2155,2158,2160,2162],{"class":112,"line":224},[165,2156,2157],{"class":174},"    \"Ordered\"",[165,2159,378],{"class":207},[165,2161,411],{"class":174},[165,2163,349],{"class":207},[165,2165,2166,2169,2171,2173],{"class":112,"line":254},[165,2167,2168],{"class":174},"    \"Total (GBP)\"",[165,2170,378],{"class":207},[165,2172,428],{"class":174},[165,2174,349],{"class":207},[165,2176,2177,2180,2182,2185],{"class":112,"line":352},[165,2178,2179],{"class":174},"    \"Customer name\"",[165,2181,378],{"class":207},[165,2183,2184],{"class":174},"\"customer_name\"",[165,2186,349],{"class":207},[165,2188,2189],{"class":112,"line":364},[165,2190,1000],{"class":207},[165,2192,2193],{"class":112,"line":400},[165,2194,221],{"emptyLinePlaceholder":220},[165,2196,2197,2200,2202,2205,2207,2209,2212],{"class":112,"line":417},[165,2198,2199],{"class":207},"missing ",[165,2201,230],{"class":203},[165,2203,2204],{"class":280}," KEYS",[165,2206,1617],{"class":207},[165,2208,1620],{"class":203},[165,2210,2211],{"class":280}," set",[165,2213,2214],{"class":207},"(df.columns)\n",[165,2216,2217,2220],{"class":112,"line":422},[165,2218,2219],{"class":203},"if",[165,2221,1631],{"class":207},[165,2223,2224,2227,2229,2231,2233,2236,2238,2240,2242,2244,2246],{"class":112,"line":454},[165,2225,2226],{"class":203},"    raise",[165,2228,1639],{"class":280},[165,2230,1016],{"class":207},[165,2232,1644],{"class":203},[165,2234,2235],{"class":174},"\"sheet is missing expected headers: ",[165,2237,372],{"class":1650},[165,2239,1664],{"class":280},[165,2241,1667],{"class":207},[165,2243,1656],{"class":1650},[165,2245,1672],{"class":174},[165,2247,251],{"class":207},[165,2249,2250],{"class":112,"line":1007},[165,2251,221],{"emptyLinePlaceholder":220},[165,2253,2254,2256,2258,2261,2263,2265,2267,2270,2273,2275,2277],{"class":112,"line":1045},[165,2255,227],{"class":207},[165,2257,230],{"class":203},[165,2259,2260],{"class":207}," df.rename(",[165,2262,693],{"class":242},[165,2264,230],{"class":203},[165,2266,2137],{"class":280},[165,2268,2269],{"class":207},")[",[165,2271,2272],{"class":280},"list",[165,2274,1016],{"class":207},[165,2276,2137],{"class":280},[165,2278,2279],{"class":207},".values())]\n",[10,2281,2282],{},"The check before the rename is what turns a silent break into a clear error: if a producer relabels a column, the export fails with the header name rather than shipping JSON with a missing field that a consumer discovers days later.",[151,2284,2286],{"id":2285},"common-pitfalls-and-gotchas","Common pitfalls and gotchas",[2288,2289,2290,2301,2318,2328,2344],"ul",{},[2291,2292,2293,2297,2298,2300],"li",{},[2294,2295,2296],"strong",{},"Epoch-millisecond dates."," Always pass ",[14,2299,302],{}," unless the consumer genuinely wants numbers.",[2291,2302,2303,2308,2309,2311,2312,2314,2315,2317],{},[2294,2304,2305,2307],{},[14,2306,16],{}," in hand-built JSON."," Replace with ",[14,2310,569],{}," before ",[14,2313,500],{},"; ",[14,2316,16],{}," is not valid JSON.",[2291,2319,2320,2323,2324,2327],{},[2294,2321,2322],{},"Lost leading zeros."," Set ",[14,2325,2326],{},"dtype=\"string\""," on identifier columns at the read.",[2291,2329,2330,2337,2338,2340,2341,2343],{},[2294,2331,2332,1926,2334,2336],{},[14,2333,633],{},[14,2335,637],{}," objects."," ",[14,2339,500],{}," cannot serialise them; pass ",[14,2342,625],{}," or convert explicitly.",[2291,2345,2346,2349,2350,239,2353,2356],{},[2294,2347,2348],{},"Duplicate column headers."," pandas renames them ",[14,2351,2352],{},"col",[14,2354,2355],{},"col.1","; JSON objects cannot hold duplicate keys either, so decide the naming deliberately.",[151,2358,2360],{"id":2359},"performance-and-scale-notes","Performance and scale notes",[10,2362,2363,2364,2367,2368,2370,2371,2373,2374,23],{},"The Excel parse dominates, so use ",[14,2365,2366],{},"engine=\"calamine\""," for large inputs. Beyond that, ",[14,2369,190],{}," is fast and ",[14,2372,500],{}," over a list of dictionaries is noticeably slower — prefer the pandas path unless you need custom shaping. For files above a few hundred thousand rows, write JSON Lines and process it in a stream; a single array of that size costs several times the file's size in memory when parsed. If the destination is analytical rather than an API, Parquet is a better target than JSON in every dimension — smaller, typed and faster — as covered in ",[19,2375,2377],{"href":2376},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fconvert-excel-files-to-parquet-with-python\u002F","Convert Excel files to Parquet with Python",[151,2379,2381],{"id":2380},"conclusion","Conclusion",[10,2383,2384,2385,2387,2388,2390],{},"A clean Excel-to-JSON conversion is mostly about the read: declare the types, parse the dates, and replace missing values with ",[14,2386,569],{},". Then pick the ",[14,2389,265],{}," that matches the consumer, use ISO dates, and switch to JSON Lines once the file is large. Validate the result against the contract it has to satisfy, and the conversion stops being a source of downstream surprises.",[151,2392,2394],{"id":2393},"frequently-asked-questions","Frequently asked questions",[10,2396,2397,2400,2402,2403,2405,2406,2408,2409,2411],{},[2294,2398,2399],{},"Which orient should I use?",[14,2401,678],{}," for almost every API — a list of objects, one per row. ",[14,2404,693],{}," or ",[14,2407,708],{}," are useful when the consumer is another DataFrame, and ",[14,2410,723],{}," only when row labels carry meaning.",[10,2413,2414,2417,2418,2420],{},[2294,2415,2416],{},"Why do my dates come out as huge numbers?","\nThose are epoch milliseconds, pandas' default for datetimes in JSON. Pass ",[14,2419,302],{}," to get ISO 8601 strings, which is what almost every consumer expects.",[10,2422,2423,2426,2427,2429,2430,2432,2433,2435,2436,2438,2439,501,2441,2443],{},[2294,2424,2425],{},"How do I get null instead of NaN?","\nConvert with ",[14,2428,190],{},", which writes ",[14,2431,496],{}," automatically. If you build the structure yourself, replace ",[14,2434,16],{}," with ",[14,2437,569],{}," first — ",[14,2440,500],{},[14,2442,16],{}," otherwise, and that is not valid JSON.",[10,2445,2446,2449,2450,2453],{},[2294,2447,2448],{},"How do I convert every sheet in one go?","\nRead with ",[14,2451,2452],{},"sheet_name=None"," to get a dictionary of DataFrames, then build a dictionary of records keyed by sheet name and dump it once.",[10,2455,2456,2459],{},[2294,2457,2458],{},"What about a sheet with a million rows?","\nWrite JSON Lines — one object per line — instead of one array. It streams, it can be appended to, and every big-data tool reads it.",[151,2461,2463],{"id":2462},"related","Related",[2288,2465,2466,2474,2481,2488,2495],{},[2291,2467,2468,2337,2471,2473],{},[2294,2469,2470],{},"Up:",[19,2472,22],{"href":21}," — the other conversions in and out of spreadsheet formats.",[2291,2475,2476,2480],{},[19,2477,2479],{"href":2478},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fconvert-excel-to-csv-with-python\u002F","Convert Excel to CSV with Python"," — the flat-text equivalent, and when it is the better target.",[2291,2482,2483,2487],{},[19,2484,2486],{"href":2485},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-an-excel-file-from-a-url-or-bytes-in-python\u002F","Read an Excel file from a URL or bytes in Python"," — getting the workbook in when it arrives over HTTP.",[2291,2489,2490,2494],{},[19,2491,2493],{"href":2492},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Ffetch-api-data-into-excel-with-python-requests\u002F","Fetch API data into Excel with Python requests"," — the same trip in the opposite direction.",[2291,2496,2497,2499],{},[19,2498,2377],{"href":2376}," — the better destination when the consumer is analytical.",[2501,2502,2503],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":161,"searchDepth":217,"depth":217,"links":2505},[2506,2507,2508,2509,2510,2511,2512,2513,2514,2515,2516,2517,2518,2519,2520,2521],{"id":153,"depth":217,"text":154},{"id":184,"depth":217,"text":185},{"id":306,"depth":217,"text":307},{"id":483,"depth":217,"text":484},{"id":643,"depth":217,"text":644},{"id":888,"depth":217,"text":889},{"id":1087,"depth":217,"text":1088},{"id":1348,"depth":217,"text":1349},{"id":1501,"depth":217,"text":1502},{"id":1798,"depth":217,"text":1799},{"id":2123,"depth":217,"text":2124},{"id":2285,"depth":217,"text":2286},{"id":2359,"depth":217,"text":2360},{"id":2380,"depth":217,"text":2381},{"id":2393,"depth":217,"text":2394},{"id":2462,"depth":217,"text":2463},"2026-08-27","Turn a worksheet into JSON: records versus columns, dates and NaN handling, nested output, every sheet at once, JSON Lines for large files, and validating the result.","md",[2526,2528,2530,2532,2534],{"q":2399,"a":2527},"records for almost every API — a list of objects, one per row. columns or split are useful when the consumer is another DataFrame, and index only when row labels carry meaning.",{"q":2416,"a":2529},"Those are epoch milliseconds, pandas' default for datetimes in JSON. Pass date_format=\"iso\" to get ISO 8601 strings, which is what almost every consumer expects.",{"q":2425,"a":2531},"Convert with to_json, which writes null automatically. If you build the structure yourself, replace NaN with None first — json.dumps writes the literal NaN otherwise, and that is not valid JSON.",{"q":2448,"a":2533},"Read with sheet_name=None to get a dictionary of DataFrames, then build a dictionary of records keyed by sheet name and dump it once.",{"q":2458,"a":2535},"Write JSON Lines — one object per line — instead of one array. It streams, it can be appended to, and every big-data tool reads it.",{},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-an-excel-sheet-to-json-with-python",{"title":2539,"description":2540},"Excel to JSON with Python","Convert xlsx to JSON with pandas or openpyxl: orient options, ISO dates, null handling for NaN, nested structures, JSON Lines for big sheets, and schema validation.","convert-an-excel-sheet-to-json-with-python","getting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-an-excel-sheet-to-json-with-python\u002Findex","how-to","bNU94lfpJKo4qFOCHhIZ_Gk8ut4tndstgpvcvhFMm2U",[2546,2549],{"title":22,"path":2547,"stem":2548,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions","getting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Findex",{"title":2550,"path":2551,"stem":2552,"children":-1},"Convert .xls to .xlsx with Python","\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-xls-to-xlsx-with-python","getting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-xls-to-xlsx-with-python\u002Findex",1788710159589]