| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 | 
							- package astruct
 
- import (
 
- 	"encoding/json"
 
- 	"fmt"
 
- 	"log"
 
- 	"os"
 
- 	"reflect"
 
- 	"strings"
 
- 	"golang.org/x/exp/maps"
 
- 	"golang.org/x/exp/slices"
 
- )
 
- type Map map[string]reflect.Kind
 
- // The main structure to use with astruct
 
- //
 
- // This structure contains options to configure behavior and methods in which to parse (JSON) and output (structs in source code)
 
- type Astruct struct {
 
- 	// Options
 
- 	Use32Bit            bool   // Defaults to false, using 64-bit int and 64-bit float, when true, uses 32-bit variants of int and float
 
- 	UseAny              bool   // Defaults to true, when true the key word any (which is an alias to interface{}) will be used, when false an error will be thrown (this only applies to arrays/slices who have varying types)
 
- 	VerboseLogging      bool   // Dump verbose info into logs (User must setup log first!)
 
- 	AssumeStruct        bool   // Defaults to true, when true instead of a map of a basic type a structure will be made (even if the type matches), while false a map could be used in places where the type matches
 
- 	PrefixStructName    string // If not an empty "" string, Structures will be prefixed (for cases where you're going to have a single file produced with various structures from astruct, helps prevent name collisions, which would normally error out)
 
- 	SuffixForArrayLikes string // If not empty "" string, and it's an array this suffix is added (default's to "Item")
 
- 	// In-Memory Data (This data is gathered to form final products)
 
- 	// Almost all of these will be key being field name for in a structure (if one needs to be built) and value of reflect.Kind to determine:
 
- 	// 1. If the given thing was seen before (as with the case of say an []struct) or if that isn't possible as it's something else (in which a structure might not be possible)
 
- 	// 2. If a map is actually better represented as a structure (In the case of AssumeStruct the map won't be an option instead a map[string]struct would be used, or even []struct)
 
- 	MapLikes   map[string]Map            // Holds map-like structures which might be better defined as structures if they have varying types (else it will retain a single type being a map)
 
- 	ArrayLikes map[string][]reflect.Kind // Holds array-like structures (this includes slices) which might be able to be defined as arrays (but if varying types are encountered then it might end up as []any or []interface{})
 
- 	Parent     any                       // Position just above the Current position (for when we're done parsing this "depth", or if this is nil to indicate we're done)
 
- 	Depth      uint                      // Tracks how many nested structures (map, array/slice) deep we are
 
- }
 
- // Initializes to default options (this also resets the In-Memory Data to an initialized state)
 
- func (A *Astruct) Defaults() {
 
- 	A.Use32Bit = false
 
- 	A.UseAny = true
 
- 	A.VerboseLogging = false
 
- 	A.AssumeStruct = true
 
- 	A.PrefixStructName = ""
 
- 	A.SuffixForArrayLikes = "Item"
 
- 	A.Init()
 
- }
 
- // This resets the In-Memory Data to an initialized state
 
- func (A *Astruct) Init() {
 
- 	A.MapLikes = make(map[string]Map)
 
- 	A.ArrayLikes = make(map[string][]reflect.Kind)
 
- 	A.Parent = nil
 
- 	A.Depth = 0
 
- }
 
- // Creates a new Astruct, if given a Prefix for generated structs (only the first one) will be used
 
- func NewAstruct(PrefixStructName ...string) *Astruct {
 
- 	A := &Astruct{}
 
- 	A.Defaults()
 
- 	if len(PrefixStructName) != 0 {
 
- 		A.PrefixStructName = CamelCase(PrefixStructName[0])
 
- 	}
 
- 	return A
 
- }
 
- func (A *Astruct) ReadFile(filename string) error {
 
- 	data, err := os.ReadFile(filename)
 
- 	if err != nil {
 
- 		return fmt.Errorf("astruct.Astruct.ReadFile(filename='%s') On os.ReadFile > %w", filename, err)
 
- 	}
 
- 	var root map[string]any
 
- 	err = json.Unmarshal(data, &root)
 
- 	if err != nil {
 
- 		return fmt.Errorf("astruct.Astruct.ReadFile(filename='%s') On json.Unmarshal > %w", filename, err)
 
- 	}
 
- 	// Begin parsing...
 
- 	A.parse(root, "")
 
- 	return nil
 
- }
 
