Browse Source

Real world JSON example works

You can now run the example which will produce output.go (a series of
structures to JSON unmarshal test.json, which is real data from
OpenWeatherApi)

I need to update README (some of these "BUGS" and features have been
taken care of)
Apollo 1 year ago
parent
commit
766ec742b1
2 changed files with 116 additions and 47 deletions
  1. 55 26
      astrut.go
  2. 61 21
      example/main.go

+ 55 - 26
astrut.go

@@ -20,12 +20,12 @@ type Map map[string]reflect.Kind
 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 false the key word any will be replaced with interface{} (unless UseInterface is false, in which error is thrown)
-	UseInterface     bool   // Defaults to true, when astruct isn't able to determine a type (being an array with multiple types) it will fallback to interface{} (unless UseAny is true in which any is used)
-	VerboseLogging   bool   // Dump verbose info into logs (User must setup log first!)
-	AssumeStruct     bool   // Even if a type could be a map it will make a structure for it instead, default false (instead it will try to use a map unless varying types detected)
-	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)
+	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:
@@ -42,10 +42,10 @@ type Astruct struct {
 func (A *Astruct) Defaults() {
 	A.Use32Bit = false
 	A.UseAny = true
-	A.UseInterface = true
 	A.VerboseLogging = false
-	A.AssumeStruct = false
+	A.AssumeStruct = true
 	A.PrefixStructName = ""
+	A.SuffixForArrayLikes = "Item"
 	A.Init()
 }
 
@@ -128,8 +128,20 @@ func (A *Astruct) parse(at any, name string) error {
 					A.MapLikes[name][k] = reflect.TypeOf(v).Kind()
 				}
 			}
