Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ docker run -i --rm ghcr.io/itchyny/gojq
- gojq behaves differently than jq in some features, expecting jq to fix its behavior in the future. gojq supports string indexing; `"abcde"[2]` ([jq#1520](https://github.com/jqlang/jq/issues/1520)). gojq fixes handling files with no newline characters at the end ([jq#2374](https://github.com/jqlang/jq/issues/2374)). gojq fixes `@base64d` to allow binary string as the decoded string ([jq#1931](https://github.com/jqlang/jq/issues/1931)). gojq improves time formatting and parsing; deals with `%f` in `strftime` and `strptime` ([jq#1409](https://github.com/jqlang/jq/issues/1409)), parses timezone offsets with `fromdate` and `fromdateiso8601` ([jq#1053](https://github.com/jqlang/jq/issues/1053)), supports timezone name/offset with `%Z`/`%z` in `strptime` ([jq#929](https://github.com/jqlang/jq/issues/929), [jq#2195](https://github.com/jqlang/jq/issues/2195)). gojq supports nanoseconds in date and time functions.
- gojq does not support some functions intentionally; `get_jq_origin`, `get_prog_origin`, `get_search_list` (unstable, not listed in jq document), `input_line_number`, `$__loc__` (performance issue). gojq does not support some flags; `--ascii-output, -a` (performance issue), `--seq` (not used commonly), `--sort-keys, -S` (sorts by default because `map[string]any` does not keep the order), `--unbuffered` (unbuffered by default). gojq does not parse JSON extensions supported by jq; `NaN`, `Infinity`, and `[000]`. gojq does not support some regular expression metacharacters, backreferences, look-around assertions, and some flags (regular expression engine differences). gojq does not support BOM (`encoding/json` does not support this). gojq disallows using keywords for function names (`def true: .; true` is a confusing query), and module name prefixes in function declarations (using module prefixes like `def m::f: .;` is undocumented).
- gojq supports reading from YAML input (`--yaml-input`) while jq does not. gojq also supports YAML output (`--yaml-output`).
- gojq supports `fromyaml` and `toyaml` functions to convert values to/from YAML, whereas jq only supports `fromjson` and `tojson` for JSON.

### Color configuration
The gojq command automatically disables coloring output when the output is not a tty.
Expand Down
81 changes: 81 additions & 0 deletions cli/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3867,6 +3867,41 @@
"[\"foo\"]"
"{\"a\":[1,2,3]}"

- name: toyaml function
args:
- 'toyaml'
input: |
null
false
true
42
"foo"
["apple", "banana", "orange"]
{"name": "John", "age": 30}
expected: |
"null"
"false"
"true"
"42"
"foo"
"- apple\n- banana\n- orange"
"age: 30\nname: John"

- name: toyaml function with nested structure
args:
- 'toyaml'
input: |
{"person": {"name": "Alice", "age": 25, "hobbies": ["reading", "coding"]}}
expected: |
"person:\n age: 25\n hobbies:\n - reading\n - coding\n name: Alice"

- name: toyaml function with large number
args:
- 'toyaml'
input: '{"number": 4722366482869645213696}'
expected: |
"number: 4722366482869645213696"

- name: fromjson function
args:
- -c
Expand Down Expand Up @@ -3895,6 +3930,52 @@
error: |
fromjson cannot be applied to "[0": unexpected EOF

- name: fromyaml function
args:
- -c
- 'fromyaml'
input: |
"null"
"false"
"true"
"42"
"foo"
"- apple\n- banana\n- orange"
"name: John\nage: 30"
"person:\n name: Alice\n age: 25\n hobbies:\n - reading\n - coding"
expected: |
null
false
true
42
"foo"
["apple","banana","orange"]
{"age":30,"name":"John"}
{"person":{"age":25,"hobbies":["reading","coding"],"name":"Alice"}}

- name: fromyaml function with numbers
args:
- 'fromyaml'
input: '"number: 4722366482869645213696"'
expected: |
{
"number": 4722366482869645213696
}

- name: fromyaml function error
args:
- 'fromyaml'
input: '"invalid: yaml: ["'
error: |
fromyaml cannot be applied to "invalid: yaml: [": yaml: line 1: mapping values are not allowed in this context

- name: fromyaml function type error
args:
- 'fromyaml'
input: '123'
error: |
fromyaml cannot be applied to: number (123)

- name: variable definition
args:
- -c
Expand Down
15 changes: 15 additions & 0 deletions encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"strconv"
"strings"
"unicode/utf8"

"github.com/itchyny/go-yaml"
)

// Marshal returns the jq-flavored JSON encoding of v.
Expand All @@ -34,6 +36,19 @@ func jsonMarshal(v any) string {
return sb.String()
}

func yamlMarshal(v any) (string, error) {
b, err := yaml.Marshal(v)
if err != nil {
return "", err
}
// Remove trailing newline if present
s := string(b)
if len(s) > 0 && s[len(s)-1] == '\n' {
s = s[:len(s)-1]
}
return s, nil
}

func jsonEncodeString(sb *strings.Builder, v string) {
(&encoder{w: sb}).encodeString(v)
}
Expand Down
24 changes: 24 additions & 0 deletions func.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"unicode"
"unicode/utf8"

"github.com/itchyny/go-yaml"
"github.com/itchyny/timefmt-go"
)

Expand Down Expand Up @@ -85,6 +86,8 @@ func init() {
"ascii_upcase": argFunc0(funcASCIIUpcase),
"tojson": argFunc0(funcToJSON),
"fromjson": argFunc0(funcFromJSON),
"toyaml": argFunc0(funcToYAML),
"fromyaml": argFunc0(funcFromYAML),
"format": argFunc1(funcFormat),
"_tohtml": argFunc0(funcToHTML),
"_touri": argFunc0(funcToURI),
Expand Down Expand Up @@ -871,6 +874,14 @@ func funcToJSON(v any) any {
return jsonMarshal(v)
}

func funcToYAML(v any) any {
s, err := yamlMarshal(v)
if err != nil {
return &func0WrapError{"toyaml", v, err}
}
return s
}

func funcFromJSON(v any) any {
s, ok := v.(string)
if !ok {
Expand All @@ -888,6 +899,19 @@ func funcFromJSON(v any) any {
return w
}

func funcFromYAML(v any) any {
s, ok := v.(string)
if !ok {
return &func0TypeError{"fromyaml", v}
}
var w any
dec := yaml.NewDecoder(strings.NewReader(s))
if err := dec.Decode(&w); err != nil {
return &func0WrapError{"fromyaml", v, err}
}
return w
}

func funcFormat(v, x any) any {
s, ok := x.(string)
if !ok {
Expand Down