- func (A *Astruct) parse(at any, name string) error {
 
- 	switch reflect.TypeOf(at).Kind() {
 
- 	case reflect.Map:
 
- 		m := at.(map[string]any)
 
- 		var last_type reflect.Kind = 0
 
- 		var same_type bool = true
 
- 		for k, v := range m {
 
- 			if A.VerboseLogging {
 
- 				log.Printf(strings.Repeat("  ", int(A.Depth))+"'%s' = %s", k, reflect.TypeOf(v).Kind().String())
 
- 			}
 
- 			if same_type {
 
- 				if last_type == 0 {
 
- 					last_type = reflect.TypeOf(v).Kind()
 
- 				} else if reflect.TypeOf(v).Kind() != last_type {
 
- 					same_type = false
 
- 				}
 
- 			}
 
- 			switch reflect.TypeOf(v).Kind() {
 
- 			case reflect.Map:
 
- 				A.Depth += 1
 
- 				err := A.parse(v, k)
 
- 				if err != nil {
 
- 					return err
 
- 				}
 
- 				A.Depth -= 1
 
- 			case reflect.Array, reflect.Slice:
 
- 				A.Depth += 1
 
- 				err := A.parse(v, k)
 
- 				if err != nil {
 
- 					return err
 
- 				}
 
- 				A.Depth -= 1
 
- 			}
 
- 		}
 
- 		A.MapLikes[name] = make(Map)
 
- 		if !same_type {
 
- 			for k, v := range m {
 
- 				if reflect.TypeOf(v).Kind() == reflect.Float32 || reflect.TypeOf(v).Kind() == reflect.Float64 {
 
- 					f := v.(float64)
 
- 					if IsInt(f) {
 
- 						A.MapLikes[name][k] = reflect.Int64
 
- 					} else {
 
- 						A.MapLikes[name][k] = reflect.Float64
 
- 					}
 
- 				} else {
 
- 					A.MapLikes[name][k] = reflect.TypeOf(v).Kind()
 
- 				}
 
- 			}
 
- 		} else { // We'll use a empty string "", to indicate this map is same type
 
- 			A.MapLikes[name][""] = reflect.TypeOf(m[maps.Keys(m)[0]]).Kind()
 
- 			for k, v := range m {
 
- 				if reflect.TypeOf(v).Kind() == reflect.Float32 || reflect.TypeOf(v).Kind() == reflect.Float64 {
 
- 					f := v.(float64)
 
- 					if IsInt(f) {
 
- 						A.MapLikes[name][k] = reflect.Int64
 
- 					} else {
 
- 						A.MapLikes[name][k] = reflect.Float64
 
- 					}
 
- 				} else {
 
- 					A.MapLikes[name][k] = reflect.TypeOf(v).Kind()
 
- 				}
 
- 			}
 
- 		}
 
- 	case reflect.Array, reflect.Slice:
 
- 		a := at.([]any)
 
- 		var last_type reflect.Kind = 0
 
- 		var same_type bool = true
 
- 		for i, v := range a {
 
- 			if A.VerboseLogging {
 
- 				log.Printf(strings.Repeat("  ", int(A.Depth))+"%d = %s", i, reflect.TypeOf(v).Kind().String())
 
- 			}
 
- 			if same_type {
 
- 				if last_type == 0 {
 
- 					last_type = reflect.TypeOf(v).Kind()
 
- 				} else if reflect.TypeOf(v).Kind() != last_type {
 
- 					same_type = false
 
- 				}
 
- 			}
 
- 			switch reflect.TypeOf(v).Kind() {
 
- 			case reflect.Map:
 
- 				A.Depth += 1
 
- 				err := A.parse(v, A.SuffixForArrayLikes)
 
- 				if err != nil {
 
- 					return err
 
- 				}
 
- 				A.Depth -= 1
 
- 			case reflect.Array, reflect.Slice:
 
- 				A.Depth += 1
 
- 				err := A.parse(v, A.SuffixForArrayLikes)
 
- 				if err != nil {
 
- 					return err
 
- 				}
 
- 				A.Depth -= 1
 
- 			}
 
- 		}
 
- 		A.ArrayLikes[name] = []reflect.Kind{}
 
- 		if !same_type {
 
- 			for _, v := range a {
 
- 				if reflect.TypeOf(v).Kind() == reflect.Float32 || reflect.TypeOf(v).Kind() == reflect.Float64 {
 
- 					f := v.(float64)
 
- 					if IsInt(f) {
 
- 						A.ArrayLikes[name] = append(A.ArrayLikes[name], reflect.Int64)
 
- 					} else {
 
- 						A.ArrayLikes[name] = append(A.ArrayLikes[name], reflect.Float64)
 
- 					}
 
- 				} else {
 
- 					A.ArrayLikes[name] = append(A.ArrayLikes[name], reflect.TypeOf(v).Kind())
 
- 				}
 
- 			}
 
- 		} else {
 
- 			v := a[0]
 
- 			if reflect.TypeOf(v).Kind() == reflect.Float32 || reflect.TypeOf(v).Kind() == reflect.Float64 {
 
- 				f := v.(float64)
 
- 				if IsInt(f) {
 
- 					A.ArrayLikes[name] = append(A.ArrayLikes[name], reflect.Int64)
 
- 				} else {
 
- 					A.ArrayLikes[name] = append(A.ArrayLikes[name], reflect.Float64)
 
- 				}
 
- 			} else {
 
- 				A.ArrayLikes[name] = append(A.ArrayLikes[name], reflect.TypeOf(v).Kind())
 
- 			}
 
- 		}
 
- 	default:
 
- 		if A.VerboseLogging {
 
- 			log.Printf(strings.Repeat("  ", int(A.Depth))+"%s", reflect.TypeOf(at).Kind().String())
 
- 		}
 
- 	}
 
- 	return nil
 
- }
 
