Initial commit
This commit is contained in:
commit
7744206872
|
@ -0,0 +1,19 @@
|
||||||
|
# jsonsearch
|
||||||
|
Very simple tool to search by string or number value and get the JSON key from it.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
```sh
|
||||||
|
# Search for the first occurrence of 'foo Bar'.
|
||||||
|
jsonsearch "foo Bar" myfile.json
|
||||||
|
|
||||||
|
# Search for all occurrences of 'foo Bar'.
|
||||||
|
jsonsearch -all "foo Bar" myfile.json
|
||||||
|
|
||||||
|
# Search using STDIN
|
||||||
|
cat myfile.json | jsonsearch "foo Bar"
|
||||||
|
# or
|
||||||
|
cat myfile.json | jsonsearch "foo Bar" -
|
||||||
|
|
||||||
|
# Search a number
|
||||||
|
jsonsearch -type=number 3.141518 myfile.json
|
||||||
|
```
|
|
@ -0,0 +1,10 @@
|
||||||
|
module git.myrkvi.com/myrkvi/jsonsearch
|
||||||
|
|
||||||
|
go 1.21.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/fatih/color v1.16.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
golang.org/x/sys v0.14.0 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,11 @@
|
||||||
|
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||||
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||||
|
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
@ -0,0 +1,161 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
var findAll = flag.Bool("all", false, "Find all occurrences")
|
||||||
|
var findType = flag.String("type", "string", "The type of value to find. Must be one of 'string' or 'number'")
|
||||||
|
|
||||||
|
type JSON = interface{}
|
||||||
|
type JSONObject = map[string]interface{}
|
||||||
|
type JSONArray = []interface{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var inputData []byte
|
||||||
|
|
||||||
|
if flag.NArg() == 1 || flag.Arg(1) == "-" {
|
||||||
|
stdin, err := io.ReadAll(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
inputData = stdin
|
||||||
|
} else if flag.NArg() == 2 {
|
||||||
|
file, err := os.ReadFile(flag.Arg(1))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
inputData = file
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Usage: %s [-all] [-type=string|number] <match> [<file> | -]\n", filepath.Base(os.Args[0]))
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make(JSONObject)
|
||||||
|
json.Unmarshal(inputData, &data)
|
||||||
|
switch *findType {
|
||||||
|
case "string":
|
||||||
|
// "EpToggle_PAYMENTINSURANCE_CAR_Description"
|
||||||
|
findValue(data, flag.Arg(0))
|
||||||
|
case "number":
|
||||||
|
parsedNum, err := strconv.ParseFloat(flag.Arg(0), 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
findValue(data, parsedNum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findValue[T comparable](haystack JSON, text T) {
|
||||||
|
switch x := haystack.(type) {
|
||||||
|
case JSONObject:
|
||||||
|
findInObject(x, text, "")
|
||||||
|
case JSONArray:
|
||||||
|
findInList(x, text, "")
|
||||||
|
default:
|
||||||
|
slog.Error("JSON is not a nested structure.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findInObject[T comparable](haystack JSONObject, value T, path string) {
|
||||||
|
for k, v := range haystack {
|
||||||
|
switch x := v.(type) {
|
||||||
|
case string:
|
||||||
|
if *findType != "string" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if stringContains(x, value) {
|
||||||
|
printPathAndMatch(path, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
case float64:
|
||||||
|
if *findType != "number" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if floatEquals(x, value) {
|
||||||
|
printPathAndMatch(path, x)
|
||||||
|
}
|
||||||
|
case JSONObject:
|
||||||
|
findInObject(x, value, path+"."+k)
|
||||||
|
case []interface{}:
|
||||||
|
findInList(x, value, fmt.Sprintf("%s.%s", path, k))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findInList[T comparable](list []interface{}, value T, path string) {
|
||||||
|
for i, v := range list {
|
||||||
|
switch x := v.(type) {
|
||||||
|
case string:
|
||||||
|
if *findType != "string" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if stringContains(x, value) {
|
||||||
|
printPathAndMatch(path, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
case float64:
|
||||||
|
if *findType != "number" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if floatEquals(x, value) {
|
||||||
|
printPathAndMatch(path, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
case JSONObject:
|
||||||
|
findInObject(x, value, fmt.Sprintf("%s[%d]", path, i))
|
||||||
|
case []interface{}:
|
||||||
|
findInList(x, value, fmt.Sprintf("%s[%d]", path, i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printPathAndMatch(path string, match interface{}) {
|
||||||
|
if strings.HasPrefix(path, ".") {
|
||||||
|
path = strings.Replace(path, ".", "", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := color.New(color.Bold)
|
||||||
|
c.Printf("%s", path)
|
||||||
|
fmt.Printf(": ")
|
||||||
|
c = color.New(color.FgHiBlack)
|
||||||
|
c.Printf("%v\n", match)
|
||||||
|
|
||||||
|
if !*findAll {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringContains[T comparable](haystackString string, match T) bool {
|
||||||
|
matchString, ok := any(match).(string)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.Contains(haystackString, matchString)
|
||||||
|
}
|
||||||
|
|
||||||
|
func floatEquals[T comparable](haystackFloat float64, match T) bool {
|
||||||
|
matchFloat, ok := any(match).(float64)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return haystackFloat == matchFloat
|
||||||
|
}
|
Loading…
Reference in New Issue