{"id":13413901,"url":"https://github.com/jgroeneveld/schema","last_synced_at":"2025-03-14T20:30:45.869Z","repository":{"id":36355859,"uuid":"40660639","full_name":"jgroeneveld/schema","owner":"jgroeneveld","description":"Quick and easy expression matching for JSON schemas used in requests and responses","archived":false,"fork":false,"pushed_at":"2019-10-13T10:57:48.000Z","size":49,"stargazers_count":21,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-07-31T20:52:57.925Z","etag":null,"topics":["go","json","matcher","testing"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jgroeneveld.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}},"created_at":"2015-08-13T13:36:54.000Z","updated_at":"2024-03-10T05:41:32.000Z","dependencies_parsed_at":"2022-08-28T19:41:19.487Z","dependency_job_id":null,"html_url":"https://github.com/jgroeneveld/schema","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jgroeneveld%2Fschema","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jgroeneveld%2Fschema/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jgroeneveld%2Fschema/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jgroeneveld%2Fschema/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jgroeneveld","download_url":"https://codeload.github.com/jgroeneveld/schema/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243641999,"owners_count":20323947,"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":["go","json","matcher","testing"],"created_at":"2024-07-30T20:01:52.357Z","updated_at":"2025-03-14T20:30:45.560Z","avatar_url":"https://github.com/jgroeneveld.png","language":"Go","readme":"# schema [![GoDoc](https://godoc.org/github.com/jgroeneveld/schema?status.svg)](https://godoc.org/github.com/jgroeneveld/schema) [![GoReport](https://goreportcard.com/badge/github.com/jgroeneveld/schema)](https://goreportcard.com/report/github.com/jgroeneveld/schema)\n**schema** makes it easier to check if map/array structures match a certain schema. Great for testing JSON API's or validating the format of incoming requests and providing error messages to the api user.  Also see [trial](https://github.com/jgroeneveld/trial) for simple test assertions.\n\nThe initial version was built by [gfrey](https://github.com/gfrey) and [jgroenveld](https://github.com/jgroeneveld). It was inspired by [chancancode/json_expressions](https://github.com/chancancode/json_expressions)\n\nCoverage: https://gocover.io/github.com/jgroeneveld/schema\n\n## Example\n\n[example_test.go](example_test.go)\n```go\nfunc TestJSON(t *testing.T) {\n    reader := getJSONResponse()\n\n    err := schema.MatchJSON(\n        schema.Map{\n            \"id\":       schema.IsInteger,\n            \"name\":     \"Max Mustermann\",\n            \"age\":      42,\n            \"height\":   schema.IsFloat,\n            \"footsize\": schema.IsPresent,\n            \"address\": schema.Map{\n                \"street\": schema.IsString,\n                \"zip\":    schema.IsString,\n            },\n            \"tags\": schema.ArrayIncluding(\"red\"),\n        },\n        reader,\n    )\n\n    if err != nil {\n        t.Fatal(err)\n    }\n}\n```\n\n**JSON Input**\n\n```json\n{\n    \"id\": 12,\n    \"name\": \"Hans Meier\",\n    \"age\": 42,\n    \"height\": 1.91,\n    \"address\": {\n        \"street\": 12\n    },\n    \"tags\": [\"blue\", \"green\"]\n}\n```\n    \n**err.Error() Output**\n\n```\n\"address\": Missing keys: \"zip\"\n\"address.street\": is no string but float64\n\"name\": \"Hans Meier\" != \"Max Mustermann\"\n\"tags\": red:string(0) not included\nMissing keys: \"footsize\"\n```\n\n## Entry Points\n\n```go\nschema.Match(schema.Matcher, interface{}) error\nschema.MatchJSON(schema.Matcher, io.Reader) error\n```\n\n\n## Matchers\n\n    \"ConcreteValue\"\n        Any concrete value like: \"Name\", 12, true, false, nil\n\n    IsPresent\n        Is the value given (empty string counts as given).\n        This is essentially a wildcard in map values or array elements.\n\n    Types\n        - IsString\n        - IsInt\n        - IsFloat\n        - IsBool\n        - IsTime(format)\n\n    Map{\"key\":Matcher, ...}\n        Matches maps where all given keys and values have to match. \n        No extra or missing keys allowed.\n\n    MapIncluding{\"key\":Matcher, ...}\n        Matches maps but only checks the given keys and values and ignores extra ones.\n\n    Array(Matcher...)\n        Matches all array elements in order.\n\n    ArrayUnordered(Matcher...)\n        Matches all array elements but order is ignored.\n\n    ArrayIncluding(Matcher...)\n        Reports elements that can not be matched.\n\n    ArrayEach(Matcher)\n        Each element of the array has to match the given matcher.\n        \n    Capture(name)\n        Can be used once or more to capture values and to make sure a value stays the same \n        if it occurs multiple times in a schema. See [capture_test.go](capture_test.go).\n        Can also be used to use the captured value in future operations (e.g. multiple requests with the same id).\n        \n    StringEnum(...values)\n\n## How to write matchers\n\nTo use custom or more specialized matchers, the [schema.Matcher](schema.go#L12) interface needs to be implemented.\nEither via struct or by using `schema.MatcherFunc`\n\nTo report errors, `schema.SelfError(message)` needs to be used if the `data` itself is the problem.\n\n`schema.Error.Add(field, message)` if a subelement of the data is the problem (see `Map` and `Array`).\n```go\nvar IsTime = schema.MatcherFunc(\"IsTime\",\n    func(data interface{}) *schema.Error {\n        s, ok := data.(string)\n        if !ok {\n            return schema.SelfError(\"is no valid time: not a string\")\n        }\n\n        _, err := time.Parse(time.RFC3339, s)\n        if err != nil {\n            return schema.SelfError(\"is no valid time: \" + err.Error())\n        }\n        return nil\n    },\n)\n```\n\nTo be more generic with regard to the time format the following pattern can be used:\n\n```go\nfunc IsTime (format string) schema.Matcher\n\treturn schema.MatcherFunc(\"IsTime\",\n\t\tfunc(data interface{}) *schema.Error {\n\t\t\ts, ok := data.(string)\n\t\t\tif !ok {\n\t\t\t\treturn schema.SelfError(\"is no valid time: not a string\")\n\t\t\t}\n\n\t\t\t_, err := time.Parse(format, s)\n\t\t\tif err != nil {\n\t\t\t\treturn schema.SelfError(\"is no valid time: \" + err.Error())\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t)\n}\n```\n\n## Ideas\n\n- write Optional(Matcher) that matches if key is missing or given matcher is satisfied\n- write Combine(...Matcher) that matches if all given matchers are satisfied\n\n\n    \n## Issues\n\n**Numbers**\n\nJSON does not differ between integers and floats, ie. there are only numbers. This is why the go JSON library will always return a `float64` value if no type was specified (unmarshalling into an `interface{}` type). This requires some magic internally and can result in false positives. For very large or small integers errors could occur due to rounding errors. If something has no fractional value it is assumed to be equal to an integer (42.0 == 42).\n\n\n**Array Matchers**\n\nFor arrays there are matcher variants for including and unordered. They take the following steps:\n\n* Order the given matchers, where concrete values are matched first, then all matchers except the most generic `IsPresent`, and finally all `IsPresent` matchers. This order guarantees that the most specific values are matched first.\n* For each of the ordered matchers, verify one of the remaining values matches.\n* Keep a log of all matched values.\n\nThis will work in most of the cases, but might fail for some weird nested structures where something like a backtracking approach would be required. \n","funding_links":[],"categories":["Testing","测试","测试相关","测试相关`测试库和测试数据集生成库`","Template Engines","Testing Frameworks"],"sub_categories":["Testing Frameworks","查询语","HTTP客户端","HTTP Clients"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjgroeneveld%2Fschema","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjgroeneveld%2Fschema","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjgroeneveld%2Fschema/lists"}