- // Checks if the given JSON Number (represented as float64) can be a int64 instead
 
- func IsInt(v float64) bool {
 
- 	str := fmt.Sprintf("%f", v)
 
- 	var hit_dot bool = false
 
- 	for _, r := range str {
 
- 		if r == '.' {
 
- 			hit_dot = true
 
- 			continue
 
- 		}
 
- 		if hit_dot {
 
- 			if r != '0' {
 
- 				return false
 
- 			}
 
- 		}
 
- 	}
 
- 	return true
 
- }
 
- // Checks if the given data (either map or array/slice) contains the same types
 
- //
 
- // Used to identify if a map or array/slice could be used (unless AssumeStruct is true in which a struct will always be made)
 
- func SameType(data any) bool {
 
- 	if reflect.TypeOf(data) == reflect.TypeOf(Map{}) {
 
- 		m := data.(Map)
 
- 		var last_type reflect.Kind = 0
 
- 		for _, v := range m {
 
- 			if last_type == 0 {
 
- 				last_type = v
 
- 			} else if v != last_type {
 
- 				return false
 
- 			}
 
- 		}
 
- 		return true
 
- 	}
 
- 	switch reflect.TypeOf(data).Kind() {
 
- 	case reflect.Map:
 
- 		m := data.(map[string]any)
 
- 		var last_type reflect.Kind = 0
 
- 		for _, v := range m {
 
- 			if last_type == 0 {
 
- 				last_type = reflect.TypeOf(v).Kind()
 
- 			} else if reflect.TypeOf(v).Kind() != last_type {
 
- 				return false
 
- 			}
 
- 		}
 
- 		return true
 
- 	case reflect.Array, reflect.Slice:
 
- 		a := data.([]any)
 
- 		var last_type reflect.Kind = 0
 
- 		for _, v := range a {
 
- 			if last_type == 0 {
 
- 				last_type = reflect.TypeOf(v).Kind()
 
- 			} else if reflect.TypeOf(v).Kind() != last_type {
 
- 				return false
 
- 			}
 
- 		}
 
- 		return true
 
- 	}
 
- 	return false
 
- }
 
- func CamelCase(field string) string {
 
- 	if len(field) == 0 {
 
- 		return ""
 
- 	}
 
- 	var result string
 
- 	if !strings.Contains(field, " ") || !strings.Contains(field, "-") {
 
- 		result += strings.ToUpper(string(field[0]))
 
- 		result += field[1:]
 
- 		return result
 
- 	}
 
- 	result = strings.ToTitle(field)
 
- 	result = strings.ReplaceAll(result, " ", "")
 
- 	result = strings.ReplaceAll(result, "-", "")
 
- 	return result
 
- }
 
- func (A *Astruct) WriteFile(filename, packageName string, permissions os.FileMode) error {
 
- 	if packageName == "" {
 
- 		return fmt.Errorf("astruct.Astruct.WriteFile(filename='%s', packageName='%s', permissions=%d) > %s", filename, packageName, permissions, "Missing 'packageName', this must not be empty!")
 
- 	}
 
- 	var successful bool = true
 
- 	fh, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, permissions)
 
- 	if err != nil {
 
- 		return fmt.Errorf("astruct.Astruct.WriteFile(filename='%s', packageName='%s', permissions=%d) On os.OpenFile > %w", filename, packageName, permissions, err)
 
- 	}
 
- 	defer func() {
 
- 		fh.Close()
 
- 		if !successful {
 
- 			err = os.Remove(filename)
 
- 			if err != nil {
 
- 				log.Panicf("astruct.Astruct.WriteFile(filename='%s', packageName='%s', permissions=%d) On os.Remove > %v", filename, packageName, permissions, err)
 
- 			}
 
- 		}
 
- 	}()
 
- 	fh.WriteString(fmt.Sprintf("package %s\n\n", packageName))
 
- 	//var slices_to_structs []string = []string{}
 
- 	var seen []string = []string{}
 
