PEP 837: Extensible JSON serialization

I’m not a fan of it being a global state, but I somewhat think copyreg makes more sense for a location for this than most places, including on the class itself (different applications need to serialize types differently, we know this). (Constructing and passing around a serializer/deserializer with the appropriate options seems the best option, but that’s already possible and people don’t always do it when they should)

I wonder if the API in copyreg can be imrpoved so that consumers of the api can get a point in time snapshot that can’t change under them, without having to create than snapshot themselves.

1 Like

Sure, but the point from Serhiy is that while potential breakage will be taken into consideration, we also don’t have to feel bad if there is some due to people not following this rule.

It would be very hard to implement in a way that’s forwards-compatible. Python is a language of consenting adults, so people should realize what risks they are taking on when doing something.

9 Likes

Thank you, I fixed this little inaccuracy in in the top post (it was not in the PEP).

that would otherwise head toward the isinstance checks, default= or TypeError.

Hello! I maintain treekle, which converts arbitrary objects to plain trees and back for json.dumps.

We started with exactly that pure-Python pre-pass. On 43 real experiment configs (nested dataclasses), the Python walker was 6.3x slower to encode and 95x slower to decode than the
same logic in Rust (4.8 ms vs 0.77 ms; 16.2 ms vs 0.17 ms). GitHub - rekursiv-ai/treekle: Strongly-typed fast pickler producing plain python trees. · GitHub

A wrapper works, but each one walks the tree a second time in Python, and that walk dominates the cost. Hooks inside the C encoder avoid it. Msgspec follows suit but defined their own container to speed it up whereas we opted to support native python.

Question on “Neutral ground”: is the intent that third-party native encoders read copyreg.json_dispatch_table directly?

Benchmark:

To be clear, when I said wrapper function, I just meant something like:

def json_dumps(*args, **kwargs):
    return json.dumps(*args, **kwargs, dispatch_table={decimal.Decimal: str}, default=json_default)

Not walk the entire structure (although I suppose that’s also an option – and one that works with any JSON library).

I see your point; probably my point was just noise then. I did want to raise however that the walking is the hard/slow part.

But regardless, I definitely agree we should have an agnostic __serialize__ and this was what I optimized for in treekle. In fact, this is why its “tree”+“pickle” not “json pickle”–I felt going to plain python tree is better because then last mile serialization can be handled elsewhere.

There is a caveat though. Converting object to tree and tree to JSON can be made 2x faster if you go directly to JSON so there is a tension between agnosticism and performance.

1 Like