{"id":24325692,"url":"https://github.com/abe-winter/dartjsonclass","last_synced_at":"2025-06-24T13:38:08.922Z","repository":{"id":89518682,"uuid":"544155549","full_name":"abe-winter/dartjsonclass","owner":"abe-winter","description":"generate dart dataclasses from python pydantic classes","archived":false,"fork":false,"pushed_at":"2023-07-15T03:31:47.000Z","size":100,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-14T03:37:02.119Z","etag":null,"topics":["codegen","dart","dartlang","dataclass","flutter","json","pydantic","serialization"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/abe-winter.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-10-01T19:43:14.000Z","updated_at":"2024-12-13T18:58:51.000Z","dependencies_parsed_at":null,"dependency_job_id":"0588a9d8-3d2f-40f5-9e2a-dad913e40e7f","html_url":"https://github.com/abe-winter/dartjsonclass","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/abe-winter/dartjsonclass","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abe-winter%2Fdartjsonclass","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abe-winter%2Fdartjsonclass/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abe-winter%2Fdartjsonclass/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abe-winter%2Fdartjsonclass/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/abe-winter","download_url":"https://codeload.github.com/abe-winter/dartjsonclass/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abe-winter%2Fdartjsonclass/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261686628,"owners_count":23194306,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["codegen","dart","dartlang","dataclass","flutter","json","pydantic","serialization"],"created_at":"2025-01-17T20:18:55.910Z","updated_at":"2025-06-24T13:38:08.902Z","avatar_url":"https://github.com/abe-winter.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# dartjsonclass\n\nThis is a python project that generates dart classes from pydantic classes.\n\nThe dart classes which we output:\n- convert to and from json or `Map`\n- have `operator ==` and `copy`\n- have basic metaprogramming features (getAttr / setAttr / fields list)\n- mostly-working nesting\n\nPlay with it by running `make e2e` in the repo. This runs the tool on `example.py` and runs a (very simple) suite of dart tests to exercise capabilities.\n\nAlso run `make dart-test` to run a more comprehensive suite of dart tests on the generated classes.\n\n## installation\n\n(pip with git+)\n\n## example\n\ngiven this input in python:\n\n```python\nclass Msg(pydantic.BaseModel):\n    id: str\n    maybe: Optional[int]\n    item: Item # Item is defined elsewhere -- check out example.py in this repo for the actual full example\n    dt: datetime\n    item_list: List[Item]\n    item_dict: Dict[str, Item]\n    id_dict: Dict[uuid.UUID, Item]\n\n```\n\nyou will get this dart (and you can suppress getAttr / setAttr, which are the bulkiest methods here, if you don't need them):\n\n```dart\nclass Msg extends JsonBaseMeta {\n  String id;\n  int? maybe;\n  Item item;\n  DateTime dt;\n  List\u003cItem\u003e item_list;\n  Map\u003cString, Item\u003e item_dict;\n  Map\u003cString, Item\u003e id_dict;\n  Msg(this.id, this.maybe, this.item, this.dt, this.item_list, this.item_dict, this.id_dict);\n  factory Msg.fromMap(Map\u003cString, dynamic\u003e raw) =\u003e Msg(raw[\"id\"]!, raw[\"maybe\"], Item.fromMap(raw[\"item\"]), DateTime.parse(raw[\"dt\"]), raw[\"item_list\"].map\u003cItem\u003e((elt) =\u003e Item.fromMap(elt)).toList(), raw[\"item_dict\"].map\u003cString, Item\u003e((key, val) =\u003e MapEntry(key as String, Item.fromMap(val))), raw[\"id_dict\"].map\u003cString, Item\u003e((key, val) =\u003e MapEntry(key as String, Item.fromMap(val))));\n  factory Msg.fromJson(String raw) =\u003e Msg.fromMap(jsonDecode(raw));\n  @override\n  Map\u003cString, dynamic\u003e toMap() =\u003e Map.fromEntries([MapEntry(\"id\", id), MapEntry(\"maybe\", maybe), MapEntry(\"item\", item.toMap()), MapEntry(\"dt\", dt.toIso8601String()), MapEntry(\"item_list\", item_list.map((e) =\u003e e.toMap()).toList()), MapEntry(\"item_dict\", item_dict.map((key, value) =\u003e MapEntry(key, value.toMap()))), MapEntry(\"id_dict\", id_dict.map((key, value) =\u003e MapEntry(key, value.toMap())))]);\n  static List\u003cString\u003e djc__fields = [\"id\", \"maybe\", \"item\", \"dt\", \"item_list\", \"item_dict\", \"id_dict\"];\n  @override\n  getAttr(String name) {\n    switch(name) {\n      case \"id\": return id;\n      case \"maybe\": return maybe;\n      case \"item\": return item;\n      case \"dt\": return dt;\n      case \"item_list\": return item_list;\n      case \"item_dict\": return item_dict;\n      case \"id_dict\": return id_dict;\n      default: throw ArgumentError(\"Unknown field ${name}\");\n    } \n  } \n  @override\n  void setAttr(String name, dynamic val) {\n    switch(name) {\n      case \"id\":\n        id = val;\n        break;\n      case \"maybe\":\n        maybe = val;\n        break;\n      case \"item\":\n        item = val;\n        break;\n      case \"dt\":\n        dt = val;\n        break;\n      case \"item_list\":\n        item_list = val;\n        break;\n      case \"item_dict\":\n        item_dict = val;\n        break;\n      case \"id_dict\":\n        id_dict = val;\n        break;\n      default: throw ArgumentError(\"Unknown field ${name}\");\n    } \n  } \n  @override\n  bool operator ==(Object other) {\n    if (other is! Msg) return false;\n    var x = other as Msg;\n    return id == x.id \u0026\u0026 maybe == x.maybe \u0026\u0026 item == x.item \u0026\u0026 dt == x.dt \u0026\u0026 listEqual(item_list, x.item_list) \u0026\u0026 mapEqual(item_dict, x.item_dict) \u0026\u0026 mapEqual(id_dict, x.id_dict);\n  } \n  @override\n  int get hashCode =\u003e Object.hash(id, maybe, item, dt, hashcodeList(item_list), hashcodeMap(item_dict), hashcodeMap(id_dict));\n  @override\n  Msg copy() =\u003e Msg(id, maybe, item.copy(), dt, [...item_list], {...item_dict}, {...id_dict});\n}\n```\n\n## status\n\nDon't use this in a prod codebase unless:\n- you have simple serialization classes (not much nesting, straightforward nulls)\n- you have fairly comprehensive testing around serialization in your dart codebase\n\nEven then, you should post a github issue with your use case so we can talk about risks and maybe add test coverage.\n\nKnown or likely problems:\n- direct nesting of collection classes like `List\u003cList\u003cString\u003e\u003e`. `List\u003cString\u003e` is okay, `List\u003cSomeDataclass\u003e` is probably okay (even if SomeDataclass has collections in it)\n- nullable field handling seems to be okay, but is not fully exercised in tests and I wouldn't be surprised if there are problems\n\n## why\n\nVarious parts of my message class workflow were not working:\n- codegen is slow (15ish seconds when I had 3 tools, of which I legit needed 2 probably)\n- the source classes are hard to write (like I'm writing the name of the class 6 times, or inserting weird `_$_` prefixes, or defining part memberships which don't exist until I run a tool)\n- generated classes are hard to extend\n- parts of my codebase rely on and metaprogramming and reflectable was a pain to work with\n- no one-stop shop for serialization + metaprogramming\n\nAlso, my message objects are strongly typed in my backend codebase. Maintaining them in the frontend codebase felt like duplicate work.\n\n## roadmap\n\n- [x] generic codegen / expression renderer\n- [x] dart specific codegen\n- [x] pydantic-to-dart\n- [x] some collection support\n- [x] fromMap / toMap, fromJson / toJson\n- [x] basic metaprogramming: fieldNames / getAttr / setAttr\n- [x] operator ==, copy, hash\n  - [ ] try https://pub.dev/packages/equatable or quiver.collections to replace the bespoke versions in here\n  - [ ] deep copy. detect cases where splat `...` operator won't cut it\n  - [ ] operator == for collections (mostly working, needs comprehensive combination testing)\n  - [x] literals\n  - [ ] non-literals (mostly working, needs comprehensive tests)\n  - [ ] factory with named arguments, copyWith\n- [x] nullable fields\n  - [ ] inventory all special cases + cover them in test suite\n- [x] nested collections like `Map\u003cString, List\u003cItem\u003e\u003e`\n  - [ ] cover third-level nesting in test suite\n  - [ ] comprehensive tests for `List\u003cMap\u003e`, `Map\u003cString, List\u003e`, and both classes and literals in the inner collection\n- [ ] tests\n  - [x] CI + tests passing\n  - [ ] coverage in py codebase\n- [ ] format version strings into the generated .dart (including jsonbase) (include version of this tool and of the source codebase)\n- [ ] support no-json, no-meta, no-dataclass flags (semi working, dart generics support is an obstacle here, need strategy that doesn't limit consumer code)\n- [x] make get/set opt-in per class (it's the largest feature by line count, also probably the least-used)\n- [ ] rehydrate union types (instead of making them dynamic)\n  - [ ] based on literal flag\n  - [ ] based on field or dynamic type testing where not ambiguous\n- [ ] way of specifying configs on source classes which DJC can consume\n- [x] datetime support\n\n### nice-to-haves\n\n- [ ] global and per-class immutable\n- [ ] codegen: walk tree to render DartExprs from root render, don't require pre-rendering\n- [ ] better intermediate representation for classes (don't convert directly from pydantic to DartClass)\n- [ ] ser/des error handling\n  - [ ] global and per-class hooks (e.g. to show a toast)\n  - [ ] distinguish conversion errors from missing field errors\n  - [ ] include context in error (pass down dotted address to nested parse)\n- [ ] include API GET route in generated class, with template string\n  - [ ] also specify query params\n  - [ ] also specify non-GET routes\n- [ ] generate inheritance that matches pydantic (for base testing. may need this for flagged union rehydration)\n  - [ ] delegate some ser/des to base class using spread ops? tricky bc some fields may be overridden\n\n## other useful features\n\n- [`codegen.py`](dartjsonclass/codegen.py) provides a generic tool for generating formatted source in any language from python. [`dartgen.py`](dartjsonclass/dartgen.py) has an example of how to use it\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabe-winter%2Fdartjsonclass","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fabe-winter%2Fdartjsonclass","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabe-winter%2Fdartjsonclass/lists"}