Library / SDK
miku/zek avatar
miku/zek

zek: a Go struct generator for XML documents you read rather than write

Generate a Go struct from XML.

821 stars63 forksGoGPL-3.0

At a glance

What is it?
zek is a prototype command line tool that turns an XML document into a single Go struct, inferring the shape from a sample instead of a schema, with no XSD or DTD required. It was written at Leipzig University Library to shorten the distance from raw XML to accessible data in Go, and its own README is candid about its limits.
Who is it for?
Adopt zek when you have an XML document from somewhere else, no schema for it, and you want a Go struct in the next five minutes so you can start reading the data. Do not adopt it for writing XML, for recursive structures, or when you need typed fields, because the README states the output is best for reading, that recursive types are unsupported, and that without a schema everything is a string.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A struct from a sample, not from a schema

zek is a prototype for creating a Go struct from an XML document, and the whole tool is one pipeline: read XML, infer a shape, print a Go type.

The README shows the entire workflow in two commands. You pipe an XML file into the binary and it emits a type:

bash
curl -s https://raw.githubusercontent.com/miku/zek/master/fixtures/e.xml | zek -e

The output is a single Go struct with the XMLName field, a chardata field for the element text, fields for attributes, and nested anonymous structs for child elements. Slices appear where a child element repeats, and each field carries a comment with an example value taken from the input, which is what makes the generated type readable rather than merely correct.

The project was developed at Leipzig University Library to shorten the time it took to go from raw XML to a struct that lets Go programs access the XML data, and the generated type is described as working best for reading XML. For creating XML, the README points elsewhere. It was also shown as a lightning talk about struct generators at GoLab 2018.

Two ways to try it need no installation at all. There is a WASM build online at xml-to-go.github.io, credited to YaroslavPodorvanov, and a second web version at blog.kowalczyk.info/tools/xmltogo, credited to kjk.

One struct keeps the representation compact

The design choice that defines the output is that everything becomes one struct. The list of upsides in the README starts with the practical ones: it works fine for non-recursive structures, it does not need an XSD or a DTD, and it is relatively convenient to access attributes, children and text.

The single struct decision follows from that. Rather than a named type per element level, the generator emits one type whose fields are either scalars, slices, or inline anonymous structs, so the whole document shape is visible in one screen of Go source. The README lists that as an upside in its own right, noting that it makes for a quite compact representation, and the sample output shows it: a channel struct holding an item slice, each item holding a guid struct, all anonymous and all in one type declaration.

Two other upsides are worth pulling out. The generated fields include comments with examples, so the type doubles as a description of what the feed actually contains. And the tool performs schema inference across multiple files, which means you can point it at a directory of samples rather than a single document and get a struct that reflects the union of what you have seen.

The interface is described simply as a user interface, and the option set in the usage output is short enough to read in a minute.

No XSD, no DTD, and no type inference

The absence of schema support is framed as a feature and it is a real trade.

A schema driven generator, given an XSD, produces typed fields because the schema tells it the types. zek deliberately does not need a schema, which means it works on the many real feeds that arrive with no XSD attached, and it means the types are guesses.

The README states this plainly under the downsides: there is no type inference and everything is accessible as string, adding the parenthetical that without a schema type inference may fail if the type guess is wrong. So the field that holds a numeric identifier or a price arrives as a string, and the tool may in some cases guess something it should not have.

The other stated downside is structural. There is no support for recursive types, and the README compares the situation to the Russian Doll strategy for nested XML. A document whose elements contain elements of the same name cannot be represented by the generated struct, which is a real limit for catalogue style feeds where an item can contain related items.

And the project labels itself honestly at the top of the downsides list: experimental, early, buggy, unstable prototype. That is the maintainer's own assessment, and it is the sentence to keep in mind when deciding how much of your pipeline to rest on it.

Why reading works and writing does not

The README reproduces a note from the Go standard library that explains the fundamental problem better than any summary could. The quote is that mapping between XML elements and data structures is inherently flawed, because an XML element is an order-dependent collection of anonymous values while a data structure is an order-independent collection of named values.

That is the whole argument for a one directional tool. Reading an XML document into a struct is a lossy but workable mapping, because you accept that some ordering and some anonymity are dropped in exchange for named access to the values. Going the other way, turning a struct back into XML, requires deciding what the element order should be and what to call the unnamed bits, and a generator that inferred its shape from a sample has no reliable answer for either.

The generated type reflects that asymmetry. The chardata field collects the element's text, attributes become separate fields with the attr option in the tag, and repeated children become slices. That is a reading model. If you need to produce XML with a particular element order, or produce XML that round trips exactly, the README's advice is to use something else, and it links to an XML utilities list for that purpose.

The related projects section makes the same point from the other direction, listing JSONGen, XMLGen, chidley and go-xmlstruct as projects in the same space without describing them, and pointing to the awesome-go list of XML utilities.

Installing: go install, packages and the AUR

The primary installation route is the standard Go tool install command:

bash
go install github.com/miku/zek/cmd/zek@latest

