Skip to content

jarxorg/tree

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tree

PkgGoDev Report Card

Tree is a simple structure for dealing with dynamic or unknown JSON/YAML in Go.

Table of Contents

Features

  • 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.

Road to 1.0

  • Placeholders in query.
  • Merge support in tq.

Syntax

Go

tree.Map{
	"ID":     tree.ToValue(1),
	"Name":   tree.ToValue("Reds"),
	"Colors": tree.ToArrayValues("Crimson", "Red", "Ruby", "Maroon"),
}

JSON

{
	"ID": 1,
	"Name": "Reds",
	"Colors": ["Crimson", "Red", "Ruby", "Maroon"]
}

YAML

ID: 1
Name: Reds
Colors:
- Crimson
- Red
- Ruby
- Maroon

Marshal and Unmarshal

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]]
}

Using other parsers

Tree may work on other parsers that are compatible with "encoding/json" or "gopkg.in/yaml.v2". See examples directory.

Alternate json.RawMessage

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"
}

Get

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
}

Find

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

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"

Illustrative Object

{
  "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" }
        ]
      }
    ]
  }
}

Built-in Methods

Tree provides several built-in methods for data manipulation and querying:

Aggregate Methods

  • count() - Returns the count of elements in arrays or maps
  • keys() - Returns the keys of arrays (as indices) or maps
  • values() - Returns the values of arrays or maps as an array

Condition Methods

  • 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)

Data Type Methods

  • type() - Returns the type name of the node ("array", "object", "string", "number", "boolean", "null")

Array Methods

  • first() - Returns the first element of an array
  • last() - Returns the last element of an array

Examples

// 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"

Edit

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

tq is a portable command-line JSON/YAML processor.

Installation

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

Usage

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"}]

for jq user

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'

Third-party library licenses

About

Simple tree structure and a handy command line tool named 'tq'

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages