From ca6ced064ef82fa6f5d7f8c9423c74644dc0c2df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Wed, 5 Jul 2023 15:52:07 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0go=20json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tool/gojson/json-to-struct.go | 497 ++++++++++++++++++++++++++++++++++ 1 file changed, 497 insertions(+) create mode 100644 tool/gojson/json-to-struct.go diff --git a/tool/gojson/json-to-struct.go b/tool/gojson/json-to-struct.go new file mode 100644 index 0000000..40471ac --- /dev/null +++ b/tool/gojson/json-to-struct.go @@ -0,0 +1,497 @@ +// Package gojson ... +// +// Description : json_tool ... +// +// Author : go_developer@163.com<张德满> +// +// Date : 2022-01-09 10:48 PM +package gojson + +import ( + "bytes" + "encoding/json" + "fmt" + "go/format" + "io" + "math" + "reflect" + "sort" + "strconv" + "strings" + "unicode" + + "gopkg.in/yaml.v3" +) + +var ForceFloats bool + +// commonInitialisms is a set of common initialisms. +// Only add entries that are highly unlikely to be non-initialisms. +// For instance, "ID" is fine (Freudian code is rare), but "AND" is not. +var commonInitialisms = map[string]bool{ + "API": true, + "ASCII": true, + "CPU": true, + "CSS": true, + "DNS": true, + "EOF": true, + "GUID": true, + "HTML": true, + "HTTP": true, + "HTTPS": true, + "ID": true, + "IP": true, + "JSON": true, + "LHS": true, + "QPS": true, + "RAM": true, + "RHS": true, + "RPC": true, + "SLA": true, + "SMTP": true, + "SSH": true, + "TLS": true, + "TTL": true, + "UI": true, + "UID": true, + "UUID": true, + "URI": true, + "URL": true, + "UTF8": true, + "VM": true, + "XML": true, + "NTP": true, + "DB": true, +} + +var intToWordMap = []string{ + "zero", + "one", + "two", + "three", + "four", + "five", + "six", + "seven", + "eight", + "nine", +} + +type Parser func(io.Reader) (interface{}, error) + +func ParseJson(input io.Reader) (interface{}, error) { + var result interface{} + if err := json.NewDecoder(input).Decode(&result); err != nil { + return nil, err + } + return result, nil +} + +func ParseYaml(input io.Reader) (interface{}, error) { + var result interface{} + b, err := readFile(input) + if err != nil { + return nil, err + } + if err := yaml.Unmarshal(b, &result); err != nil { + return nil, err + } + return result, nil +} + +func readFile(input io.Reader) ([]byte, error) { + buf := bytes.NewBuffer(nil) + _, err := io.Copy(buf, input) + if err != nil { + return []byte{}, nil + } + return buf.Bytes(), nil +} + +// Generate a struct definition given a JSON string representation of an object and a name structName. +func Generate(input io.Reader, parser Parser, structName, pkgName string, tags []string, subStruct bool, convertFloats bool) ([]byte, error) { + var subStructMap map[string]string = nil + if subStruct { + subStructMap = make(map[string]string) + } + + var result map[string]interface{} + + iresult, err := parser(input) + if err != nil { + return nil, err + } + + switch iresult := iresult.(type) { + case map[interface{}]interface{}: + result = convertKeysToStrings(iresult) + case map[string]interface{}: + result = iresult + case []interface{}: + src := fmt.Sprintf("package %s\n\ntype %s %s\n", + pkgName, + structName, + typeForValue(iresult, structName, tags, subStructMap, convertFloats)) + formatted, err := format.Source([]byte(src)) + if err != nil { + err = fmt.Errorf("error formatting: %s, was formatting\n%s", err, src) + } + return formatted, err + default: + return nil, fmt.Errorf("unexpected type: %T", iresult) + } + + src := fmt.Sprintf("package %s\ntype %s %s}", + pkgName, + structName, + generateTypes(result, structName, tags, 0, subStructMap, convertFloats)) + + keys := make([]string, 0, len(subStructMap)) + for key := range subStructMap { + keys = append(keys, key) + } + + sort.Strings(keys) + + for _, k := range keys { + src = fmt.Sprintf("%v\n\ntype %v %v", src, subStructMap[k], k) + } + + formatted, err := format.Source([]byte(src)) + if err != nil { + err = fmt.Errorf("error formatting: %s, was formatting\n%s", err, src) + } + return formatted, err +} + +func convertKeysToStrings(obj map[interface{}]interface{}) map[string]interface{} { + res := make(map[string]interface{}) + + for k, v := range obj { + res[fmt.Sprintf("%v", k)] = v + } + + return res +} + +// Generate go struct entries for a map[string]interface{} structure +func generateTypes(obj map[string]interface{}, structName string, tags []string, depth int, subStructMap map[string]string, convertFloats bool) string { + structure := "struct {" + + keys := make([]string, 0, len(obj)) + for key := range obj { + keys = append(keys, key) + } + sort.Strings(keys) + + for _, key := range keys { + value := obj[key] + valueType := typeForValue(value, structName, tags, subStructMap, convertFloats) + + //value = mergeElements(value) + + //If a nested value, recurse + switch value := value.(type) { + case []interface{}: + if len(value) > 0 { + sub := "" + if v, ok := value[0].(map[interface{}]interface{}); ok { + sub = generateTypes(convertKeysToStrings(v), structName, tags, depth+1, subStructMap, convertFloats) + "}" + } else if v, ok := value[0].(map[string]interface{}); ok { + sub = generateTypes(v, structName, tags, depth+1, subStructMap, convertFloats) + "}" + } + + if sub != "" { + subName := sub + + if subStructMap != nil { + if val, ok := subStructMap[sub]; ok { + subName = val + } else { + subName = fmt.Sprintf("%v_sub%v", structName, len(subStructMap)+1) + + subStructMap[sub] = subName + } + } + + valueType = "[]" + subName + } + } + case map[interface{}]interface{}: + sub := generateTypes(convertKeysToStrings(value), structName, tags, depth+1, subStructMap, convertFloats) + "}" + subName := sub + + if subStructMap != nil { + if val, ok := subStructMap[sub]; ok { + subName = val + } else { + subName = fmt.Sprintf("%v_sub%v", structName, len(subStructMap)+1) + + subStructMap[sub] = subName + } + } + valueType = subName + case map[string]interface{}: + sub := generateTypes(value, structName, tags, depth+1, subStructMap, convertFloats) + "}" + subName := sub + + if subStructMap != nil { + if val, ok := subStructMap[sub]; ok { + subName = val + } else { + subName = fmt.Sprintf("%v_sub%v", structName, len(subStructMap)+1) + + subStructMap[sub] = subName + } + } + + valueType = subName + } + + fieldName := FmtFieldName(key) + + tagList := make([]string, 0) + for _, t := range tags { + tagList = append(tagList, fmt.Sprintf("%s:\"%s\"", t, key)) + } + + structure += fmt.Sprintf("\n%s %s `%s`", + fieldName, + valueType, + strings.Join(tagList, " ")) + } + return structure +} + +// FmtFieldName formats a string as a struct key +// +// Example: +// +// FmtFieldName("foo_id") +// +// Output: FooID +func FmtFieldName(s string) string { + runes := []rune(s) + for len(runes) > 0 && !unicode.IsLetter(runes[0]) && !unicode.IsDigit(runes[0]) { + runes = runes[1:] + } + if len(runes) == 0 { + return "_" + } + + s = stringifyFirstChar(string(runes)) + name := lintFieldName(s) + runes = []rune(name) + for i, c := range runes { + ok := unicode.IsLetter(c) || unicode.IsDigit(c) + if i == 0 { + ok = unicode.IsLetter(c) + } + if !ok { + runes[i] = '_' + } + } + s = string(runes) + s = strings.Trim(s, "_") + if len(s) == 0 { + return "_" + } + return s +} + +func lintFieldName(name string) string { + // Fast path for simple cases: "_" and all lowercase. + if name == "_" { + return name + } + + allLower := true + for _, r := range name { + if !unicode.IsLower(r) { + allLower = false + break + } + } + if allLower { + runes := []rune(name) + if u := strings.ToUpper(name); commonInitialisms[u] { + copy(runes[0:], []rune(u)) + } else { + runes[0] = unicode.ToUpper(runes[0]) + } + return string(runes) + } + + allUpperWithUnderscore := true + for _, r := range name { + if !unicode.IsUpper(r) && r != '_' { + allUpperWithUnderscore = false + break + } + } + if allUpperWithUnderscore { + name = strings.ToLower(name) + } + + // Split camelCase at any lower->upper transition, and split on underscores. + // Check each word for common initialisms. + runes := []rune(name) + w, i := 0, 0 // index of start of word, scan + for i+1 <= len(runes) { + eow := false // whether we hit the end of a word + + if i+1 == len(runes) { + eow = true + } else if runes[i+1] == '_' { + // underscore; shift the remainder forward over any run of underscores + eow = true + n := 1 + for i+n+1 < len(runes) && runes[i+n+1] == '_' { + n++ + } + + // Leave at most one underscore if the underscore is between two digits + if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) { + n-- + } + + copy(runes[i+1:], runes[i+n+1:]) + runes = runes[:len(runes)-n] + } else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) { + // lower->non-lower + eow = true + } + i++ + if !eow { + continue + } + + // [w,i) is a word. + word := string(runes[w:i]) + if u := strings.ToUpper(word); commonInitialisms[u] { + // All the common initialisms are ASCII, + // so we can replace the bytes exactly. + copy(runes[w:], []rune(u)) + + } else if strings.ToLower(word) == word { + // already all lowercase, and not the first word, so uppercase the first character. + runes[w] = unicode.ToUpper(runes[w]) + } + w = i + } + return string(runes) +} + +// generate an appropriate struct type entry +func typeForValue(value interface{}, structName string, tags []string, subStructMap map[string]string, convertFloats bool) string { + //Check if this is an array + if objects, ok := value.([]interface{}); ok { + types := make(map[reflect.Type]bool, 0) + for _, o := range objects { + types[reflect.TypeOf(o)] = true + } + if len(types) == 1 { + return "[]" + typeForValue(mergeElements(objects).([]interface{})[0], structName, tags, subStructMap, convertFloats) + } + return "[]interface{}" + } else if object, ok := value.(map[interface{}]interface{}); ok { + return generateTypes(convertKeysToStrings(object), structName, tags, 0, subStructMap, convertFloats) + "}" + } else if object, ok := value.(map[string]interface{}); ok { + return generateTypes(object, structName, tags, 0, subStructMap, convertFloats) + "}" + } else if reflect.TypeOf(value) == nil { + return "interface{}" + } + v := reflect.TypeOf(value).Name() + if v == "float64" && convertFloats { + v = disambiguateFloatInt(value) + } + return v +} + +// All numbers will initially be read as float64 +// If the number appears to be an integer value, use int instead +func disambiguateFloatInt(value interface{}) string { + const epsilon = .0001 + vfloat := value.(float64) + if !ForceFloats && math.Abs(vfloat-math.Floor(vfloat+epsilon)) < epsilon { + var tmp int64 + return reflect.TypeOf(tmp).Name() + } + return reflect.TypeOf(value).Name() +} + +// convert first character ints to strings +func stringifyFirstChar(str string) string { + first := str[:1] + + i, err := strconv.ParseInt(first, 10, 8) + + if err != nil { + return str + } + + return intToWordMap[i] + "_" + str[1:] +} + +func mergeElements(i interface{}) interface{} { + switch i := i.(type) { + default: + return i + case []interface{}: + l := len(i) + if l == 0 { + return i + } + for j := 1; j < l; j++ { + i[0] = mergeObjects(i[0], i[j]) + } + return i[0:1] + } +} + +func mergeObjects(o1, o2 interface{}) interface{} { + if o1 == nil { + return o2 + } + + if o2 == nil { + return o1 + } + + if reflect.TypeOf(o1) != reflect.TypeOf(o2) { + return nil + } + + switch i := o1.(type) { + default: + return o1 + case []interface{}: + if i2, ok := o2.([]interface{}); ok { + i3 := append(i, i2...) + return mergeElements(i3) + } + return mergeElements(i) + case map[string]interface{}: + if i2, ok := o2.(map[string]interface{}); ok { + for k, v := range i2 { + if v2, ok := i[k]; ok { + i[k] = mergeObjects(v2, v) + } else { + i[k] = v + } + } + } + return i + case map[interface{}]interface{}: + if i2, ok := o2.(map[interface{}]interface{}); ok { + for k, v := range i2 { + if v2, ok := i[k]; ok { + i[k] = mergeObjects(v2, v) + } else { + i[k] = v + } + } + } + return i + } +}