Skip to content

Added support for class specific custom conversion through to_json/from_json methods #819

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

mfateev
Copy link
Member

@mfateev mfateev commented Apr 9, 2025

What was changed

Added ability to customize a class conversion by adding to_json and from_json methods.

Why?

Some classes, for example, a reference to a large value stored in an external database, require custom conversion logic.

@mfateev mfateev changed the title DRAFT! DO NOT MERGE! Generic dataclass serialization/deserialization. Added support for class specific custom conversion through to_json/from_json methods Apr 17, 2025
@mfateev mfateev marked this pull request as ready for review April 17, 2025 15:45
@mfateev mfateev requested a review from a team as a code owner April 17, 2025 15:45
Copy link
Member

@cretz cretz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, only a couple of comments. @dandavison - any opinions here?

@@ -489,6 +489,43 @@ class AdvancedJSONEncoder(json.JSONEncoder):

This encoder supports dataclasses and all iterables as lists.

A class can implement to_json and from_json methods to support custom conversion logic.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move this to JSONPlainPayloadConverter instead of here since this class is only for to_json. May also be worth a tiny statement about to_json/from_json support in the "Data Conversion" section of the README.

self.field1 = field1
self.field2 = "foo"

@classmethod
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a test for @staticmethod too just in case?

@@ -499,6 +536,15 @@ def default(self, o: Any) -> Any:

See :py:meth:`json.JSONEncoder.default`.
"""
# Custom encoding and decoding through to_json and from_json
# to_json should be an instance method with only self argument
to_json = "to_json"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I too could not find this method used anywhere (Pydantic V1 and V2 have similar but different names). However, I am concerned our users may have it defined on a model and this code may start to break on upgrade. I wonder if we need some kind of global opt-out or something just to be safe. One option could just be module level const TO_JSON_METHOD_NAME = "to_json" (and from equivalent) so they could change it . Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also concerned that the to_json name might be in use by users with different semantics (after all, to_json should really return bytes). How about calling it "to_jsonable_python"? This is the name that pydantic have chosen for this functionality: https://docs.pydantic.dev/latest/api/pydantic_core/#pydantic_core.to_jsonable_python

It's pretty likely that anyone who already has a method with that name has the expected semantics.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, in some cases pydantic's to_jsonable_python will be a suitable implementation of this method, in which case the name will be helpfully suggestive.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to keep it Temporal specific, I think @mfateev just did some searching and saw no library was expecting to_json/from_json on models (I can't find any either) so he used it. If we are concerned about clashing, which I admittedly am, I would prefer temporal_to_json and temporal_from_json so it's not accidentally used for any other purpose.

Copy link
Contributor

@dandavison dandavison Apr 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the name should not be to_json for at least two reasons:

  1. A function named to_json should return valid JSON bytes, not Python objects.

  2. Pydantic uses the name to_json to return valid JSON bytes, not Python objects.

The Python objects wouldn't even conform to JSON, e.g. they could have non-string keys.

As they say on the pydantic-ai GitHub page, Pydantic is very dominant in the Python AI-oriented ecosystem

the validation layer of the OpenAI SDK, the Anthropic SDK, LangChain, LlamaIndex, AutoGPT, Transformers, CrewAI, Instructor and many more

so, in addition to it making sense from a typing POV, it also makes sense for our conventions to be consistent with those of Pydantic.

Calling the method to_jsonable_python would be consistent with Pydantic and would almost never clash with a method that wasn't suitable for purpose. But we could prefix with temporal_, or introduce a TO_JSONABLE_PYTHON_METHOD_NAME escape hatch.

Although other libraries have in the past used "to_json" to mean "return jsonable python", it's clear that Pydantic has taken a stance against that in favor of using names that are more correct from a type POV.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not be consistent with Pydantic or be consistent with any library. I think these methods should be Temporal-SDK-only methods that have no other purpose or expectations to match any other use but Temporal's. I think to_temporal_json and from_temporal_json (or switch verb and temporal around there) is ideal to make sure there is no confusion about what they are for or their very limited use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants