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