Skip to content
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

Possible to serialize a top-level list/array? #69

Closed
ShardPhoenix opened this issue Feb 1, 2022 · 8 comments
Closed

Possible to serialize a top-level list/array? #69

ShardPhoenix opened this issue Feb 1, 2022 · 8 comments
Labels
enhancement New feature or request

Comments

@ShardPhoenix
Copy link

JSON allows an array at top level (instead of an object). It would be nice if we have eg a List[MyDataClass] to be able to serialize this directly without a wrapper. Is this possible in mashumaro?

Currently I'm working around this like follows:

json = f"[{','.join([item.to_json() for item in my_list])}]"
@Fatal1ty
Copy link
Owner

Fatal1ty commented Feb 1, 2022

Hi @ShardPhoenix

It's not possible with mashumaro at the moment. I'm thinking about a separated from dataclass function for serialization but it's not coming soon.

@illeatmyhat
Copy link

dataclasses-json has a many=true field for their dump() function. Seems to work well enough.
https://github.com/lidatong/dataclasses-json#use-my-dataclass-with-json-arrays-or-objects

@Fatal1ty
Copy link
Owner

At the moment, it can be done using a generic dataclass wrapper. A couple of examples:

@dataclass
class CommonClass(
    Generic[T],
    DataClassJSONMixin,
    DataClassMessagePackMixin,
    DataClassYAMLMixin,
    DataClassTOMLMixin,
):
    x: T

    @classmethod
    def __pre_deserialize__(cls: Type[T], d: Dict[Any, Any]) -> Dict[Any, Any]:
        return {"x": d}

    def __post_serialize__(self: T, d: Dict[Any, Any]) -> Dict[Any, Any]:
        return d["x"]


def create_unpacker(data_type: T, fmt: Literal["dict", "json"] = "dict"):
    class ConcreteClass(CommonClass[data_type]):
        pass

    if fmt == "json":
        def unpacker(data) -> T:
            return ConcreteClass.from_json(data)
    else:
        def unpacker(data) -> T:
            return ConcreteClass.from_dict(data)

    return unpacker


def create_packer(data_type: T, fmt: Literal["dict", "json"] = "dict"):
    class ConcreteClass(CommonClass[data_type]):
        pass

    if fmt == "json":
        def packer(data) -> T:
            return ConcreteClass(data).to_json()
    else:
        def packer(data) -> T:
            return ConcreteClass(data).to_dict()

    return packer


@dataclass
class MyDataClass(DataClassDictMixin):
    x: datetime


my_json_packer = create_packer(list[MyDataClass], fmt="json")
print(repr(my_json_packer([MyDataClass(datetime.utcnow())])))
# '[{"x": "2023-03-26T10:33:44.567742"}]'

my_dict_packer = create_packer(list[MyDataClass], fmt="dict")
print(repr(my_dict_packer([MyDataClass(datetime.utcnow())])))
# [{'x': '2023-03-26T10:34:06.932352'}]


def from_json(cls: T, data) -> T:
    class ConcreteClass(CommonClass[cls]):
        pass

    return ConcreteClass.from_json(data).x


def as_json(cls: T, obj) -> T:
    class ConcreteClass(CommonClass[cls]):
        pass

    return ConcreteClass(obj).to_json()


print(repr(as_json(list[MyDataClass], [MyDataClass(datetime.utcnow())])))
# '[{"x": "2023-03-26T10:38:24.234213"}]'

print(from_json(list[MyDataClass], '[{"x": "2023-03-26T10:36:25.902384"}]'))
# [MyDataClass(x=datetime.datetime(2023, 3, 26, 10, 36, 25, 902384))]

@Fatal1ty Fatal1ty added the enhancement New feature or request label Mar 26, 2023
@RA80533
Copy link
Contributor

RA80533 commented Apr 23, 2023

Is that specific construct something you were going to add to the package?

Is there anything I can do to help bring this feature to fruition?

@Fatal1ty
Copy link
Owner

Is that specific construct something you were going to add to the package?

I've given a couple of examples of how this issue can be addressed from outside, but I wasn't going to include it to the package because it's still a workaround. For native support I see the following steps:

  • Come up with the convenient API
  • Extend the existing CodeBuilder or create something similar to it that would build functions for specific types from ground up without extra dataclasses

Is there anything I can do to help bring this feature to fruition?

You can play around with CodeBuilder and pack.py / unpack.py modules and try to get a proof of concept. Any help is welcome.

@joaonc
Copy link

joaonc commented Jun 6, 2023

Started working with mashumaro and congrats on a great package. IMO, the ability to serialize as array at top level is a killer feature as it's used in many scenarios.

One suggestion is to add the functionality above into the package, even if it's not a complete solution and then modify it later, with the risk of breaking the API if a better solution comes along. This is much needed 😃

@Fatal1ty
Copy link
Owner

Good news everyone! I’m working on it, so this functionality will be a part of 3.11 release.

@Fatal1ty
Copy link
Owner

The long-awaited pull request has landed! See updated docs here.

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

No branches or pull requests

5 participants