{"id":15445375,"url":"https://github.com/scriptnull/jsonseal","last_synced_at":"2025-04-19T20:41:33.750Z","repository":{"id":238190556,"uuid":"795236228","full_name":"scriptnull/jsonseal","owner":"scriptnull","description":"A JSON validator for Go { :question: :monocle_face: :question: }","archived":false,"fork":false,"pushed_at":"2024-05-24T17:42:00.000Z","size":40,"stargazers_count":7,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-29T13:11:18.223Z","etag":null,"topics":["golang","json","json-validation","validator"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/scriptnull.png","metadata":{"files":{"readme":"README.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}},"created_at":"2024-05-02T21:18:49.000Z","updated_at":"2024-06-16T05:58:47.000Z","dependencies_parsed_at":"2024-06-19T11:30:04.285Z","dependency_job_id":null,"html_url":"https://github.com/scriptnull/jsonseal","commit_stats":null,"previous_names":["scriptnull/jsonseal"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scriptnull%2Fjsonseal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scriptnull%2Fjsonseal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scriptnull%2Fjsonseal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scriptnull%2Fjsonseal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/scriptnull","download_url":"https://codeload.github.com/scriptnull/jsonseal/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249795203,"owners_count":21326777,"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":["golang","json","json-validation","validator"],"created_at":"2024-10-01T19:44:47.383Z","updated_at":"2025-04-19T20:41:33.715Z","avatar_url":"https://github.com/scriptnull.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/scriptnull/jsonseal/assets/4211715/59caaf7a-2def-4556-8370-4213a4358d87\" height=\"128px\" style=\"max-width: 100%;\" /\u003e\n  \u003cbr\u003e\u003cbr\u003e\n  \u003cspan\u003e\u003cb\u003ejsonseal\u003c/b\u003e\u003c/span\u003e\n  \u003cbr\u003e\u003cbr\u003e\n  \u003cspan\u003eA JSON validator for Go { ❓ 🧐 ❓ }\u003c/span\u003e\n  \u003cbr\u003e\u003cbr\u003e\n\n[![Tests](https://github.com/scriptnull/jsonseal/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/scriptnull/jsonseal/actions/workflows/test.yml)\n[![Go Reference](https://pkg.go.dev/badge/github.com/scriptnull/jsonseal.svg)](https://pkg.go.dev/github.com/scriptnull/jsonseal)\n\n🚧 Work In Progress 🚧\n\n\u003c/div\u003e\n\n\u0026nbsp;\n\n## Goals\n\n- Validation errors should be human-friendly.\n- Writing custom validators is a breeze. (just write a `func() error`)\n- An [errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) style API for expressing validation logic.\n- A drop-in replacement for `json.Unmarshal`. (if you wish)\n\n## Installation\n\n```\ngo get github.com/scriptnull/jsonseal\n```\n\n## Example\n\nConsider the following JSON, that could arrive in a web request for performing payments.\n\n```js\n{\n  \"account_id\": \"3ee7b5eb-f3fc-4f0b-9e01-8d7a0fa76f0b\",\n  \"balance\": 15,\n  \"currency\": \"USD\",\n  \"payment\": {\n    \"amount\": 50,\n    \"currency\": \"USD\",\n    \"payment_mode\": \"card\"\n  }\n}\n```\n\nValidation logic for the json could written as shown below:\n\n```go\nfunc (r *PaymentRequest) Validate() error {\n\tvar payment jsonseal.CheckGroup\n\n\tpayment.Check(func() error {\n\t\tif r.Payment.Currency != r.Currency {\n\t\t\treturn errors.New(\"payment not allowed to different currency\")\n\t\t}\n\n\t\tif r.Payment.Amount \u003e r.Balance {\n\t\t\treturn errors.New(\"insufficient balance\")\n\t\t}\n\n\t\treturn nil\n\t})\n\n\tpayment.Check(func() error {\n\t\tif !slices.Contains(SupportedPaymentModes, r.Payment.Mode) {\n\t\t\treturn fmt.Errorf(\"unsupported payment mode: %s\", r.Payment.Mode)\n\t\t}\n\n\t\treturn nil\n\t})\n\n\treturn payment.Validate()\n}\n```\n\nNow use `jsonseal.Unmarshal`instead of `json.Unmarshal` to inflate your struct and perform validation rules.\n\n```go\nvar paymentRequest PaymentRequest\n\nerr := jsonseal.Unmarshal(paymentRequestWithInsufficientFunds, \u0026paymentRequest)\nif err != nil {\n  // report error\n}\n```\n\n## API\n- [Check Groups](#check-groups)\n- [Errors](#errors)\n- [Fields](#fields)\n- [Drop-in Replacements](#drop-in-replacements)\n- [Unknown Field Suggestions](#unknown-field-suggestions)\n\n### Check Groups\n\nCheck groups are a way to group multiple checks and perform validation for them at once.\n\n```go\nvar grp1 jsonseal.CheckGroup\ngrp1.Check(func() error { /* check condition 1 */ })\ngrp1.Check(func() error { /* check condition 2 */ })\nerr1 := grp1.Validate()\n\nvar grp2 jsonseal.CheckGroup\ngrp2.Check(func() error { /* check condition 1 */ })\ngrp2.Check(func() error { /* check condition 2 */ })\nerr2 := grp2.Validate()\n```\n\n### Errors\n\njsonseal comes with built-in error formatters for convenience.\n\n```go\nerr := jsonseal.Unmarshal(paymentRequestWithInsufficientFunds, \u0026paymentRequest)\nif err != nil {\n\tfmt.Println(\"Plain error\")\n\tfmt.Print(err)\n\tfmt.Println()\n\n\tfmt.Println(\"JSON error\")\n\tfmt.Println(jsonseal.JSONFormat(err))\n\tfmt.Println()\n\n\tfmt.Println(\"JSON error with indent\")\n\tfmt.Println(jsonseal.JSONIndentFormat(err, \"\", \"  \"))\n\tfmt.Println()\n\treturn\n}\n```\n\nBut if you wish to get a Go struct that denotes all the validation errors, you could get it like this:\n\n```go\nerr := jsonseal.Unmarshal(paymentRequestWithInsufficientFunds, \u0026paymentRequest)\nif err != nil {\n\t\tif validationErrors, ok := err.(*jsonseal.Errors); ok {\n\t\t\tfmt.Println(validationErrors)\n\t\t}\n}\n```\n\nAn example error message that is returned by `jsonseal.JSONIndentFormat` looks like\n\n```js\n{\n  \"errors\": [\n    {\n      \"error\": \"insufficient balance\"\n    },\n    {\n      \"error\": \"unsupported payment mode: neft\"\n    }\n  ]\n}\n```\n\n### Fields\n\nJSON fields could be associated with the validation errors like this:\n\n```go\npayment.Field(\"payment.mode\").Check(func() error {\n\tif !slices.Contains(SupportedPaymentModes, r.Payment.Mode) {\n\t\treturn fmt.Errorf(\"unsupported payment mode: %s\", r.Payment.Mode)\n\t}\n\n\treturn nil\n})\n```\n\nThe above code associates the json field `payment.mode` with any error that arises from the `Check` block attached to it.\n\n```js\n// before calling Field()\n{\n  \"error\": \"unsupported payment mode: neft\"\n}\n\n// after calling Field()\n{\n  \"fields\": [\n    \"payment.mode\"\n  ],\n  \"error\": \"unsupported payment mode: neft\"\n}\n```\n\nA method called `Fieldf` is available to help with cases like `payments.Fieldf(\"payments[%d].amount\", idx)` (while trying to associate array element as a field).\n\nAn error could be asscoiated with multiple different fields by chaining `Field` or `Fieldf`.\n\n```go\nusers.Field(\"sender.id\").Field(\"receiver.id\").Check(AreFriends())\n```\n\nThis will make sure to associate both fields with the error in case of the validation error.\n\n```js\n{\n  \"fields\": [\n    \"sender.id\",\n    \"receiver.id\"\n  ],\n  \"error\": \"sender and receiver are not friends\"\n}\n```\n\n### Drop-in Replacements\n\njsonseal provides drop-in replacements for a few things in [encoding/json](https://pkg.go.dev/encoding/json) package. This is to ensure API compatibility and seamless migration experience.\n\n- `jsonseal.Unmarshal` could be used in the place of `json.Unmarshal`\n- `jsonseal.Decoder` could be used in the place of `json.Decoder`\n  ```go\n  err = jsonseal.NewDecoder(data).Decode(\u0026v)\n  ```\n\nIf you wish to ensure that `jsonseal.Validator` interface was implemented by the input at compile time, you could use the below alternatives:\n- `jsonseal.UnmarshalValidate` could be used instead of `jsonseal.Unmarshal`.\n- `jsonseal.DecodeValidate` could be used instead of `jsonseal.Decode`.\n\nAlternatively, you could also do the following to ensure the compile time guarantee.\n\n```go\nvar _ jsonseal.Validator = \u0026PaymentRequest{}\n```\n\n### Unknown Field Suggestions\n\nIt might be useful to validate if the JSON data contains only the fields that are expected by the struct to which it is decoded to.\n\nExample: A user sends `{\"expires\": 50}` as the JSON data but our code expects it to be `{\"expires_in\": 50}`. If you are using `json` package, you might enable this validation by calling `DisallowUnknownFields()` on the `json.Decoder`. That will give you an error like `json: unknown field \"expires\"`.\n\njsonseal provides `WithUnknownFieldSuggestion()` method which takes the error message to the next level by suggesting the right field name based on the [Levenshtein Distance](https://en.wikipedia.org/wiki/Levenshtein_distance) between the wrongly typed field name and all possible field names of the struct that we are decoding to.\n\n```go\ntype Data struct {\n  ExpiresIn      int    `json:\"expires_in\"`\n  Balance        int    `json:\"balance,omitempty\"`\n  PrivateField   string `json:\"-\"`\n}\nvar d Data\nerr := jsonseal.NewDecoder(data).WithUnknownFieldSuggestion().Decode(\u0026d)\nif err != nil {\n  fmt.Println(jsonseal.JSONIndentFormat(err, \"\", \"  \"))\n}\n```\n\nThis gives the following error\n\n```json\n{\n  \"errors\": [\n    {\n      \"fields\": [\"expires\"],\n      \"error\": \"unknown field. Did you mean \\\"expires_in\\\"\"\n    }\n  ]\n}\n```\n\nThanks to [this blog post](https://brandur.org/disallow-unknown-fields) for providing inspiration and motivation for this feature in jsonseal :pray:.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscriptnull%2Fjsonseal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscriptnull%2Fjsonseal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscriptnull%2Fjsonseal/lists"}