-		} else {
-			A.MapLikes[name]["all"] = reflect.TypeOf(m[maps.Keys(m)[0]]).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)
@@ -147,14 +159,14 @@ func (A *Astruct) parse(at any, name string) error {
 			switch reflect.TypeOf(v).Kind() {
 			case reflect.Map:
 				A.Depth += 1
-				err := A.parse(v, fmt.Sprint(i))
+				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, fmt.Sprint(i))
+				err := A.parse(v, A.SuffixForArrayLikes)
 				if err != nil {
 					return err
 				}
@@ -296,21 +308,25 @@ func (A *Astruct) WriteFile(filename, packageName string, permissions os.FileMod
 	//var slices_to_structs []string = []string{}
 	var seen []string = []string{}
 	for name, m := range A.MapLikes {
-		if name == "all" { // Ignore the root node for now
+		/*if name == "" { // Ignore the root node for now
 			continue
-		}
+		}*/
 		if slices.Contains(seen, name) {
 			continue
 		}
-		if !A.AssumeStruct || !SameType(m) {
+		_, 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) {
-					if SameType(A.MapLikes[k]) {
-						fh.WriteString(fmt.Sprintf("\t%s map[string]%s\n", CamelCase(k), A.MapLikes[k]["all"].String()))
+					_, 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 {
+					} 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) {
@@ -322,21 +338,16 @@ func (A *Astruct) WriteFile(filename, packageName string, permissions os.FileMod
 							} 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 && A.AssumeStruct {
-								fh.WriteString(fmt.Sprintf("\t%s []%s\n", CamelCase(k), CamelCase(k)))
-								continue
-							} else if a[0] == reflect.Map && !A.AssumeStruct {
-								fh.WriteString(fmt.Sprintf("\t%s []%s%d\n", CamelCase(k), CamelCase(k), 0))
+							} 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 && A.UseInterface {
+							if A.UseAny {
 								fh.WriteString(fmt.Sprintf("\t%s []any\n", CamelCase(k)))
-							} else if !A.UseAny && A.UseInterface {
-								fh.WriteString(fmt.Sprintf("\t%s []interface{}\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)
 							}
@@ -354,6 +365,24 @@ func (A *Astruct) WriteFile(filename, packageName string, permissions os.FileMod
 				}
 			}
 			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")
 		}
 	}
 	/*

+ 61 - 21
example/main.go

@@ -1,41 +1,81 @@
 package main
 
 import (
+	"encoding/json"
+	"errors"
 	"fmt"
+	"os"
+	"time"
 
 	"git.red-green.com/david/astruct"
 )
 
 func main() {
-	var a *astruct.Astruct = astruct.NewAstruct("Weather")
-	var err error = a.ReadFile("test.json")
+	var err error
+	_, err = os.Stat("output.go")
 	if err != nil {
-		fmt.Println("Err:", err)
-		return
-	}
-	fmt.Println("MapLikes:")
-	for name, m := range a.MapLikes {
-		if name == "" {
-			continue
+		if !errors.Is(err, os.ErrNotExist) {
+			fmt.Println("Err:", err)
+			return
 		}
-		fmt.Printf("%s\n", name)
-		for k, v := range m {
-			fmt.Printf("  '%s' = %s\n", k, v.String())
+		var a *astruct.Astruct = astruct.NewAstruct("Weather")
+		err = a.ReadFile("test.json")
+		if err != nil {
+			fmt.Println("Err:", err)
+			return
 		}
-	}
-	fmt.Println("ArrayLikes:")
-	for name, a := range a.ArrayLikes {
-		if name == "" {
-			continue
+		fmt.Println("MapLikes:")
+		for name, m := range a.MapLikes {
+			if name == "" {
+				continue
+			}
+			fmt.Printf("%s\n", name)
+			for k, v := range m {
+				fmt.Printf("  '%s' = %s\n", k, v.String())
+			}
+		}
+		fmt.Println("ArrayLikes:")
+		for name, a := range a.ArrayLikes {
+			if name == "" {
+				continue
+			}
+			fmt.Printf("%s\n", name)
+			for i, v := range a {
+				fmt.Printf("  %d = %s\n", i, v.String())
+			}
 		}
-		fmt.Printf("%s\n", name)
-		for i, v := range a {
-			fmt.Printf("  %d = %s\n", i, v.String())
+		err = a.WriteFile("output.go", "main", 0666)
+		if err != nil {
+			fmt.Println("Err:", err)
+			return
 		}
 	}
-	err = a.WriteFile("output.txt", "test", 0666)
+	// --- Test unmarshaling our new go code ---
+	var (
+		pay  []byte
+		data Weather
+	)
+	pay, err = os.ReadFile("test.json")
+	if err != nil {
+		fmt.Println("Err:", err)
+		return
+	}
+	err = json.Unmarshal(pay, &data)
 	if err != nil {
 		fmt.Println("Err:", err)
 		return
 	}
+	fmt.Println("Weather Report:")
+	fmt.Printf("%s, %s\n", data.Name, data.Sys.Country)
+	fmt.Printf("lon %0.4f, lat %0.4f\n", data.Coord.Lon, data.Coord.Lat)
+	fmt.Printf("Currently %s with %0.0f%% cload coverage\n", data.Weather[0].Description, data.Clouds.All)
+	fmt.Printf("Wind of %0.1f MPH with gusts of upto %0.1f MPH\n", data.Wind.Speed, data.Wind.Gust)
+	fmt.Printf("It feels like %0.1f F, Low is %0.1f F with a high of %0.1f F\n", data.Main.Feels_like, data.Main.Temp_min, data.Main.Temp_max)
+	fmt.Printf("Humidity of %0.0f%%\n", data.Main.Humidity)
+	at := time.Unix(data.Dt, 0).Round(time.Second)
+	fmt.Printf("This report was generated on %s\n", at.Format(time.UnixDate))
+	rise := time.Unix(data.Sys.Sunrise, 0).Round(time.Second)
+	set := time.Unix(data.Sys.Sunset, 0).Round(time.Second)
+	fmt.Printf("Sun rise at %s\n", rise.Format("03:04:05PM"))
+	fmt.Printf("Sun set at %s\n", set.Format("03:04:05PM"))
 }