CLI tool
TomWright/dasel avatar
TomWright/dasel

Dasel: one selector language across eight data formats

Unified querying, transformation, and modification of JSON, TOML, YAML, XML, INI, HCL, KDL and CSV.

8,072 stars183 forksGoMIT

At a glance

What is it?
Dasel is a Go command-line tool and library that queries, modifies and converts JSON, YAML, TOML, XML, CSV, INI, HCL and KDL with the same selector syntax. Five formats are backed by an external library and three are not, the YAML library is pinned at a release candidate, and the Go install path tracks the master branch.
Who is it for?
Reach for Dasel when you need one selector language across many structured formats, especially in shell pipelines where a separate tool per format is not workable and where converting a file is as common as reading a value.
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 12 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 November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • TomWright/dasel8,072 ★ · +33
Star history
Repository2026-09-222026-11-14
TomWright/dasel8k8.1k

33 Stars in the last 53 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The package layout is the pipeline: parsing, model, selector, execution

Dasel is short for Data-Select, and it is described as both a command-line tool and a library for querying, modifying and transforming data structures. The pitch is a single selector syntax that works in every supported format, so the same expression reads JSON, YAML, TOML, XML, CSV and KDL without a per-format syntax.

The repository root shows how that is arranged. Four directories sit alongside the command entry point: `parsing/`, `model/`, `selector/` and `execution/`. Read as a pipeline, that is parse the input format into one internal representation, carry a model of the data, resolve a selector against it, then execute the resulting action.

The library surface is the root package. `api.go` sits at the top level next to `api_test.go` and `api_example_test.go`, and `cmd/` holds the command line front end. The module path is `github.com/tomwright/dasel/v3`, so version 3 is a distinct import path from earlier majors rather than a compatible upgrade in place.

The consequence is that the unified syntax is not a veneer over the CLI. It is the same selector engine the library exposes, so a script and a Go program resolve expressions identically.

The Go install path follows master, not a version tag

Three installation routes are given, and one of them has a detail worth reading twice.

sh
brew install dasel
sh
go install github.com/tomwright/dasel/v3/cmd/dasel@master

That middle command ends in `@master`, not `@v3.11.2`. `go install` resolves the named ref, so the binary it produces is whatever the default branch holds at the moment you run it, and there is no version step between one day and the next.

The third route is a prebuilt binary from the Releases page, available for Linux, macOS and Windows, and there is a fourth pointer for everything else in the installation docs.

The consequence is a reproducibility split. Homebrew and the release binary give you a fixed artifact, while the Go route gives you a moving one. That is convenient for trying the tool and wrong for a pinned pipeline, where the same script should not silently change behaviour when someone else's commit lands.

The YAML library is pinned at a release candidate

There are eleven direct requirements in go.mod, and one of them is not a final release.

go
require (
	github.com/alecthomas/kong v1.14.0
	github.com/charmbracelet/bubbles v1.0.0
	github.com/charmbracelet/bubbletea v1.3.10
	github.com/charmbracelet/lipgloss v1.1.0
	github.com/goccy/go-json v0.10.6
	github.com/google/go-cmp v0.7.0
	github.com/hashicorp/hcl/v2 v2.24.0
	github.com/pelletier/go-toml/v2 v2.4.3
	github.com/zclconf/go-cty v1.17.0
	go.yaml.in/yaml/v4 v4.0.0-rc.7
	gopkg.in/ini.v1 v1.67.0
)

The YAML library is `go.yaml.in/yaml/v4` at `v4.0.0-rc.7`, a release candidate at a major version four. That is the parser standing between your input and every YAML operation, including the recursive descent and search examples in the README.

Also notable is `github.com/charmbracelet/bubbles` at exactly `v1.0.0`, which is the first release of a new major, alongside bubbletea and lipgloss.

The consequence is that a data tool, whose whole job is parsing what other systems wrote, has its YAML handling resting on an unreleased parser. Read the version before you trust a conversion on untrusted input, and test the edge cases your own files contain.

Five formats have a library behind them and three do not

Line the advertised formats up against the direct requirements and a pattern appears.

JSON has `goccy/go-json`. YAML has `go.yaml.in/yaml/v4`. TOML has `pelletier/go-toml/v2`. INI has `gopkg.in/ini.v1`. HCL has `hashicorp/hcl/v2` together with `zclconf/go-cty`, which is the value and type library HCL is built on.

Five formats, five external implementations. The remaining five direct requirements are not parsers at all: `alecthomas/kong` for command-line parsing, the three Charm libraries for a terminal interface, and `google/go-cmp` for comparison.

Now look at what the feature list claims to support: JSON, YAML, TOML, XML, CSV, HCL, INI and KDL. Three of those, XML, CSV and KDL, have no library anywhere in the direct requirements.

The consequence is that go.mod alone does not show where their parsing comes from; XML and CSV may use the Go standard library, which go.mod does not list. If your data includes XML, CSV or KDL, test those formats on your own files before you rely on the tool.

Roughly twenty indirect packages exist to draw a terminal

The second require block in go.mod is where the dependency story turns. Alongside the usual entries sit a cluster of terminal rendering packages, marked indirect.

They include `charmbracelet/colorprofile`, `charmbracelet/x/ansi`, `charmbracelet/x/cellbuf` and `charmbracelet/x/term`, then `clipperhouse/displaywidth`, `clipperhouse/stringish` and `clipperhouse/uax29/v2` for measuring and slicing text, `lucasb-eyer/go-colorful` and `mattn/go-isatty` and `mattn/go-runewidth` and `mattn/go-localereader`, `muesli/ansi`, `muesli/cancelreader`, `muesli/termenv` and `rivo/uniseg`, plus `xo/terminfo`, `mitchellh/go-wordwrap`, `erikgeiser/coninput` and `aymanbagabas/go-osc52/v2`.

