Skip to main content

EDJAS: Extract Data in JSON from Any Spreadsheet

This project is an attempt to help organisations that insist on managing their businesses, or major aspects thereof, using spreadsheets. Many articles have been written on the limitations of spreadsheet technology. If you have any doubts then look at the "The Problem with Spreadsheets" section of this LinkedIn article. Some large organisations are now providing advice — although in many cases better advice might be: stop using spreadsheets for that!

Rather than try to change the way people do business (imagine "If I Ruled the World" playing softly in the background), EDJAS is intended to help people extract that locked-up data more effectively, in simple and easy-to-understand ways that don't affect existing workflows.

EDJAS leaves your spreadsheet completely untouched. Instead of adding anything to the workbook, you write a small specification file — a TOML document — describing what to extract. One spec can serve many workbooks, and one workbook can have many specs, each tailored to a different audience.

The specification file

A spec is a TOML file with an [extract] table mapping the output keys you want to the values to pull from the spreadsheet:

[extract]
title  = "Summary!B2"                    # a single cell   -> scalar
hours  = "{Hours}"                        # a 2-column range -> object
prices = "{Prices | int}"                 # object, values coerced to int
sales  = "[Sales | records]"              # a table -> list of objects
people = "[Grid | transpose | records]"   # a pipeline of transforms

Each value is an extraction expression. There are three forms:

  • ref (a plain reference) — the value of a single cell.
  • [ref] — the range as a JSON list, or a list of row-lists if it is two-dimensional.
  • {ref} — a two-column range as a JSON object (left column names, right column values).

A ref is either a named range or an A1-style cell range (D3:E9), optionally qualified with a sheet name (Summary!D3:E9). Named ranges are recommended: they survive layout changes, whereas bare cell references do not.

A worked example

The repository ships a sample workbook and a spec exercising every construct described here. Run them together:

edjas examples/example.xlsx examples/example.toml

examples/example.xlsx is a small café report spread over four sheets:

Named range Where Shape
Title, PeriodEnd, AvgSpend Summary!B2:B4 single cells
Hours, Prices, Covers, Codes Data two-column name/value ranges
Tags Data!M1:M3 a single column
Sales Sales!A1:C4 a table with a heading row
Staff Staff!A1:C2 one field per row, so it needs transposing

The three forms, against that workbook (comments show the JSON produced):

title = "Title"      # "Riverside Cafe"
tags  = "[Tags]"     # ["Vegan", "Gluten-free", "Dairy-free"]
hours = "{Hours}"    # {"Monday": "07:00-20:00", ... "Sunday": "Closed"}

and the same three ways of writing a reference:

title_again = "B2"             # an A1 cell on the active sheet
prices      = "{Data!D1:E3}"   # a sheet-qualified A1 range
tags        = "[Tags]"         # a named range

Here's a dump of the spreadsheet with the named ranges marked in the same colors as the referenced cells.

img The Example Workbook

Transforming values with functions

Any expression may append a pipeline of functions, separated by |, applied left to right after extraction — so [Grid | transpose | records] transposes the range, then builds objects from it. Functions come from a fixed, built-in registry (no arbitrary code runs). The built-ins:

Function Typical input Result
records [table] first row is headings; remaining rows become a list of objects
columns [table] first row is headings; columns become a {heading: [values]} object
transpose [table] swaps rows and columns
flatten [table] flattens nested rows into a single list
keys / values / items {object} the object's keys, values, or [key, value] pairs
invert {object} swaps keys and values
int / float / str any coerces every value to that type
round any rounds every floating-point value; takes the number of decimal places (default 2)
isodate any formats date/time values as ISO-8601 strings

Each of them against the sample workbook, with the JSON they produce:

sales_rows       = "[Sales]"                       # [["Region","Q1","Q2"], ["North",1200,1350], ...]
sales_records    = "[Sales | records]"             # [{"Region":"North","Q1":1200,"Q2":1350}, ...]
sales_columns    = "[Sales | columns]"             # {"Region":["North","South","East"], "Q1":[1200,980,1440], ...}
sales_transposed = "[Sales | transpose]"           # [["Region","North","South","East"], ["Q1",1200,980,1440], ...]
staff_flat       = "[Staff | flatten]"             # ["name","Ada","Grace","role","Barista","Chef"]
staff            = "[Staff | transpose | records]" # [{"name":"Ada","role":"Barista"}, {"name":"Grace","role":"Chef"}]
price_list       = "{Prices | keys}"               # ["Tea","Coffee","Bacon roll"]
price_values     = "{Prices | values}"             # [3.25, 4, 8.25]
price_items      = "{Prices | items}"              # [["Tea",3.25], ["Coffee",4], ["Bacon roll",8.25]]
code_names       = "{Codes | invert}"              # {"Gluten-free":"GF", "Vegan":"VG"}
covers           = "{Covers | int}"                # {"Mon":128, "Tue":143, "Wed":97}
prices_as_text   = "{Prices | str}"                # {"Tea":"3.25", "Coffee":"4", "Bacon roll":"8.25"}
average_spend    = "AvgSpend | round 2"            # 8.75   (the cell holds 8.7451)
spend_whole      = "AvgSpend | round 0"            # 9.0
period_ending    = "PeriodEnd | isodate"           # "2026-03-31"

