objx: a typed accessor for Go maps that came from JSON
Go package for dealing with maps, slices, JSON and other data.
At a glance
- What is it?
- objx is a small Go package that wraps a map[string]any so you can pull a value out of it with dot and array notation and get a typed result without writing the assertions yourself. What makes it more than a convenience is the accessor surface: a value carries both predicates that test its type and accessors that extract it with a fallback.
- Who is it for?
- Use objx when your Go code has to read JSON or an otherwise untyped map and you want the type handling to be explicit rather than scattered across assertion blocks, because the accessor design keeps the fallback next to the read. Do not reach for it where a struct and encoding/json already do the job, because the point of objx is to handle data whose shape you do not know at compile time.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- 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 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What objx.Map actually is
Objx is a Go package for dealing with maps, slices, JSON and other data, and the whole design rests on one type. Objx provides the objx.Map type, which is a map[string]any that exposes a powerful Get method, among others, that lets you get at data inside the map without worrying too much about type assertions, missing data or default values. The critical detail is that objx.Map is a defined type whose underlying type is map[string]any rather than a distinct structure, so it is a map with methods on it. That is why ranging over it does what you would expect from a map, and why the package can be introduced into code that already handles a map without a rewrite of the surrounding logic.
Creating a map and the Must convention
You create a map by calling one of the objx functions, and the README's example is the JSON path.
m, err := objx.FromJSON(json)There is a naming convention that matters more than it looks. Any method or function with the Must prefix will panic if something goes wrong, while the rest are optimistic and try to figure things out without panicking. So there is a deliberate split between the two error strategies: use Must when a missing or malformed value means the program cannot continue, and use the plain variant when it does not. The plain constructors return an error, as FromJSON does, and the difference in behaviour between the two families is the difference between a checked failure and an immediate panic.
Dot and array notation for nested paths
Access uses a path string rather than a chain of map lookups.
m.Get("places[0].latlng")That path walks into a nested map by key and into a slice by index, which is the shape most JSON documents have. The reason a path language is worth having is that the alternative is a chain of assertions where each level has to be checked for presence and for the right type before the next level can be indexed, and that chain is where the nil panics live in code that parses untrusted input. The README notes that the package also provides many useful methods for iterating, manipulating and selecting slice data when a value holds a list.
Two ways to read the value: test it, then assume it
Once you have a value, there are two ways to use it. The Is* methods determine the type without extracting anything, so you can branch on the shape of the data.
if m.Get("code").IsStr() { // Your code... }Or you can assume the type and use one of the strong type methods to extract the real value.
m.Get("code").Int()If there is no value there, or if it is the wrong type, a default value is returned, and you can be explicit about the default by passing it. The two-step design is the useful part. The Is* predicates let you handle the case where the document does not have the shape you expected, and the typed accessors let you state the shape you did expect without repeating the assertion. The fallback behaviour means a missing key does not panic, which is what makes it safe to point at an arbitrary path in a document from an API you do not control.
The reading example, with a nickname fallback
The README's worked example is short and shows the fallback in use.
// Use MustFromJSON to make an objx.Map from some JSON
m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`)
// Get the details
name := m.Get("name").Str()
age := m.Get("age").Int()
// Get their nickname (or use their name if they don't have one)
nickname := m.Get("nickname").Str(name)The last line is the pattern worth taking away. A missing nickname yields the name rather than an empty string, and the fallback is written at the point of use, so the reader of that line sees the default without having to look it up in the package documentation. The type-specific accessors each come in that form, with the default supplied as an argument when the zero value is not the one you want.
The repository is mostly generated code
The top-level file list explains how the accessor surface is built and maintained. There are hand-written files for the map type itself in map.go, the accessors in accessors.go, conversions in conversions.go, mutations in mutations.go, the value wrapper in value.go, and the type-specific methods in type_specific.go. Alongside them are type_specific_codegen.go and a codegen directory, which is where the generated part comes from, and each of those has a test file with a matching name, including type_specific_codegen_test.go. There is also a security.go with a security_test.go, and a SECURITY.md at the root. The practical consequence is that the accessor list is not written by hand and is not going to grow by hand either, so a pull request that adds a type is more likely to be a change to the generator than to the generated file. The repository also uses a golangci configuration, a Taskfile and a devcontainer setup.
Versioning, releases and the Go requirement
The project is on a zero-major line with three releases in the space of two days: v0.6.0 and v0.7.0 on 2026-10-13 and v0.8.0 on 2026-10-14, with the last push to the repository on 2026-10-15. A burst like that is what a generator change looks like when it regenerates a large surface, and it is the reason to read the release notes before bumping the version in a dependency file. The module file is minimal, requiring go 1.25 and no third-party dependencies at all, which is a pleasant property for a package that ends up in a dependency graph of a service. The README states that the project supports the three recent major Go versions, which is a promise that tracks the toolchain rather than the library, since there is nothing to compile beyond the standard library.
Where objx is the wrong choice
Four limits apply. It is a map access library, not a decoder, so it does not remove the question of whether your document matches your expectation, it only makes reading it less error prone. It carries no dependencies, which is good for supply chain reasons, but it also means there is no schema validation behind it, so a document that changes shape produces a wrong typed value rather than an error. The Must variants panic, which is the right default for a program that cannot continue and the wrong default inside a request handler serving many callers. And the accessor surface is wide enough to be a temptation: reading an arbitrary path with a fallback is convenient, and it is also a way of writing code that quietly accepts malformed input forever, because the fallback always returns something. If your data has a known shape, a struct and encoding/json give you compile-time checking that objx is deliberately giving up.
Editorial conclusion
Use objx when your Go code has to read JSON or an otherwise untyped map and you want the type handling to be explicit rather than scattered across assertion blocks, because the accessor design keeps the fallback next to the read. Do not reach for it where a struct and encoding/json already do the job, because the point of objx is to handle data whose shape you do not know at compile time. Verify first that the accessor you need exists in the version you pin, since a good part of the surface is generated and the release notes move with it, that your Go version satisfies the module's requirement of 1.25, and whether you want the Must variants, which panic on failure, at all in a request path.
Frequently asked questions
What is objx in Go?
It is a package for dealing with maps, slices, JSON and other data. Its core is the objx.Map type, a map[string]any with methods, whose Get method reaches into nested data without the caller writing type assertions, missing-data handling and default values by hand.
How do I install objx and update it?
Use go get github.com/stretchr/objx to install it, and go get github.com/stretchr/objx@latest to move to the latest version. The project supports the three recent major Go versions, and the module file requires go 1.25.
What is the difference between MustFromJSON and FromJSON?
Any method or function with the Must prefix panics if something goes wrong, while the rest are optimistic and try to work things out without panicking. So FromJSON returns an error and MustFromJSON panics on failure.
How do I read a nested value with objx?
Use a path with dot and array notation, for example m.Get("places[0].latlng"). The path walks into nested maps by key and into slices by index.
How do I get a default value from objx?
The typed accessors return a default when the value is missing or of the wrong type, and you can pass the default explicitly, so m.Get("nickname").Str(name) falls back to the name when there is no nickname.
Official sources
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.
[](https://hysenlabs.com/projects/stretchr-objx)Was this useful?