{"id":51419693,"url":"https://github.com/oboard/jsonx","last_synced_at":"2026-07-04T23:01:30.659Z","repository":{"id":358836797,"uuid":"1243258110","full_name":"oboard/jsonx","owner":"oboard","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-19T08:02:17.000Z","size":15,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-19T10:52:15.537Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"MoonBit","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oboard.png","metadata":{"files":{"readme":"README.mbt.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-19T07:19:51.000Z","updated_at":"2026-05-19T08:02:21.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/oboard/jsonx","commit_stats":null,"previous_names":["oboard/jsonx"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/oboard/jsonx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oboard%2Fjsonx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oboard%2Fjsonx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oboard%2Fjsonx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oboard%2Fjsonx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oboard","download_url":"https://codeload.github.com/oboard/jsonx/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oboard%2Fjsonx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35138079,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2026-07-04T23:01:29.918Z","updated_at":"2026-07-04T23:01:30.648Z","avatar_url":"https://github.com/oboard.png","language":"MoonBit","funding_links":[],"categories":[],"sub_categories":[],"readme":"# oboard/jsonx\n\nSmall helpers for reading MoonBit `Json` values with typed fallbacks, plus a\ncode generator for `ToJson` / `FromJson` impls.\n\n## Runtime helpers\n\nAdd the package to a `moon.pkg`:\n\n```moonbit nocheck\nimport {\n  \"oboard/jsonx\"\n}\n```\n\n`opt` converts a `Json` value through `@json.FromJson` and returns `None`\ninstead of raising on decode errors.\n\n```mbt check\n///|\ntest \"opt\" {\n  let ok : Int? = @jsonx.opt(42.0)\n  assert_true(ok is Some(42))\n\n  let bad : Int? = @jsonx.opt(\"42\")\n  assert_true(bad is None)\n}\n```\n\n`nul` preserves three states for nullable fields: decode failure, explicit\n`null`, and a present value.\n\n```mbt check\n///|\ntest \"nul\" {\n  let explicit_null : String?? = @jsonx.nul(null)\n  assert_true(explicit_null is Some(None))\n\n  let present : String?? = @jsonx.nul(\"Ada\")\n  assert_true(present is Some(Some(\"Ada\")))\n\n  let bad : String?? = @jsonx.nul(true)\n  assert_true(bad is None)\n}\n```\n\n`get`, `strf`, and `intf` read nested object fields. Paths can be dot-separated\nstrings, string arrays, `StringView`, `Int`, or `Double`.\n\n```mbt check\n///|\ntest \"path reads\" {\n  let obj : Json = {\n    \"user\": { \"profile\": { \"name\": \"Ada\", \"age\": 37.0 } },\n    \"items\": { \"1\": \"first\" },\n  }\n\n  let name : String? = @jsonx.get(obj, \"user.profile.name\")\n  assert_true(name is Some(\"Ada\"))\n\n  let age : Int? = @jsonx.get(obj, [\"user\", \"profile\", \"age\"])\n  assert_true(age is Some(37))\n\n  assert_true(@jsonx.strf(obj, \"items.1\") is Some(\"first\"))\n  let missing : String? = @jsonx.get(obj, [\"user\", \"missing\"])\n  assert_true(missing is None)\n}\n```\n\n`str` and `int` provide small coercions for common JSON input.\n\n```mbt check\n///|\ntest \"coercions\" {\n  assert_true(@jsonx.str(12.0) is Some(\"12\"))\n  assert_true(@jsonx.str(true) is None)\n\n  assert_true(@jsonx.int(\"42\") is Some(42))\n  assert_true(@jsonx.int(\"x\") is None)\n}\n```\n\n`sub` is still available when you already have a `Map[String, Json]`.\n\n```mbt check\n///|\ntest \"sub object\" {\n  let obj : Json = {\n    \"child\": { \"label\": \"nested\" },\n    \"text\": \"nested\",\n  }\n\n  guard @jsonx.sub(obj, \"child\") is Some(child) else {\n    fail(\"expected child object\")\n  }\n  assert_true(@jsonx.strf(Json::object(child), \"label\") is Some(\"nested\"))\n  assert_true(@jsonx.sub(obj, \"text\") is None)\n}\n```\n\n## Code generation\n\nMark structs with `#jsonx.struct`. Use `#jsonx.key(\"...\")` when the JSON key\ndiffers from the MoonBit field name.\n\n```mbt nocheck\n///|\n#jsonx.struct\npub(all) struct Person {\n  name : String\n  age : Int\n  #jsonx.key(\"method\")\n  method_ : String\n}\n```\n\nRun the generator with an input file and an output file:\n\n```bash\nmoon run cmd/jsonxgen -- -o person.g.mbt person.mbt\n```\n\nFor the struct above, `jsonxgen` emits:\n\n```mbt nocheck\n///|\npub impl ToJson for Person with to_json(self) {\n  { \"name\": self.name, \"age\": self.age, \"method\": self.method_ }\n}\n\n///|\npub impl FromJson for Person with from_json(json, path) {\n  guard json is Object(obj) else {\n    raise @json.JsonDecodeError((path, \"expected object\"))\n  }\n  let name = match obj.get(\"name\") {\n    Some(String(name)) =\u003e name\n    _ =\u003e raise @json.JsonDecodeError((path, \"expected string for 'name'\"))\n  }\n  let age = match obj.get(\"age\") {\n    Some(Number(age, ..)) =\u003e age.to_int()\n    _ =\u003e raise @json.JsonDecodeError((path, \"expected number for 'age'\"))\n  }\n  let method_ = match obj.get(\"method\") {\n    Some(String(method_)) =\u003e method_\n    _ =\u003e raise @json.JsonDecodeError((path, \"expected string for 'method'\"))\n  }\n  Person::{ name, age, method_ }\n}\n```\n\nThe generator currently emits specialized field checks for `String` and `Int`.\nOther field types are decoded through `@json.from_json`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foboard%2Fjsonx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foboard%2Fjsonx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foboard%2Fjsonx/lists"}