astrut.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. package astruct
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "log"
  6. "os"
  7. "reflect"
  8. "strings"
  9. "golang.org/x/exp/maps"
  10. "golang.org/x/exp/slices"
  11. )
  12. type Map map[string]reflect.Kind
  13. // The main structure to use with astruct
  14. //
  15. // This structure contains options to configure behavior and methods in which to parse (JSON) and output (structs in source code)
  16. type Astruct struct {
  17. // Options
  18. Use32Bit bool // Defaults to false, using 64-bit int and 64-bit float, when true, uses 32-bit variants of int and float
  19. 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)
  20. VerboseLogging bool // Dump verbose info into logs (User must setup log first!)
  21. 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
  22. 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)
  23. SuffixForArrayLikes string // If not empty "" string, and it's an array this suffix is added (default's to "Item")
  24. // In-Memory Data (This data is gathered to form final products)
  25. // 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:
  26. // 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)
  27. // 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)
  28. 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)
  29. 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{})
  30. 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)
  31. Depth uint // Tracks how many nested structures (map, array/slice) deep we are
  32. }
  33. // Initializes to default options (this also resets the In-Memory Data to an initialized state)
  34. func (A *Astruct) Defaults() {
  35. A.Use32Bit = false
  36. A.UseAny = true
  37. A.VerboseLogging = false
  38. A.AssumeStruct = true
  39. A.PrefixStructName = ""
  40. A.SuffixForArrayLikes = "Item"
  41. A.Init()
  42. }
  43. // This resets the In-Memory Data to an initialized state
  44. func (A *Astruct) Init() {
  45. A.MapLikes = make(map[string]Map)
  46. A.ArrayLikes = make(map[string][]reflect.Kind)
  47. A.Parent = nil
  48. A.Depth = 0
  49. }
  50. // Creates a new Astruct, if given a Prefix for generated structs (only the first one) will be used
  51. func NewAstruct(PrefixStructName ...string) *Astruct {
  52. A := &Astruct{}
  53. A.Defaults()
  54. if len(PrefixStructName) != 0 {
  55. A.PrefixStructName = CamelCase(PrefixStructName[0])
  56. }
  57. return A
  58. }
  59. func (A *Astruct) ReadFile(filename string) error {
  60. data, err := os.ReadFile(filename)
  61. if err != nil {
  62. return fmt.Errorf("astruct.Astruct.ReadFile(filename='%s') On os.ReadFile > %w", filename, err)
  63. }
  64. var root map[string]any
  65. err = json.Unmarshal(data, &root)
  66. if err != nil {
  67. return fmt.Errorf("astruct.Astruct.ReadFile(filename='%s') On json.Unmarshal > %w", filename, err)
  68. }
  69. // Begin parsing...
  70. A.parse(root, "")
  71. return nil
  72. }
  73. func (A *Astruct) parse(at any, name string) error {
  74. switch reflect.TypeOf(at).Kind() {
  75. case reflect.Map:
  76. m := at.(map[string]any)
  77. var last_type reflect.Kind = 0
  78. var same_type bool = true
  79. for k, v := range m {
  80. if A.VerboseLogging {
  81. log.Printf(strings.Repeat(" ", int(A.Depth))+"'%s' = %s", k, reflect.TypeOf(v).Kind().String())
  82. }
  83. if same_type {
  84. if last_type == 0 {
  85. last_type = reflect.TypeOf(v).Kind()
  86. } else if reflect.TypeOf(v).Kind() != last_type {
  87. same_type = false
  88. }
  89. }
  90. switch reflect.TypeOf(v).Kind() {
  91. case reflect.Map:
  92. A.Depth += 1
  93. err := A.parse(v, k)
  94. if err != nil {
  95. return err
  96. }
  97. A.Depth -= 1
  98. case reflect.Array, reflect.Slice:
  99. A.Depth += 1
  100. err := A.parse(v, k)
  101. if err != nil {
  102. return err
  103. }
  104. A.Depth -= 1
  105. }
  106. }
  107. A.MapLikes[name] = make(Map)
  108. if !same_type {
  109. for k, v := range m {
  110. if reflect.TypeOf(v).Kind() == reflect.Float32 || reflect.TypeOf(v).Kind() == reflect.Float64 {
  111. f := v.(float64)
  112. if IsInt(f) {
  113. A.MapLikes[name][k] = reflect.Int64
  114. } else {
  115. A.MapLikes[name][k] = reflect.Float64
  116. }
  117. } else {
  118. A.MapLikes[name][k] = reflect.TypeOf(v).Kind()
  119. }
  120. }
  121. } else { // We'll use a empty string "", to indicate this map is same type
  122. A.MapLikes[name][""] = reflect.TypeOf(m[maps.Keys(m)[0]]).Kind()
  123. for k, v := range m {
  124. if reflect.TypeOf(v).Kind() == reflect.Float32 || reflect.TypeOf(v).Kind() == reflect.Float64 {
  125. f := v.(float64)
  126. if IsInt(f) {
  127. A.MapLikes[name][k] = reflect.Int64
  128. } else {
  129. A.MapLikes[name][k] = reflect.Float64
  130. }
  131. } else {
  132. A.MapLikes[name][k] = reflect.TypeOf(v).Kind()
  133. }
  134. }
  135. }
  136. case reflect.Array, reflect.Slice:
  137. a := at.([]any)
  138. var last_type reflect.Kind = 0
  139. var same_type bool = true
  140. for i, v := range a {
  141. if A.VerboseLogging {
  142. log.Printf(strings.Repeat(" ", int(A.Depth))+"%d = %s", i, reflect.TypeOf(v).Kind().String())
  143. }
  144. if same_type {
  145. if last_type == 0 {
  146. last_type = reflect.TypeOf(v).Kind()
  147. } else if reflect.TypeOf(v).Kind() != last_type {
  148. same_type = false
  149. }
  150. }
  151. switch reflect.TypeOf(v).Kind() {
  152. case reflect.Map:
  153. A.Depth += 1
  154. err := A.parse(v, A.SuffixForArrayLikes)
  155. if err != nil {
  156. return err
  157. }
  158. A.Depth -= 1
  159. case reflect.Array, reflect.Slice:
  160. A.Depth += 1
  161. err := A.parse(v, A.SuffixForArrayLikes)
  162. if err != nil {
  163. return err
  164. }
  165. A.Depth -= 1
  166. }
  167. }
  168. A.ArrayLikes[name] = []reflect.Kind{}
  169. if !same_type {
  170. for _, v := range a {
  171. if reflect.TypeOf(v).Kind() == reflect.Float32 || reflect.TypeOf(v).Kind() == reflect.Float64 {
  172. f := v.(float64)
  173. if IsInt(f) {
  174. A.ArrayLikes[name] = append(A.ArrayLikes[name], reflect.Int64)
  175. } else {
  176. A.ArrayLikes[name] = append(A.ArrayLikes[name], reflect.Float64)
  177. }
  178. } else {
  179. A.ArrayLikes[name] = append(A.ArrayLikes[name], reflect.TypeOf(v).Kind())
  180. }
  181. }
  182. } else {
  183. v := a[0]
  184. if reflect.TypeOf(v).Kind() == reflect.Float32 || reflect.TypeOf(v).Kind() == reflect.Float64 {
  185. f := v.(float64)
  186. if IsInt(f) {
  187. A.ArrayLikes[name] = append(A.ArrayLikes[name], reflect.Int64)
  188. } else {
  189. A.ArrayLikes[name] = append(A.ArrayLikes[name], reflect.Float64)
  190. }
  191. } else {
  192. A.ArrayLikes[name] = append(A.ArrayLikes[name], reflect.TypeOf(v).Kind())
  193. }
  194. }
  195. default:
  196. if A.VerboseLogging {
  197. log.Printf(strings.Repeat(" ", int(A.Depth))+"%s", reflect.TypeOf(at).Kind().String())
  198. }
  199. }
  200. return nil
  201. }
  202. // Checks if the given JSON Number (represented as float64) can be a int64 instead
  203. func IsInt(v float64) bool {
  204. str := fmt.Sprintf("%f", v)
  205. var hit_dot bool = false
  206. for _, r := range str {
  207. if r == '.' {
  208. hit_dot = true
  209. continue
  210. }
  211. if hit_dot {
  212. if r != '0' {
  213. return false
  214. }
  215. }
  216. }
  217. return true
  218. }
  219. // Checks if the given data (either map or array/slice) contains the same types
  220. //
  221. // Used to identify if a map or array/slice could be used (unless AssumeStruct is true in which a struct will always be made)
  222. func SameType(data any) bool {
  223. if reflect.TypeOf(data) == reflect.TypeOf(Map{}) {
  224. m := data.(Map)
  225. var last_type reflect.Kind = 0
  226. for _, v := range m {
  227. if last_type == 0 {
  228. last_type = v
  229. } else if v != last_type {
  230. return false
  231. }
  232. }
  233. return true
  234. }
  235. switch reflect.TypeOf(data).Kind() {
  236. case reflect.Map:
  237. m := data.(map[string]any)
  238. var last_type reflect.Kind = 0
  239. for _, v := range m {
  240. if last_type == 0 {
  241. last_type = reflect.TypeOf(v).Kind()
  242. } else if reflect.TypeOf(v).Kind() != last_type {
  243. return false
  244. }
  245. }
  246. return true
  247. case reflect.Array, reflect.Slice:
  248. a := data.([]any)
  249. var last_type reflect.Kind = 0
  250. for _, v := range a {
  251. if last_type == 0 {
  252. last_type = reflect.TypeOf(v).Kind()
  253. } else if reflect.TypeOf(v).Kind() != last_type {
  254. return false
  255. }
  256. }
  257. return true
  258. }
  259. return false
  260. }
  261. func CamelCase(field string) string {
  262. if len(field) == 0 {
  263. return ""
  264. }
  265. var result string
  266. if !strings.Contains(field, " ") || !strings.Contains(field, "-") {
  267. result += strings.ToUpper(string(field[0]))
  268. result += field[1:]
  269. return result
  270. }
  271. result = strings.ToTitle(field)
  272. result = strings.ReplaceAll(result, " ", "")
  273. result = strings.ReplaceAll(result, "-", "")
  274. return result
  275. }
  276. func (A *Astruct) WriteFile(filename, packageName string, permissions os.FileMode) error {
  277. if packageName == "" {
  278. return fmt.Errorf("astruct.Astruct.WriteFile(filename='%s', packageName='%s', permissions=%d) > %s", filename, packageName, permissions, "Missing 'packageName', this must not be empty!")
  279. }
  280. var successful bool = true
  281. fh, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, permissions)
  282. if err != nil {
  283. return fmt.Errorf("astruct.Astruct.WriteFile(filename='%s', packageName='%s', permissions=%d) On os.OpenFile > %w", filename, packageName, permissions, err)
  284. }
  285. defer func() {
  286. fh.Close()
  287. if !successful {
  288. err = os.Remove(filename)
  289. if err != nil {
  290. log.Panicf("astruct.Astruct.WriteFile(filename='%s', packageName='%s', permissions=%d) On os.Remove > %v", filename, packageName, permissions, err)
  291. }
  292. }
  293. }()
  294. fh.WriteString(fmt.Sprintf("package %s\n\n", packageName))
  295. //var slices_to_structs []string = []string{}
  296. var seen []string = []string{}
  297. for name, m := range A.MapLikes {
  298. /*if name == "" { // Ignore the root node for now
  299. continue
  300. }*/
  301. if slices.Contains(seen, name) {
  302. continue
  303. }
  304. _, sameType := m[""] // Check if the empty string exists in this map (if it does it contains same typed data)
  305. if !A.AssumeStruct || !sameType {
  306. fh.WriteString(fmt.Sprintf("type %s%s struct {\n", A.PrefixStructName, CamelCase(name)))
  307. for k, v := range m {
  308. if slices.Contains(maps.Keys(A.MapLikes), k) {
  309. _, sameType2 := A.MapLikes[k][""]
  310. if sameType2 && !A.AssumeStruct {
  311. fh.WriteString(fmt.Sprintf("\t%s map[string]%s\n", CamelCase(k), A.MapLikes[k][""].String()))
  312. seen = append(seen, k)
  313. } else if !sameType2 || A.AssumeStruct && v == reflect.Map {
  314. fh.WriteString(fmt.Sprintf("\t%s %s%s\n", CamelCase(k), A.PrefixStructName, CamelCase(k)))
  315. } else if !sameType2 || A.AssumeStruct && v != reflect.Map {
  316. fh.WriteString(fmt.Sprintf("\t%s %s\n", CamelCase(k), v.String()))
  317. }
  318. } else {
  319. if slices.Contains(maps.Keys(A.ArrayLikes), k) {
  320. a := A.ArrayLikes[k]
  321. if len(a) == 1 {
  322. if a[0] == reflect.Float64 && A.Use32Bit {
  323. fh.WriteString(fmt.Sprintf("\t%s []float32\n", CamelCase(k)))
  324. continue
  325. } else if a[0] == reflect.Int64 && A.Use32Bit {
  326. fh.WriteString(fmt.Sprintf("\t%s []int32\n", CamelCase(k)))
  327. continue
  328. } else if a[0] == reflect.Map {
  329. fh.WriteString(fmt.Sprintf("\t%s []%s%s\n", CamelCase(k), CamelCase(k), A.SuffixForArrayLikes))
  330. continue
  331. }
  332. fh.WriteString(fmt.Sprintf("\t%s []%s\n", CamelCase(k), a[0]))
  333. } else {
  334. //fh.WriteString(fmt.Sprintf("\t%s []%s\n", CamelCase(k), CamelCase(k)))
  335. //slices_to_structs = append(slices_to_structs, k)
  336. if A.UseAny {
  337. fh.WriteString(fmt.Sprintf("\t%s []any\n", CamelCase(k)))
  338. } else {
  339. 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)
  340. }
  341. }
  342. } else {
  343. if v == reflect.Float64 && A.Use32Bit {
  344. fh.WriteString(fmt.Sprintf("\t%s float32\n", CamelCase(k)))
  345. continue
  346. } else if v == reflect.Int64 && A.Use32Bit {
  347. fh.WriteString(fmt.Sprintf("\t%s int32\n", CamelCase(k)))
  348. continue
  349. }
  350. fh.WriteString(fmt.Sprintf("\t%s %s\n", CamelCase(k), v.String()))
  351. }
  352. }
  353. }
  354. fh.WriteString("}\n\n")
  355. } else if A.AssumeStruct && sameType {
  356. fh.WriteString(fmt.Sprintf("type %s%s struct {\n", A.PrefixStructName, CamelCase(name)))
  357. t := m[""]
  358. var kind reflect.Kind
  359. if t == reflect.Float64 && A.Use32Bit {
  360. kind = reflect.Float32
  361. } else if t == reflect.Int64 && A.Use32Bit {
  362. kind = reflect.Int32
  363. } else {
  364. kind = t
  365. }
  366. for k := range m {
  367. if k == "" {
  368. continue
  369. }
  370. fh.WriteString(fmt.Sprintf("\t%s %s\n", CamelCase(k), kind))
  371. }
  372. fh.WriteString("}\n\n")
  373. }
  374. }
  375. /*
  376. for _, name := range slices_to_structs {
  377. fh.WriteString(fmt.Sprintf("type %s%s struct {\n", A.PrefixStructName, name))
  378. for _,
  379. }
  380. */
  381. return nil
  382. }