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 with built-in methods.
- Edit function can be specified the Edit expression.
- Bundled 'tq' that is a portable command-line JSON/YAML processor.
- Placeholders in query.
- Merge support in tq.
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 work on other parsers that are 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
}
For more details on built-in methods, see the Built-in Methods section.
Query | Description | Results |
---|---|---|
.store.book[0] | The first book | {"category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95, "tags": [...]} |
.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[.tags[.name == "genre" and .value == "fiction"]].title | All titles of books tagged "fiction" | "Sword of Honour", "Moby Dick" |
.store.book[(.category == "fiction" or .category == "reference") and .price < 10].title | All titles of books that are categorized as "fiction" or "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", "tags", "title"] |
.store.book[0].values() | Values of the first book | ["Nigel Rees", "reference", 8.95, [tag objects], "Sayings of the Century"] |
.store.book.last().has("isbn") | Check if last book has ISBN | true |
.store.book[.author.contains("Tolkien")] | Check if any author contains "Tolkien" | [{"author": "J. R. R. Tolkien", ...}] |
.store.book[0].category.type() | Get type of category field | "string" |
.store.book[0].empty() | Check if first book is empty | false |
.store.book.first().title | Get title of first book | "Sayings of the Century" |
.store.book.last().author | Get author of last book | "J. R. R. Tolkien" |
{
"store": {
"bicycle": {
"color": "red",
"price": 19.95
},
"book": [
{
"author": "Nigel Rees",
"category": "reference",
"price": 8.95,
"title": "Sayings of the Century",
"tags": [
{ "name": "genre", "value": "reference" },
{ "name": "era", "value": "20th century" },
{ "name": "theme", "value": "quotations" }
]
},
{
"author": "Evelyn Waugh",
"category": "fiction",
"price": 12.99,
"title": "Sword of Honour",
"tags": [
{ "name": "genre", "value": "fiction" },
{ "name": "era", "value": "20th century" },
{ "name": "theme", "value": "WWII" }
]
},
{
"author": "Herman Melville",
"category": "fiction",
"isbn": "0-553-21311-3",
"price": 8.99,
"title": "Moby Dick",
"tags": [
{ "name": "genre", "value": "fiction" },
{ "name": "era", "value": "19th century" },
{ "name": "theme", "value": "whale hunting" }
]
},
{
"author": "J. R. R. Tolkien",
"category": "fiction",
"isbn": "0-395-19395-8",
"price": 22.99,
"title": "The Lord of the Rings",
"tags": [
{ "name": "genre", "value": "fantasy" },
{ "name": "era", "value": "20th century" },
{ "name": "theme", "value": "good vs evil" }
]
}
]
}
}
Tree provides several built-in methods for data manipulation and querying:
count()
- Returns the count of elements in arrays or mapskeys()
- Returns the keys of arrays (as indices) or mapsvalues()
- Returns the values of arrays or maps as an array
empty()
- Checks if the node is empty (empty arrays, maps, null values, or empty strings)has(key)
- Checks if the node has the specified key (works with arrays and maps)contains(value)
- Checks if the node contains the specified value (arrays, maps, or substring in strings)
type()
- Returns the type name of the node ("array", "object", "string", "number", "boolean", "null")
first()
- Returns the first element of an arraylast()
- Returns the last element of an array
// Count elements
node.Find(".store.book.count()") // Returns: 4
// Check if key exists
node.Find(".store.book[0].has(\"title\")") // Returns: true
// Check if contains value in string
node.Find(".store.book[0].author.contains(\"Nigel\")") // Returns: true
// Get type
node.Find(".store.book[0].price.type()") // Returns: "number"
// Array operations
node.Find(".store.book.first().title") // Returns: "Sayings of the Century"
node.Find(".store.book.last().title") // Returns: "The Lord of the Rings"
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
# For macOS (Darwin)
VERSION=0.8.4 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
# For Linux x64
VERSION=0.8.4 GOOS=Linux GOARCH=amd64; 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
# For Windows x64
VERSION=0.8.4; curl -fsSL "https://github.com/jarxorg/tree/releases/download/v${VERSION}/tree_${VERSION}_windows_amd64.zip" -o tq.zip && unzip tq.zip tq.exe
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"
]
}
# Using built-in methods
% echo '{"books": [{"author": "Tolkien"}, {"author": "Hemingway"}]}' | tq '.books[.author.contains("Tol")]'
[{"author": "Tolkien"}]
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' |