{"id":675,"date":"2025-10-22T20:27:08","date_gmt":"2025-10-23T03:27:08","guid":{"rendered":"https:\/\/www.airs.com\/blog\/?p=675"},"modified":"2025-10-22T20:27:08","modified_gmt":"2025-10-23T03:27:08","slug":"json-schemas-in-go","status":"publish","type":"post","link":"https:\/\/www.airs.com\/blog\/archives\/675","title":{"rendered":"JSON Schemas in Go"},"content":{"rendered":"\n<p>In 2024 I did some work on a project at Google (<a href=\"https:\/\/pkg.go.dev\/github.com\/firebase\/genkit\/go\">the Go version of Genkit<\/a>) that used <a href=\"https:\/\/json-schema.org\/\">JSON schemas<\/a>.<\/p>\n\n\n\n<p>JSON schemas let programs specify how JSON data should be structured. Basically, you can say things like &#8216;this JSON data must have a field &#8220;name&#8221; which is a string and a field &#8220;age&#8221; which is a number.&#8217; Of course you can get much more complicated than that. JSON schemas can themselves be represented as JSON values, and there is a metaschema that verifies whether a JSON value is a valid JSON schema.<\/p>\n\n\n\n<p>For Genkit we needed Go code that could<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Read the JSON form of a JSON schema<\/li>\n\n\n\n<li>Construct a JSON schema by hand<\/li>\n\n\n\n<li>Construct a JSON schema that described a Go <code>struct<\/code> type<\/li>\n\n\n\n<li>Validate a JSON value against a JSON schema<\/li>\n<\/ul>\n\n\n\n<p>The overall Genkit system could use JSON schemas to help people enter data in the expected format, although that was implemented in TypeScript, not Go.<\/p>\n\n\n\n<p>At the time we couldn&#8217;t find one Go package that could do all of those operations, so we used a couple of packages (<a href=\"https:\/\/pkg.go.dev\/github.com\/invopop\/jsonschema\">github.com\/invopop\/jsonschema<\/a> and <a href=\"https:\/\/pkg.go.dev\/github.com\/xeipuuv\/gojsonschema\">github.com\/xeipuuv\/gojsonschema<\/a>). We converted between their data structures as needed by marshaling one implementation to JSON and unmarshaling into the other implementation.<\/p>\n\n\n\n<p>Since then Jonathan Amsterdam and others at Google have written a package that does everything necessary: <a href=\"https:\/\/pkg.go.dev\/github.com\/google\/jsonschema-go\">github.com\/google\/jsonschema-go<\/a>. However, at the time we didn&#8217;t have that.<\/p>\n\n\n\n<p>JSON schemas are moderately complex, with multiple drafts of the specification, support for cross-referencing within a schema and to external schemas, and checks like &#8220;every field that wasn&#8217;t explicitly mentioned must satisfy this subschema.&#8221; The specification is somewhat abstract and seems to have evolved over time as people have found interesting uses for schemas, especially when working in a dynamic language like JavaScript.<\/p>\n\n\n\n<p>I&#8217;ve always enjoyed the implementation of complex specifications, and it&#8217;s led me to side projects like demangling C++ identifiers (<a href=\"https:\/\/pkg.go.dev\/github.com\/ianlancetaylor\/demangle\">github.com\/ianlancetaylor\/demangle<\/a> in Go and <a href=\"https:\/\/gcc.gnu.org\/pipermail\/gcc-patches\/2003-November\/120086.html\">the first draft of the current GCC demangler<\/a> in C) and doing stack backtraces in C code (<a href=\"https:\/\/github.com\/ianlancetaylor\/libbacktrace\">github.com\/ianlancetaylor\/libbacktrace<\/a>, which had to be async signal safe and required implementing three (3) different decompression algorithms).<\/p>\n\n\n\n<p>So I started working on a JSON schema implementation on the side. It took a while, but I finally published it at <a href=\"https:\/\/pkg.go.dev\/github.com\/ianlancetaylor\/jsonschema\">github.com\/ianlancetaylor\/jsonschema<\/a>.<\/p>\n\n\n\n<p>Rather than implementing JSON schemas as a struct, as the other Go implementations do, JSON schemas are represented as a slice of keyword\/value pairs. This is somewhat more space efficient, which doesn&#8217;t matter much. It is also more efficient at validating JSON objects: rather than checking every field of the JSON schema struct, it only has to validate the keywords that are actually specified.<\/p>\n\n\n\n<p>Not using a single struct also makes it easier to implement multiple drafts of the JSON schema. The current implementation supports draft-07, draft2019-09, and draft2020-12. Adding support for more drafts should be straightforward.<\/p>\n\n\n\n<p>Now that I&#8217;ve written this package, I have no particular use for it. If other people find it useful, I&#8217;ll fix bugs and tweak the API for usability. In particular it&#8217;s a bit awkward to change any aspect of an existing JSON schema in Go code, which might be useful for some applications. It also doesn&#8217;t have full support for JSON schema annotations, as it&#8217;s not clear to me that they are useful in Go. And I&#8217;m sure there are a number of other infelicities.<\/p>\n\n\n\n<p>Let me know if you find this package useful. Happy hacking.<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In 2024 I did some work on a project at Google (the Go version of Genkit) that used JSON schemas. JSON schemas let programs specify how JSON data should be structured. Basically, you can say things like &#8216;this JSON data must have a field &#8220;name&#8221; which is a string and a field &#8220;age&#8221; which is [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6],"tags":[16,15],"class_list":["post-675","post","type-post","status-publish","format-standard","hentry","category-programming","tag-go","tag-programming"],"_links":{"self":[{"href":"https:\/\/www.airs.com\/blog\/wp-json\/wp\/v2\/posts\/675","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.airs.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.airs.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.airs.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.airs.com\/blog\/wp-json\/wp\/v2\/comments?post=675"}],"version-history":[{"count":1,"href":"https:\/\/www.airs.com\/blog\/wp-json\/wp\/v2\/posts\/675\/revisions"}],"predecessor-version":[{"id":676,"href":"https:\/\/www.airs.com\/blog\/wp-json\/wp\/v2\/posts\/675\/revisions\/676"}],"wp:attachment":[{"href":"https:\/\/www.airs.com\/blog\/wp-json\/wp\/v2\/media?parent=675"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.airs.com\/blog\/wp-json\/wp\/v2\/categories?post=675"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.airs.com\/blog\/wp-json\/wp\/v2\/tags?post=675"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}