Sometimes good old tools beat the new shiny turd.
I had to do this once for a large piece of JSON that ballooned quite large when loaded into RAM. But in my case I could wait until after json.loads to do the interning; but I think you can do it on the fly with object_hook or object_pairs_hook.
Alternatively, Rust+Serde & doing as bare-bones a computation to get it working, again with an eye out to whether the decoded form will fit. (Hopefully, since Serde'ing into a struct should make the keys 0 bytes, essentially, but there would be some memory lost for pointers and the like.)
Alternatively, a 20 GiB swap file.
Naturally I was curious what such a shell script consisted of, but most tools choked on it. IIRC, even vim and less couldn't open it, but emacs could.
https://stedolan.github.io/jq/manual/#Streaming
You have to rewrite your criteria, but it's what's allowed me to process huge json files without issues.