Two of them also point at user-facing capability rather than plumbing: `atotto/clipboard` for the system clipboard and the OSC 52 escape sequence package.

The consequence is that a tool which reads a document and writes a value to a pipe carries a full terminal rendering stack in its module graph. If you vendor dependencies or audit supply chain, this is where the count lives.

A container image built with defaults reports its version as master

The Dockerfile is small and readable, and one build argument default carries a consequence.

dockerfile
ARG GOLANG_VERSION=1.25.0
ARG TARGET_BASE_IMAGE=debian:bookworm-slim
FROM golang:${GOLANG_VERSION} AS builder

ARG MAJOR_VERSION=v3
ARG RELEASE_VERSION=master
ARG CGO_ENABLED=0

COPY . .

RUN go build -o /dasel -ldflags="-w -s -X 'github.com/tomwright/dasel/${MAJOR_VERSION}/internal.Version=${RELEASE_VERSION}'" ./cmd/dasel

FROM ${TARGET_BASE_IMAGE}

COPY --from=builder --chmod=755 /dasel /usr/local/bin/dasel

ENTRYPOINT ["/usr/local/bin/dasel"]
CMD ["--help"]

`RELEASE_VERSION` defaults to `master`, and that value is compiled into the binary through a linker flag targeting `internal.Version`. So a container built with no arguments reports its own version as `master` rather than as a number, and the only way to get a real version string is to pass the argument.

One smaller detail: the path passed to the linker flag expands with `MAJOR_VERSION`, so it becomes `github.com/tomwright/dasel/v3/internal.Version`, matching the module path exactly.

The consequence is operational rather than structural. If you run this image in a pipeline that logs tool versions for reproducibility, override the argument or you will get `master` in the record forever.

A bare selector prints one value, and --root prints the document

By default Dasel evaluates the final selector and prints the result. That single sentence explains most of what surprises people.

sh
echo '{"foo": {"bar": "baz"}}' | dasel -i json 'foo.bar'
# Output: "baz"

An assignment looks identical in shape.

sh
echo '{"foo": {"bar": "baz"}}' | dasel -i json 'foo.bar = "bong"'
# Output: "bong"

The output is the assigned value, not the document. Passing `--root` changes that, emitting the whole structure with the change applied, and the same flag is what lets a transformation over an array be seen in full.

sh
echo '[1,2,3,4,5]' | dasel -i json --root 'each($this = $this*2)'

So `each` reads the previous value and rewrites it, doubling every element.

The consequence is that when data arrives on standard input, nothing is rewritten in place and the printed value can look like a successful edit when it is only a returned result. The modify-in-place behaviour applies to files, and on a pipe you choose deliberately between the value and the document.

Recursive descent and search look alike and do different things

Two of the built-in selectors both return collections, and they work on different principles.

Recursive descent uses `..` and searches all nested objects and arrays for a matching key or index.

sh
echo '{"foo": {"bar": "baz"}}' | dasel -i json '..bar'

That example returns an array containing one string, even though exactly one key matched.

Search is a function and finds all values matching a condition anywhere in the structure.

sh
echo '{"foo": {"bar": "baz"}}' | dasel -i json 'search(bar == "baz")'

That one returns the enclosing object rather than the value, again wrapped in an array.

The consequence is twofold. First, `..` matches on name and `search` matches on condition, so choosing between them is a decision about what you are looking for rather than about how deeply you want to look. Second, both wrap results in an array, which means a script that assumes a scalar needs to unwrap it even in the common case of a single match.

Format conversion sits alongside these. Passing `-i` for the input format and `-o` for the output turns a read into a rewrite, which is the shortest path in the tool for reserialising a file.

Editorial conclusion

Reach for Dasel when you need one selector language across many structured formats, especially in shell pipelines where a separate tool per format is not workable and where converting a file is as common as reading a value. Do not reach for it expecting a dependency-light filter, because the module graph carries roughly twenty indirect packages for terminal rendering, and do not assume XML, CSV and KDL behave like the formats backed by an external library, since none of those three has a third-party library among go.mod's direct requirements. Before you pin a version, remember that the Go install path follows the master branch rather than a tag, that the YAML library is pinned at a release candidate, and that a container image built without the release version argument reports itself as master rather than as a number.

Frequently asked questions

What does dasel mean?

Dasel is short for Data-Select. It is a command-line tool and Go library for querying, modifying and transforming data structures including JSON, YAML, TOML, XML, CSV and KDL.

How do I install dasel?

Three routes are given: brew install dasel for macOS and Linux, go install github.com/tomwright/dasel/v3/cmd/dasel@master, or a prebuilt binary for Linux, macOS and Windows from the Releases page. Note that the Go route follows the master branch rather than a version tag.

How do I use dasel to convert JSON to YAML?

Pass the input format with -i and the output format with -o. The example given is cat data.json | dasel -i json -o yaml, and the same two flags work across every supported format.

Does dasel pin any dependencies at release candidates?

Yes. go.mod requires go.yaml.in/yaml/v4 at v4.0.0-rc.7, which is the YAML parser. The module itself declares go 1.25 as its language version.

Can I use dasel as a Go library?

Yes, the feature list names library support for Go projects. The module path is github.com/tomwright/dasel/v3, so version 3 lives under a major-version suffixed path, and the API is exposed from the root package with api.go and its example test at the repository root.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. TomWright/dasel 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/tomwright-dasel.svg)](https://hysenlabs.com/projects/tomwright-dasel)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/tomwright-dasel.svg)](https://hysenlabs.com/projects/tomwright-dasel)

Was this useful?