├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── main.go ├── screenshot1.png ├── screenshot2.png └── screenshot3.png /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | jqview 3 | *.json 4 | deploy 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Whatever Qt requires, LGPL or something like that. 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | dist: deploy/linux/jqview deploy/windows/jqview.exe 2 | mkdir -p dist 3 | cd deploy/linux && tar -czvf jqview_linux.tar.gz jqview 4 | mv deploy/linux/jqview_linux.tar.gz dist/ 5 | rm -f deploy/windows/jqview_windows.zip 6 | cd deploy/windows && zip jqview_windows *.exe 7 | mv deploy/windows/jqview_windows.zip dist/ 8 | 9 | deploy/linux/jqview: $(shell find . -name "*.go") 10 | qtdeploy -ldflags="-s -w" -fast build desktop github.com/fiatjaf/jqview 11 | 12 | deploy/windows/jqview.exe: $(shell find . -name "*.go") 13 | qtdeploy -ldflags="-s -w" -docker build windows_64_static 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ./jqview [![Mentioned in Awesome jq](https://awesome.re/mentioned-badge.svg)](https://github.com/fiatjaf/awesome-jq) 2 | 3 | The simplest possible native GUI for inspecting JSON objects with [jq](https://stedolan.github.io/jq/manual/). 4 | 5 | Made with [Qt](https://qt.io/) and [gojq](https://github.com/itchyny/gojq). 6 | 7 | ## Usage 8 | 9 | ``` 10 | ~> echo '[{"name": "Mises"}, {"name": "Hayek"}, {"name": "Menger"}]' | jqview 11 | ``` 12 | 13 | ![](screenshot1.png) 14 | 15 | ``` 16 | ~> echo '[{"name": "Mises"}, {"name": "Hayek"}, {"name": "Menger"}]' | jqview '.[].name' 17 | ``` 18 | 19 | ![](screenshot2.png) 20 | 21 | ``` 22 | ~> echo '[{"name": "Mises"}, {"name": "Hayek"}, {"name": "Menger"}]' > names.json 23 | ~> jqview 'map(select(.name | startswith("M")))' names.json 24 | ``` 25 | 26 | ![](screenshot3.png) 27 | 28 | ## Installation 29 | 30 | Download from [releases](https://github.com/fiatjaf/jqview/releases), or compile using instructions from [this table](https://github.com/therecipe/qt/wiki/Deploying-Application). 31 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "strings" 10 | "time" 11 | 12 | "github.com/itchyny/gojq" 13 | "github.com/mitchellh/go-homedir" 14 | "github.com/therecipe/qt/widgets" 15 | ) 16 | 17 | var filterValue = "." 18 | var inputValue = `[{ 19 | "fruit": "mango" 20 | }, { 21 | "fruit": "banana" 22 | }]` 23 | 24 | var loadfileDialog *widgets.QFileDialog 25 | var input *widgets.QPlainTextEdit 26 | var filter *widgets.QLineEdit 27 | var output *widgets.QPlainTextEdit 28 | 29 | func main() { 30 | app := widgets.NewQApplication(len(os.Args), os.Args) 31 | 32 | if len(os.Args) > 1 { 33 | filterValue = os.Args[1] 34 | } 35 | if len(os.Args) > 2 { 36 | b, err := ioutil.ReadFile(os.Args[2]) 37 | if err != nil { 38 | log.Fatal("failed to read " + os.Args[2] + " : " + err.Error()) 39 | } 40 | inputValue = string(b) 41 | } else { 42 | if m, _ := os.Stdin.Stat(); m.Mode()&os.ModeCharDevice != os.ModeCharDevice { 43 | b, err := ioutil.ReadAll(os.Stdin) 44 | if err == nil { 45 | inputValue = string(b) 46 | } 47 | } 48 | } 49 | 50 | window := widgets.NewQMainWindow(nil, 0) 51 | window.SetMinimumSize2(400, 500) 52 | window.SetWindowTitle("jqview") 53 | 54 | dir, _ := homedir.Dir() 55 | loadfileDialog = widgets.NewQFileDialog2(nil, "Select a JSON file", dir, "") 56 | loadfileDialog.ConnectFileSelected(func(filepath string) { 57 | b, err := ioutil.ReadFile(filepath) 58 | if err != nil { 59 | log.Print("failed to read " + filepath + " : " + err.Error()) 60 | } else { 61 | input.SetPlainText(string(b)) 62 | go refresh() 63 | } 64 | }) 65 | 66 | loadfileButton := widgets.NewQPushButton2("Load", nil) 67 | loadfileButton.SetMaximumWidth(40) 68 | loadfileButton.ConnectClicked(func(_ bool) { 69 | loadfileDialog.Open(nil, "") 70 | }) 71 | 72 | input = widgets.NewQPlainTextEdit(nil) 73 | input.SetPlaceholderText("JSON input") 74 | input.SetPlainText(inputValue) 75 | input.ConnectTextChanged(refresh) 76 | 77 | inputSection := widgets.NewQWidget(nil, 0) 78 | inputSection.SetLayout(widgets.NewQHBoxLayout()) 79 | inputSection.SetMaximumHeight(150) 80 | inputSection.Layout().AddWidget(loadfileButton) 81 | inputSection.Layout().AddWidget(input) 82 | 83 | filter = widgets.NewQLineEdit(nil) 84 | filter.SetPlaceholderText("jq filter") 85 | filter.SetText(filterValue) 86 | filter.ConnectTextChanged(func(value string) { 87 | filterValue = value 88 | go refresh() 89 | }) 90 | 91 | output = widgets.NewQPlainTextEdit(nil) 92 | output.SetSizeAdjustPolicy(widgets.QAbstractScrollArea__AdjustToContents) 93 | output.SetMinimumHeight(300) 94 | 95 | widget := widgets.NewQWidget(nil, 0) 96 | widget.SetLayout(widgets.NewQVBoxLayout()) 97 | widget.Layout().AddWidget(inputSection) 98 | widget.Layout().AddWidget(filter) 99 | widget.Layout().AddWidget(output) 100 | 101 | window.Show() 102 | window.SetCentralWidget(widget) 103 | 104 | go refresh() 105 | 106 | app.Exec() 107 | } 108 | 109 | func refresh() { 110 | inputValue = input.ToPlainText() 111 | output.SetPlainText(runJQ(context.Background(), inputValue, filterValue)) 112 | } 113 | 114 | func runJQ( 115 | ctx context.Context, 116 | input string, 117 | filter string, 118 | ) string { 119 | ctx, cancel := context.WithTimeout(ctx, time.Second*20) 120 | defer cancel() 121 | 122 | var object interface{} 123 | err := json.Unmarshal([]byte(input), &object) 124 | if err != nil { 125 | return err.Error() 126 | } 127 | 128 | query, err := gojq.Parse(filter) 129 | if err != nil { 130 | return err.Error() 131 | } 132 | 133 | iter := query.RunWithContext(ctx, object) 134 | 135 | var results []string 136 | for { 137 | v, exists := iter.Next() 138 | if !exists { 139 | break 140 | } 141 | 142 | if err, ok := v.(error); ok { 143 | return err.Error() 144 | } 145 | 146 | s, _ := json.MarshalIndent(v, "", " ") 147 | results = append(results, string(s)) 148 | } 149 | 150 | return strings.Join(results, "\n") 151 | } 152 | -------------------------------------------------------------------------------- /screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiatjaf/jqview/a5641e28ce62480a7efddf9766f5f9af9f3b9593/screenshot1.png -------------------------------------------------------------------------------- /screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiatjaf/jqview/a5641e28ce62480a7efddf9766f5f9af9f3b9593/screenshot2.png -------------------------------------------------------------------------------- /screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiatjaf/jqview/a5641e28ce62480a7efddf9766f5f9af9f3b9593/screenshot3.png --------------------------------------------------------------------------------