That works because the repository is a normal Go module. The module path is github.com/miku/zek and the go directive is 1.25.0, so you need a recent Go toolchain. The module has only two direct dependencies, sethgrid/pester at version 1.2.0 and golang.org/x/net at v0.53.0, with golang.org/x/text pulled in indirectly, which is a light dependency footprint for a code generator.

Binary packages are also published. Debian and RPM packages are attached to the releases page, and the project is in the Arch User Repository as zek-bin.

The build system behind those packages is visible in the Makefile. The version is stamped into the binary at link time with a -ldflags flag setting github.com/miku/zek.Version, the test target runs the suite with coverage, the man page is generated from the Markdown source with pandoc, and the Debian and RPM packages are built with nfpm from nfpm.yaml. Releases go out through goreleaser, and the repository carries .goreleaser.yml, .build.yml and a .gitlab-ci.yml for the pipeline.

The repository layout is flat and readable for a small tool: cmd/ for the command, the core logic in node.go, stack.go, io.go and structwriter.go, fixtures/ with sample XML, testdata/, examples/ with the misc and oai examples, and docs/.

Flags that decide the shape of the generated type

The usage output is short enough to read in full. The options shown are a fixed banner string with -B, described as useful for CI, so the generated file does not carry a timestamp and diffs stay stable. -C emits a less compact struct, which is the opposite trade from the default and useful when the generated type is meant to be edited by hand. -F skips formatting.

The two options with real semantic weight are -I, which uses verbatim innerxml instead of chardata, and -P, which writes the struct out within a package of the given name. The first matters when the element content is mixed content that must be preserved as raw XML rather than reduced to a text field. The second turns the output from a snippet you paste into a file into a compilable package.

There is also -S with an integer argument, which reads at most that many tags, approximately, with zero meaning no limit. On a very large document that is the flag that keeps a run from taking forever, at the cost of inferring the shape from a sample of the tags rather than the whole document.

One caveat belongs here. The generated banner includes a generation timestamp and the generating user and host, as the sample output shows, which is exactly what the -B option exists to suppress. For a generated file that is checked into a repository, that flag is the difference between a clean diff and a diff that changes on every run.

Where zek is the wrong tool

Three cases are clear from the README itself.

If you need to write XML, zek is the wrong tool. The generated struct works best for reading, and for producing XML you should use something else, as the README says, pointing to the awesome-go XML utilities list.

If your document is recursive, there is no output. Recursive types are unsupported, and no flag changes that, because the limitation is in the representation the generator produces rather than in how it samples the input.

If you need typed fields, you need a schema. Without type inference everything arrives as a string, and the README warns that a wrong guess can be worse than no guess, which means a feed whose fields are typed in the XSD you do not have will not get typed fields out of zek.

On top of those, the practical caveat is the maturity. The project calls itself an experimental, early, buggy, unstable prototype, and it is distributed under GPL-3.0 with the licence text in the LICENSE file. The licence matters for a code generator whose output you commit to your own repository, since the generated file is your file but the tool that produced it carries copyleft terms.

For what it is good at, the picture is favourable. The project badge marks it as active, the last push was on 2026-04-23 and the most recent release is v0.1.31 from the same day, so the tool is being kept working even though the README tells you not to start new work with it.

Editorial conclusion

Adopt zek when you have an XML document from somewhere else, no schema for it, and you want a Go struct in the next five minutes so you can start reading the data. Do not adopt it for writing XML, for recursive structures, or when you need typed fields, because the README states the output is best for reading, that recursive types are unsupported, and that without a schema everything is a string. Verify three things first: that your sample document is representative of the whole feed, since the schema is inferred from it and a field missing from the sample becomes a missing field in the struct; whether the fields you care about need typing, because a wrong guess is worse than a string under the tool's own account of type inference; and that the licence suits you, since it is GPL-3.0. The licence text is in the LICENSE file, the last push was on 2026-04-23 and the latest release is v0.1.31 dated 2026-04-23.

Frequently asked questions

What does zek do?

It is a prototype tool for creating a Go struct from an XML document, inferring the structure from a sample of the XML without needing an XSD or DTD. It was developed at Leipzig University Library to shorten the time from raw XML to a struct that lets Go programs access the data.

How do I install zek?

Run go install github.com/miku/zek/cmd/zek@latest, which installs the command from the Go module path. Debian and RPM packages are attached to the releases page, and the project is also in the Arch User Repository as zek-bin.

Does zek support recursive XML structures?

No. The README lists no support for recursive types as one of the downsides and compares the situation to the Russian Doll strategy for nested XML. It also states that there is no type inference, so without a schema every value is accessible as a string.

Can zek generate XML as well as read it?

The README says the resulting struct works best for reading XML and suggests using something else to create XML, linking to an awesome-go list of XML utilities. The generated type is a reading model, with a chardata field for text and attribute fields for attributes.

What licence is zek under?

GPL-3.0, with the licence text in the LICENSE file at the repository root. The project is a Go module requiring Go 1.25.0, with dependencies on sethgrid/pester and golang.org/x/net.

Official sources

  1. Issues
  2. License: GPL-3.0
  3. miku/zek on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/miku-zek.svg)](https://hysenlabs.com/projects/miku-zek)

Was this useful?