Library / SDK
segmentio/encoding avatar
segmentio/encoding

segmentio/encoding is a drop-in JSON codec where only the import path changes

Go package containing implementations of efficient encoding, decoding, and validation APIs.

1,055 stars65 forksGoMIT

At a glance

What is it?
The segmentio/encoding package is a set of Go encoders and decoders built as drop-in replacements for the standard library, with a json subpackage that mirrors the encoding/json API, an iso8601 subpackage for allocation free date validation, and further packages for ASCII, Protobuf and Thrift. The published benchmark numbers are large, and they were measured on a single CPU model that the README names.
Who is it for?
segmentio/encoding suits a Go service that marshals and unmarshals JSON on a hot path and measures the cost, and that wants to change nothing but an import line. It does not suit a team that depends on the exact wording of standard library errors, and it is the wrong choice for a codebase that does not have a problem with the standard library.
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 last received commits 120 days 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 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The pitch is one import line

The json subpackage provides a re-implementation of the functionalities offered by the standard library's encoding/json package, with a focus on lowering the CPU and memory footprint of the code, and the exported API of the package mirrors the standard library's encoding/json package. The only change needed to take advantage of the performance improvements is the import path.

go
import (
    "github.com/segmentio/encoding/json"
)

That is the whole adoption story, and it is the design constraint the project has held itself to. A faster JSON library that requires a different API is a rewrite, while a faster JSON library behind the same API is a one line change, and the latter can be evaluated in an afternoon by swapping the import in one service and watching the numbers.

The motivation section explains why this exists at all. At Segment, the note says, there is a lot of marshaling and unmarshaling of data when sending, queuing or storing messages, and the resources that have to be provisioned on the infrastructure are directly related to the type and amount of data being processed. At the scale they operate at, the tools chosen to build programs can have a large impact on the efficiency of the systems, and it is important to explore alternative approaches when reaching the limits of the code already in use.

So the repository describes itself as experiments for Go packages for marshaling and unmarshaling data in various formats, with the aim of a high performance library and very low development and maintenance overhead, implemented as APIs usable as drop-in replacements for the default solutions.

The benchmark numbers, and the machine behind them

The CI pipeline runs benchmarks comparing the package with the standard library and other popular alternatives, and the README publishes the results, which is unusual and worth reading carefully.

Against encoding/json v1.16.2, on the codeResponse2 payload, marshalling goes from 6.40ms to 3.82ms, a 40.29% reduction, and unmarshalling from 28.1ms to 5.6ms, a 80.21% reduction. Throughput is reported as 303MB/s against 507MB/s for marshalling and 69.2MB/s against 349.6MB/s for unmarshalling.

The allocation figures are the more interesting ones, because allocation is usually what limits a JSON service. Unmarshalling drops from 1.80MB per operation to 0.02MB, a 99.14% reduction, and the allocation count from 76.6k to 0.1k, a 99.92% reduction. Marshalling is already allocation free in the standard library for this payload, and stays that way.

The comparison against json-iterator/go v1.1.10 shows the same shape: marshalling from 6.19ms to 3.82ms and unmarshalling from 8.52ms to 5.55ms, with marshalling allocations going to zero and unmarshalling allocations falling from 1.05MB to 0.02MB.

Two things to note before repeating any of these numbers. The benchmarks were run on a Core i9-8950HK CPU at 2.90GHz, which the README states, and the payload is a single named benchmark case, so the numbers describe that case on that machine. The CI runs the comparison on every change, which is what keeps the published table honest rather than a one-off measurement.

The Makefile shows the mechanics, with a bench-simple target that runs the standard library and this package separately and pipes both into benchstat for the comparison table, and a bench-master target that checks out the upstream implementation to compare against a previous version of the package itself.

The compatibility cost is the error message

The README is direct about what is not compatible. Although the package aims to be a drop-in replacement of encoding/json, it does not guarantee the same error messages. It will error in the same cases as the standard library, but the exact error message may be different.

That sentence is the most important line in the document for an adopter, because it defines the boundary of the word drop-in. The set of inputs that fail is the same, so the control flow of your program is the same, but the text you print, log or match on is not.

The failure mode is specific. Code that compares an error string, or a test that asserts on an error message, breaks when the package is swapped in. Code that wraps the error and reports it to a human still works. In a service, the first category is usually a small number of assertions in a test suite, and in a library that exports errors to its own callers, it is a public API change.

It is worth checking whether your codebase does either, and the alternative is to treat the swap as the change it is, with a commit of its own and a look at the diff in the error output, rather than as a dependency bump.

The upstream dependency list also gives a clue about how the speed is achieved. The module requires github.com/segmentio/asm, and the Makefile has a test target that runs the whole tree with the purego build tag, which is the portable path with the assembly path disabled. A library that ships an assembly implementation and a pure Go fallback is making a deliberate trade, and a deployment on a platform where the assembly is unsupported is the case to think about.

iso8601 exists because failed time.Parse is expensive

The iso8601 subpackage exposes APIs to efficiently deal with string representations of ISO8601 dates, and the reasoning behind it is the most useful part of the documentation.

The problem is described concretely. Data formats like JSON have no syntax for dates, so they are usually serialised and represented as a string value. In their experience, they often have to check whether a string value looks like a date, and either construct a time.Time by parsing it or simply treat it as a string. The check can be done by attempting to parse the value and falling back to the raw string if it fails, but the happy path for time.Parse is fairly efficient while constructing errors is much slower and has a much bigger memory footprint.