Covers holds its numbers as text, which is why int is worth applying. A date-only cell is stored as midnight, and both isodate and the automatic serialisation render it as a plain date rather than 2026-03-31T00:00:00; a genuine time of day is kept.

Function arguments

A function may take arguments after its name, separated by spaces. An argument is a number (2), a double-quoted string (", "), or a bare word, which is read as another range reference. The extracted value is always passed as the first argument, so [Price | round 2] means round(Price, 2). (Grouping parentheses are reserved for a possible future extension and are not yet supported.)

round is the built-in that takes an argument; the same mechanism serves functions you supply yourself:

from edjas import read_spec
read_spec("examples/example.xlsx", "examples/example.toml",
          functions={"join": lambda v, sep: sep.join(v)})

With that in place a spec entry of tag_line = '[Tags | join ", "]' yields "Vegan, Gluten-free, Dairy-free".

Usage

From the command line — pass the spreadsheet and the spec; JSON goes to standard output:

edjas data.xlsx report.toml

As a library, read_spec returns the extracted data as a Python dict. Pass functions={...} to add your own functions to (or override) the built-ins; each receives the extracted value first, then any arguments:

from edjas import read_spec
data = read_spec("data.xlsx", "report.toml",
                 functions={"join": lambda v, sep: sep.join(v)})
# ... lets the spec use:  tags = "[Tags | join \", \"]"

Date and time cells are serialised as ISO-8601 strings automatically.

Architecture

Four diagrams describe how EDJAS is put together, zooming in a level at a time. Each is a self-contained SVG that can be viewed in the browser or downloaded from the images/ directory.

  • C4 system context — the setting: the spreadsheet maintainer who carries on as before, the analyst who authors specs, and the systems the extracted JSON feeds.
  • C4 container diagram — one level in: the command-line and library containers, the files they read, and where the JSON ends up.
  • C4 component diagram — inside the library: the spec loader, expression evaluator, pipeline parser, workbook reader, pipeline executor and function registry, and the calls between them.
  • Internal structure — down at the code: the modules that make up the package, and how a single extraction run flows through them from the spec and workbook to standard output.

A demonstration of the system can be found at https://github.com/holdenweb/edjas-demo.

This is particularly useful for audiences that have an interest in only a limited number of features from a possibly quite large spreadsheet. More generally, JSON is such a widely used format that spreadsheet data can be re-used in a wide range of systems as appropriate.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

edjas-0.7.1.tar.gz (474.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

edjas-0.7.1-py3-none-any.whl (12.9 kB view details)

Uploaded Python 3

File details

Details for the file edjas-0.7.1.tar.gz.

File metadata

  • Download URL: edjas-0.7.1.tar.gz
  • Upload date:
  • Size: 474.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for edjas-0.7.1.tar.gz
Algorithm Hash digest
SHA256 a46c73b1ae866155b8c9eba0cf8efd2050503b689deeb223fb73d1d7541782f9
MD5 20b829dd2dc4b5d65566d9fdae1aa8ea
BLAKE2b-256 f13a5bfa3bd00a70a96942a4d4abfc6cd39bca3955904d9e76bcf5b1941d9b82

See more details on using hashes here.

File details

Details for the file edjas-0.7.1-py3-none-any.whl.

File metadata

  • Download URL: edjas-0.7.1-py3-none-any.whl
  • Upload date:
  • Size: 12.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for edjas-0.7.1-py3-none-any.whl
Algorithm Hash digest
SHA256 03758a862be862b6057784a5f7d9752ed1e594181dd21a2484bb8f0e718e1c83
MD5 b86221a02080a2684a25029e88040421
BLAKE2b-256 b5dde3c24f5d218c02b031b73ee5218d8655f056870a8cf8bed9d594726ddd9d

See more details on using hashes here.

Release history Release notifications | RSS feed

0.7.3

2 files

This release

0.7.1 This release

2 files

0.6.1

2 files

0.6.0

2 files

0.5.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page