Initial commit

This commit is contained in:
Vegard Berg 2023-11-30 12:01:20 +01:00
commit 7744206872
4 changed files with 201 additions and 0 deletions

19
README.md Normal file
View File

@ -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
```

10
go.mod Normal file
View File

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

11
go.sum Normal file
View File

@ -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=

161
main.go Normal file
View File

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