So the fix is a validation function that answers the question without allocating. The fast ISO8601 validation functions cause no heap allocations, and adding a validation step to determine whether the value is a date representation or a simple string reduced CPU and memory usage by 5% in some programs that were doing time.Parse calls on very hot code paths.

Five percent is a modest number, and it is honest. This is not a feature that changes an architecture, it is a change that matters in a service parsing millions of small JSON documents where the error path is the common path, because most fields are not dates.

The lesson generalises past dates. The expensive part of using a parser as a validator is not the successful parse, it is the error object created for every field that was never a date, and a validator that never fails expensively removes that cost entirely.

ASCII, Protobuf and Thrift live in the same tree

The repository holds more than JSON. The top level has an ascii/ directory, a benchmarks/ directory, an internal/ directory, iso8601/, json/, proto/ and thrift/.

The Go module is remarkable for its size, with a single direct dependency, github.com/segmentio/asm v1.1.3, and one indirect entry for golang.org/x/sys. The README states that the package has no dependencies outside of the core runtime of Go, which is a constraint that a lot of infrastructure code would like to keep and few libraries manage.

The Makefile shows the test matrix, with a test target that runs test-ascii, test-json, test-json-bugs, test-proto, test-iso8601, test-thrift and test-purego, each with coverage and the race detector where it applies, plus a dedicated target for the json/bugs/... tree, which is a regression suite for reported defects rather than a normal test package.

The benchmarking setup is also part of the repository rather than something done ad hoc, with the benchmarks/ directory holding the in-depth benchmark material and the Makefile providing benchstat based comparison tooling that it installs on demand.

For an adopter, the practical consequence of the no-dependency rule is that adding this package does not change the dependency graph of your service, which in a regulated or audited environment is often the deciding factor.

The maintenance schedule is stricter than the licence

The Requirements and Maintenance Schedule section is short and it constrains how the project is used.

The package has no dependencies outside of the core runtime of Go and requires a recent version of Go. It follows the same maintenance schedule as the Go project, which means that issues relating to versions of Go which are not supported by the Go team, or versions of this package which are older than one year, are unlikely to be considered.

That last clause is the one to notice. A package that is a drop-in replacement for a standard library is only useful if it keeps up with the standard library, and the project is explicit that it will not carry old versions indefinitely. A team that pins an old Go release therefore has to plan for the encoding package being a moving target, or for pinning a version it is on its own with.

There is a second instruction that catches people out when they upgrade dependencies. The project has fuzz tests which are not a runtime required dependency but will be pulled in when running go mod tidy, and the README asks that these go.mod updates are not included in change requests. In other words, the fuzzing corpus is a development dependency that the maintainers do not want to leak into every downstream go.mod.

The licence is MIT, the repository is not archived, and the last push was on 2026-06-25. The recent releases are v0.5.4 from 2026-03-12, v0.5.3 from 2025-07-29 and v0.5.2 from 2025-07-18, whose release note is that it addresses a performance regression in json decoding. That last one is worth reading as a signal, because a drop-in replacement that regresses is exactly the risk the API compatibility promise does not cover.

Editorial conclusion

segmentio/encoding suits a Go service that marshals and unmarshals JSON on a hot path and measures the cost, and that wants to change nothing but an import line. It does not suit a team that depends on the exact wording of standard library errors, and it is the wrong choice for a codebase that does not have a problem with the standard library. Verify first that the API surface you use is mirrored exactly, accept that the error messages will differ from encoding/json, and read the purego build tag before deploying, since the assembly path and the portable path are not the same code.

Frequently asked questions

How do I use segmentio/encoding as a replacement for encoding/json?

The exported API of the json subpackage mirrors the standard library's encoding/json, so the only change needed is the import path, from encoding/json to github.com/segmentio/encoding/json. The README notes that it does not guarantee the same error messages, even though it errors in the same cases.

What performance improvement does segmentio/encoding claim for JSON?

Against encoding/json v1.16.2 on the codeResponse2 payload and a Core i9-8950HK at 2.90GHz, the README reports unmarshalling time falling from 28.1ms to 5.6ms and unmarshalling allocations from 1.80MB to 0.02MB per operation, with marshalling time falling from 6.40ms to 3.82ms. The CI runs these comparisons on every change.

What is the iso8601 subpackage for?

It provides fast ISO8601 validation functions that cause no heap allocations, so a program can check whether a string looks like a date without paying for the error objects that failed time.Parse calls create. The README reports a 5% reduction in CPU and memory in some programs using time.Parse on hot paths.

Does segmentio/encoding have any runtime dependencies?

The README states that the package has no dependencies outside of the core runtime of Go. The go.mod lists github.com/segmentio/asm as the only direct requirement, and there is a purego build tag used by the test suite to exercise the portable path.

How long will segmentio/encoding support old versions?

It follows the same maintenance schedule as the Go project, so issues relating to Go versions the Go team no longer supports, or to versions of this package older than one year, are unlikely to be considered.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. segmentio/encoding on GitHub
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/segmentio-encoding.svg)](https://hysenlabs.com/projects/segmentio-encoding)

Was this useful?