Tree is a simple structure for dealing with dynamic or unknown JSON/YAML in Go.
- Parses json/yaml of unknown structure to get to nodes with fluent interface.
- Syntax similar to Go standard and map and slice.
- Find function can be specified the Query expression.
- Edit function can be specified the Edit expression.
- Bundled 'tq' that is a portable command-line JSON/YAML processor.
- Placeholders in query.
- Merging nodes.
tree.Map{
"ID": tree.ToValue(1),
"Name": tree.ToValue("Reds"),
"Colors": tree.ToArrayValues("Crimson", "Red", "Ruby", "Maroon"),
}
{
"ID": 1,
"Name": "Reds",
"Colors": ["Crimson", "Red", "Ruby", "Maroon"]
}
ID: 1
Name: Reds
Colors:
- Crimson
- Red
- Ruby
- Maroon
func ExampleMarshalJSON() {
group := tree.Map{
"ID": tree.ToValue(1),
"Name": tree.ToValue("Reds"),
"Colors": tree.ToArrayValues("Crimson", "Red", "Ruby", "Maroon"),
}
b, err := json.Marshal(group)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b))
// Output:
// {"Colors":["Crimson","Red","Ruby","Maroon"],"ID":1,"Name":"Reds"}
}
func ExampleUnmarshalJSON() {
data := []byte(`[
{"Name": "Platypus", "Order": "Monotremata"},
{"Name": "Quoll", "Order": "Dasyuromorphia"}
]`)
var animals tree.Array
err := json.Unmarshal(data, &animals)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", animals)
// Output:
// [map[Name:Platypus Order:Monotremata] map[Name:Quoll Order:Dasyuromorphia]]
}
Tree may works on other parsers those has compatible with "encoding/json" or "gopkg.in/yaml.v2". See examples directory.
For example, Dynamic JSON in Go shows an example of using json.RawMessage.
It may be simpler to use tree.Map instead of json.RawMessage.
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/jarxorg/tree"
)
const input = `
{
"type": "sound",
"msg": {
"description": "dynamite",
"authority": "the Bruce Dickinson"
}
}
`
type Envelope struct {
Type string
Msg tree.Map
}
func main() {
env := Envelope{}
if err := json.Unmarshal([]byte(input), &env); err != nil {
log.Fatal(err)
}
fmt.Printf("%#v\n", env)
fmt.Printf("%#v\n", env.Msg.Get("description"))
// Output:
// main.Envelope{Type:"sound", Msg:tree.Map{"authority":"the Bruce Dickinson", "description":"dynamite"}}
// "dynamite"
}
func ExampleGet() {
group := tree.Map{
"ID": tree.ToValue(1),
"Name": tree.ToValue("Reds"),
"Colors": tree.ToArrayValues("Crimson", "Red", "Ruby", "Maroon"),
"Nil": nil,
}
fmt.Println(group.Get("Colors").Get(1))
fmt.Println(group.Get("Colors", 2))
fmt.Println(group.Get("Colors").Get(5).IsNil())
fmt.Println(group.Get("Nil").IsNil())
// Output:
// Red
// Ruby
// true
// true
}
func ExampleFind() {
group := tree.Map{
"ID": tree.ToValue(1),
"Name": tree.ToValue("Reds"),
"Colors": tree.ToArrayValues("Crimson", "Red", "Ruby", "Maroon"),
}
rs, err := group.Find(".Colors[1:3]")
if err != nil {
log.Fatal(err)
}
for _, r := range rs {
fmt.Println(r)
}
// Output:
// Red
// Ruby
}
Query | Description | Results |
---|---|---|
.store.book[0] | The first book | {"category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95} |
.store.book[0].price | The price of the first book | 8.95 |
.store.book.0.price | The price of the first book (using dot) | 8.95 |
.store.book[:2].price | All prices of books[0:2] (index 2 is exclusive) | 8.95, 12.99 |
.store.book[].author | All authors of all books | "Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien" |
..author | All authors | "Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien" |
..author | [0] | The first author | "Nigel Rees" |
.store.book[(.category == "fiction" or .category == "reference") and .price < 10].title | All titles of books these are categoried into "fiction", "reference" and price < 10 | "Sayings of the Century", "Moby Dick" |
.store.book[.title ~= "^S"].title | Titles beginning with "S" | "Sayings of the Century", "Sword of Honour" |
.store.book.count() | Count books | 4 |
.store.book[0].keys() | Sorted keys of the first book | ["author", "category", "price", "title"] |
.store.book[0].values() | Values of the first book | ["Nigel Rees", "reference", 8.95, "Sayings of the Century"] |
{
"store": {
"book": [{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}
func ExampleEdit() {
var group tree.Node = tree.Map{
"ID": tree.ToValue(1),
"Name": tree.ToValue("Reds"),
"Colors": tree.ToArrayValues("Crimson", "Red", "Ruby", "Maroon"),
}
if err := tree.Edit(&group, ".Colors += \"Pink\""); err != nil {
log.Fatal(err)
}
fmt.Printf("Append Pink to Colors:\n %+v\n", group)
if err := tree.Edit(&group, ".Name = \"Blue\""); err != nil {
log.Fatal(err)
}
fmt.Printf("Set Blue to Name:\n %+v\n", group)
if err := tree.Edit(&group, ".Colors ^?"); err != nil {
log.Fatal(err)
}
fmt.Printf("Delete Colors:\n %+v\n", group)
// Output:
// Append Pink to Colors:
// map[Colors:[Crimson Red Ruby Maroon Pink] ID:1 Name:Reds]
// Set Blue to Name:
// map[Colors:[Crimson Red Ruby Maroon Pink] ID:1 Name:Blue]
// Delete Colors:
// map[ID:1 Name:Blue]
}
tq is a portable command-line JSON/YAML processor.
go install github.com/jarxorg/tree/cmd/tq@latest
Using Homebrew
brew tap jarxorg/tree
brew install jarxorg/tree/tq
Download binary
VERSION=0.8.0 GOOS=Darwin GOARCH=arm64; curl -fsSL "https://github.com/jarxorg/tree/releases/download/v${VERSION}/tree_${VERSION}_${GOOS}_${GOARCH}.tar.gz" | tar xz tq && mv tq /usr/local/bin
tq is a command-line JSON/YAML processor.
Usage:
tq [flags] [query] ([file...])
Flags:
-c, --color output with colors
-e, --edit stringArray edit expression
-x, --expand expand results
-h, --help help for tq
-U, --inplace update files, inplace
-i, --input-format string input format (json or yaml)
-j, --input-json alias --input-format json
-y, --input-yaml alias --input-format yaml
-O, --output string output file
-o, --output-format string output format (json or yaml, default json)
-J, --output-json alias --output-format json
-Y, --output-yaml alias --output-format yaml
-r, --raw output raw strings
-s, --slurp slurp all results into an array
-t, --template string golang text/template string
-v, --version print version
Examples:
% echo '{"colors": ["red", "green", "blue"]}' | tq '.colors[0]'
"red"
% echo '{"users":[{"id":1,"name":"one"},{"id":2,"name":"two"}]}' | tq -x -t '{{.id}}: {{.name}}' '.users'
1: one
2: two
% echo '{}' | tq -e '.colors = ["red", "green"]' -e '.colors += "blue"' .
{
"colors": [
"red",
"green",
"blue"
]
}
tq | jq |
---|---|
tq '.store.book[0]' | jq '.store.book[0]' |
tq '.store.book[]' | jq '.store.book[]' |
tq '.store.book[:2].price' | jq '.store.book[:2][] | .price' |
tq '.store.book[.category == "fiction" and .price < 10].title' | jq '.store.book[] | select(.category == "fiction" and .price < 10) | .title' |