- 	for name, m := range A.MapLikes {
 
- 		/*if name == "" { // Ignore the root node for now
 
- 			continue
 
- 		}*/
 
- 		if slices.Contains(seen, name) {
 
- 			continue
 
- 		}
 
- 		_, sameType := m[""] // Check if the empty string exists in this map (if it does it contains same typed data)
 
- 		if !A.AssumeStruct || !sameType {
 
- 			fh.WriteString(fmt.Sprintf("type %s%s struct {\n", A.PrefixStructName, CamelCase(name)))
 
- 			for k, v := range m {
 
- 				if slices.Contains(maps.Keys(A.MapLikes), k) {
 
- 					_, sameType2 := A.MapLikes[k][""]
 
- 					if sameType2 && !A.AssumeStruct {
 
- 						fh.WriteString(fmt.Sprintf("\t%s map[string]%s\n", CamelCase(k), A.MapLikes[k][""].String()))
 
- 						seen = append(seen, k)
 
- 					} else if !sameType2 || A.AssumeStruct && v == reflect.Map {
 
- 						fh.WriteString(fmt.Sprintf("\t%s %s%s\n", CamelCase(k), A.PrefixStructName, CamelCase(k)))
 
- 					} else if !sameType2 || A.AssumeStruct && v != reflect.Map {
 
- 						fh.WriteString(fmt.Sprintf("\t%s %s\n", CamelCase(k), v.String()))
 
- 					}
 
- 				} else {
 
- 					if slices.Contains(maps.Keys(A.ArrayLikes), k) {
 
- 						a := A.ArrayLikes[k]
 
- 						if len(a) == 1 {
 
- 							if a[0] == reflect.Float64 && A.Use32Bit {
 
- 								fh.WriteString(fmt.Sprintf("\t%s []float32\n", CamelCase(k)))
 
- 								continue
 
- 							} else if a[0] == reflect.Int64 && A.Use32Bit {
 
- 								fh.WriteString(fmt.Sprintf("\t%s []int32\n", CamelCase(k)))
 
- 								continue
 
- 							} else if a[0] == reflect.Map {
 
- 								fh.WriteString(fmt.Sprintf("\t%s []%s%s\n", CamelCase(k), CamelCase(k), A.SuffixForArrayLikes))
 
- 								continue
 
- 							}
 
- 							fh.WriteString(fmt.Sprintf("\t%s []%s\n", CamelCase(k), a[0]))
 
- 						} else {
 
- 							//fh.WriteString(fmt.Sprintf("\t%s []%s\n", CamelCase(k), CamelCase(k)))
 
- 							//slices_to_structs = append(slices_to_structs, k)
 
- 							if A.UseAny {
 
- 								fh.WriteString(fmt.Sprintf("\t%s []any\n", CamelCase(k)))
 
- 							} else {
 
- 								return fmt.Errorf("astruct.Astruct.WriteFile(filename='%s', packageName='%s', permissions=%d) > Field '%s' is a slice, but it contains varying types (!UseAny, !UseInterface)", filename, packageName, permissions, k)
 
- 							}
 
- 						}
 
- 					} else {
 
- 						if v == reflect.Float64 && A.Use32Bit {
 
- 							fh.WriteString(fmt.Sprintf("\t%s float32\n", CamelCase(k)))
 
- 							continue
 
- 						} else if v == reflect.Int64 && A.Use32Bit {
 
- 							fh.WriteString(fmt.Sprintf("\t%s int32\n", CamelCase(k)))
 
- 							continue
 
- 						}
 
- 						fh.WriteString(fmt.Sprintf("\t%s %s\n", CamelCase(k), v.String()))
 
- 					}
 
- 				}
 
- 			}
 
- 			fh.WriteString("}\n\n")
 
- 		} else if A.AssumeStruct && sameType {
 
- 			fh.WriteString(fmt.Sprintf("type %s%s struct {\n", A.PrefixStructName, CamelCase(name)))
 
- 			t := m[""]
 
- 			var kind reflect.Kind
 
- 			if t == reflect.Float64 && A.Use32Bit {
 
- 				kind = reflect.Float32
 
- 			} else if t == reflect.Int64 && A.Use32Bit {
 
- 				kind = reflect.Int32
 
- 			} else {
 
- 				kind = t
 
- 			}
 
- 			for k := range m {
 
- 				if k == "" {
 
- 					continue
 
- 				}
 
- 				fh.WriteString(fmt.Sprintf("\t%s %s\n", CamelCase(k), kind))
 
- 			}
 
- 			fh.WriteString("}\n\n")
 
- 		}
 
- 	}
 
- 	/*
 
- 		for _, name := range slices_to_structs {
 
- 			fh.WriteString(fmt.Sprintf("type %s%s struct {\n", A.PrefixStructName, name))
 
- 			for _,
 
- 		}
 
- 	*/
 
- 	return nil
 
- }
 
 
  |