jsonsearch/main.go

162 lines
3.1 KiB
Go
Raw Normal View History

2023-11-30 12:01:20 +01:00
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
}