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] [ | -]\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 }