As someone who's no stranger to high-scale ETL (Extract, Transform, Load) workloads that work across huge data lakes, this is a love-hate relationship (mostly love though).
Having said that, I've recently been spending some time around Polars and the broader DataFrame ecosystem, mostly because I keep running into the same boundary: the data is messy, but the useful part of the system wants a schema.
So, introducing two side projects around that problem space:
polars-fastjson, a lenient, schema-aware JSON projection for Python Polars.
golars, a Polars-like, Arrow-native DataFrame API for Go.
Neither is intended to replace Polars core, they are supplemental additions to the already thriving polars ecosystem. Standing on the shoulders of giants, as they say.
ComputeError: error deserializing JSON: json parsing error: 'ExpectedObjectKey at character 115 ('{')'
This error occurred in the following expression:
col("metadata").str.json_decode()
This even happens when the JSON is well formed, but the schema changes between rows:
import polars as pl
data = [
{"id": 1, "metadata": '{"role": "admin", "status": "active"}'},
{
"id": 2,
"metadata": '{"role": "user", "status": [42]}',
}, # Malformed data (status should be a string)
{"id": 3, "metadata": '{"role": "guest", "status": "active"}'},
{"id": 4, "metadata": '{"role": "user", "status": "active"}'},
]
df = pl.DataFrame(data)
parsed_df = df.with_columns(
pl.col("metadata")
.str.json_decode(
# We even specify a schema here
pl.Struct([pl.Field("role", pl.String), pl.Field("status", pl.String)])
)
.alias("parsed_meta")
).show()
Also blows up:
ComputeError: error deserializing JSON: error deserializing value "Array([Static(U64(42))])" as string.
Try increasing `infer_schema_length` or specifying a schema.
This error occurred in the following expression:
col("metadata").str.json_decode()
So the problem statement is - we have many rows and we want to decode JSON efficiently and also in such a way that we can be lenient about parse errors (and understand why they happened).
Alternatively, we could use json_path_match (for example, pl.col("raw_json").str.json_path_match("$.user.name")) but this is highly inefficient, since you need to reparse the same JSON row if you want to extract more than 1 field.
Here is where I scoured the internet to see what I was missing, since this doesn't seem like that much of a niche problem. But it looks like there's no popular solution here (happy to hear if I'm wrong, please DM me!).
So I (+ some AI agents) wrote polars-fastjson.
It takes a different approach: provide the target schema once, then project each JSON string into a typed Struct and allow for leniency (don't simply raise an exception if something goes wrong).
A bad "leaf" field becomes null and valid sibling fields are retained
Compatible values can be coerced (my personal choice here, can be configured)
In addition:
Strict modes are available when a pipeline should fail instead
Nested structs and lists are supported
You can supply a schema by a Polars dtype, a dictionary, a dataclass, a TypedDict, or a Pydantic model.
You can ask to emit diagnostics which allows for a summary to understand why rows were nulled without logging each one (huge I/O strain, and also noisy)
The performance has been promising in local benchmarks, including at million-row scale, and appears to scale roughly linearly with the number of rows. The benchmark is included in the repository if you want to try it yourself.
The motivation for golars is slightly different from polars-fastjson.
I love Polars and love Go. For some experiments, I wanted to stay in Go while still using Polars' lazy query model and native execution, but couldn't find a well-supported binding that gave me that combination.
Generally, I wanted to explore what a DataFrame API could feel like from Go without reimplementing a relational engine in Go or requiring Python as the host language.
The project exposes Arrow-backed DataFrames, lazy expressions, common Polars-style operations, and readers/writers for formats like Parquet, CSV, and Arrow IPC (lots of clients allow you to convert into Arrow).
It also includes storage adapters for S3 and GCS, which is what a lot of people use nowadays as their data lake.
The end-to-end benchmarks look promising. On the 1M-5M row workloads, golars is generally close to direct Rust Polars, while smaller workloads show more overhead and results vary by query. This does not isolate the Go->Rust FFI (Foreign Function Interface) overhead, but suggests the boundary isn't dominating the workloads I've tested (which was my main concern).
The project is still a fast-breaking v0, but it is already useful as a place to test the shape of a Go-facing Polars API.
Note on AI
Not everyone will agree, but I still find it the most efficient to code at least ~20% by hand these days and let agents naturally expand it once the boilerplate is clear.
The experience wasn't always smooth (for example, for golars it first tried to reimplement the entire polars engine in native Go, while it makes much more sense to simply call Polars via FFI). GPT-5.6 Sol should have caught this, likely in a few months this becomes less of a concern.
For these experiments, it's at least ~90% LLM generated with heavy guidance. Once it matures and people find it useful, will do another pass to "deslop" many parts there.
That feels worth writing because I have a phobia of READMEs that feel overwhelming. This was vetted and guided by a human (not a perfect one, but a human nonetheless).
Feel free to try it out and raise issues/feedback!