package main import ( "encoding/json" "fmt" "log" "os" "github.com/iancoleman/orderedmap" "github.com/xuri/excelize/v2" ) // Initial concept from https://stackoverflow.com/q/68621039 func main() { jsonFile := "test.json" parentNode := "input" sheetName := "Sheet2" outputFilename := "test.xlsx" var xlsx *excelize.File var s []byte var sheetIndex int var err error var cell string var row, column int // TODO - truncate sheetName to the maximum 31 characters // Check if xlsx file exists already, and if it does then open and append data if fileExists(outputFilename) { fmt.Printf("Output spreadsheet '%s' already exists.\n", outputFilename) xlsx, err = excelize.OpenFile(outputFilename) if err != nil { fmt.Println(err) return } // Since we have an existing workbook, check if the sheet we want to write to already exists sheetFound := false for index, name := range xlsx.GetSheetMap() { if name == sheetName { fmt.Printf("Found worksheet '%s' at index '%d'\n", sheetName, index) sheetFound = true } } if !sheetFound { // Create the sheet fmt.Printf("Creating worksheet '%s'\n", sheetName) sheetIndex, err = xlsx.NewSheet(sheetName) if err != nil { fmt.Printf("Error creating worksheet '%s' : %s\n", sheetName, err) } // Set active worksheet fmt.Printf("Setting active sheet to index %d", sheetIndex) xlsx.SetActiveSheet(sheetIndex) } } else { fmt.Printf("Creating output spreadsheet '%s'\n", outputFilename) // Create the file xlsx = excelize.NewFile() // Rename the default Sheet1 to this worksheet name if sheetName != "Sheet1" { fmt.Printf("Renaming default worksheet to '%s'\n", sheetName) err = xlsx.SetSheetName("Sheet1", sheetName) if err != nil { fmt.Printf("Error setting sheet name to '%s': %s\n", sheetName, err) } } } // Read the json input file if fileExists(jsonFile) { s, err = os.ReadFile(jsonFile) if err != nil { panic(err) } } else { fmt.Printf("Input JSON file '%s' does not exist.\n", jsonFile) } // Read the json into an orderedmap to preserve the ordering of json structure o := orderedmap.New() err = json.Unmarshal([]byte(s), &o) if err != nil { fmt.Printf("JSON Unmarshal error %s\n", err) } // Assume that our content is within the first top-level key topLevel := o.Keys() fmt.Printf("topLevel: %v\n", topLevel) parentNode = topLevel[0] // Get a reference to the top level node we specified earlier // TODO - validate this key exists vislice, ok := o.Get(parentNode) if !ok { fmt.Printf("Missing key for multitype array") } vslice := vislice.([]interface{}) // Get the keys for the first element so we know what the column names will be columnMap := vslice[0].(orderedmap.OrderedMap) columnNames := columnMap.Keys() fmt.Printf("Creating excel workbook with following headings : '%v'\n", columnNames) // Add the header row row = 1 column = 1 for i := 0; i < len(columnNames); i++ { cell, _ = excelize.CoordinatesToCellName(column, row) fmt.Printf("Setting cell %s to value %s\n", cell, columnNames[i]) xlsx.SetCellValue(sheetName, cell, columnNames[i]) column++ } // Set starting row for data row = 2 fmt.Printf("Adding %d rows of data to spreadsheet.\n", len(vslice)) // Iterate the whole slice to get the data and add to the worksheet for i, v := range vslice { // Print the contents each slice //fmt.Printf("'%d' : '%v'\n", i, v) //fmt.Printf("Slice element %d\n", i) _ = i // Each iteration should start back at column 1 column = 1 // Print each key-value pair contained in this slice vmap := v.(orderedmap.OrderedMap) k := vmap.Keys() for j := range k { //a = string(asciiValue) //cell = a + strconv.Itoa(2+i) cell, _ = excelize.CoordinatesToCellName(column, row) e, _ := vmap.Get(k[j]) //fmt.Printf("Setting cell %s to value %v\n", cell, e) xlsx.SetCellValue(sheetName, cell, e) // Move to the next column //asciiValue++ column++ } //fmt.Printf("k: %v\n", k) // Move to next row row++ } // Close off the file if err := xlsx.SaveAs(outputFilename); err != nil { log.Fatal(err) return } fmt.Printf("Excel file '%s' generated sucessfully.\n", outputFilename) /* //Test https://gitlab.com/c0b/go-ordered-json var om *ordered.OrderedMap = ordered.NewOrderedMap() err = json.Unmarshal([]byte(file), om) if err != nil { fmt.Printf("error: '%s'\n", err) } iter := om.EntriesIter() for { pair, ok := iter() if !ok { break } fmt.Printf("%-12s: %v\n", pair.Key, pair.Value) } */ } func fileExists(filename string) bool { info, err := os.Stat(filename) if os.IsNotExist(err) { return false } return !info.IsDir() } func TestUnmarshalJSON() { s := `{ "number": 4, "string": "x", "z": 1, "a": "should not break with unclosed { character in value", "b": 3, "slice": [ "1", 1 ], "orderedmap": { "e": 1, "a { nested key with brace": "with a }}}} }} {{{ brace value", "after": { "link": "test {{{ with even deeper nested braces }" } }, "test\"ing": 9, "after": 1, "multitype_array": [ "test", 1, { "map": "obj", "it" : 5, ":colon in key": "colon: in value" }, [{"inner": "map"}] ], "should not break with { character in key": 1 }` o := orderedmap.New() err := json.Unmarshal([]byte(s), &o) if err != nil { fmt.Printf("JSON Unmarshal error", err) } // Check the root keys expectedKeys := []string{ "number", "string", "z", "a", "b", "slice", "orderedmap", "test\"ing", "after", "multitype_array", "should not break with { character in key", } k := o.Keys() for i := range k { if k[i] != expectedKeys[i] { fmt.Printf("Unmarshal root key order", i, k[i], "!=", expectedKeys[i]) } } // Check nested maps are converted to orderedmaps // nested 1 level deep expectedKeys = []string{ "e", "a { nested key with brace", "after", } vi, ok := o.Get("orderedmap") if !ok { fmt.Printf("Missing key for nested map 1 deep") } v := vi.(orderedmap.OrderedMap) k = v.Keys() for i := range k { if k[i] != expectedKeys[i] { fmt.Printf("Key order for nested map 1 deep ", i, k[i], "!=", expectedKeys[i]) } } // nested 2 levels deep expectedKeys = []string{ "link", } vi, ok = v.Get("after") if !ok { fmt.Printf("Missing key for nested map 2 deep") } v = vi.(orderedmap.OrderedMap) k = v.Keys() for i := range k { if k[i] != expectedKeys[i] { fmt.Printf("Key order for nested map 2 deep", i, k[i], "!=", expectedKeys[i]) } } // multitype array expectedKeys = []string{ "map", "it", ":colon in key", } vislice, ok := o.Get("multitype_array") if !ok { fmt.Printf("Missing key for multitype array") } vslice := vislice.([]interface{}) vmap := vslice[2].(orderedmap.OrderedMap) k = vmap.Keys() for i := range k { if k[i] != expectedKeys[i] { fmt.Printf("Key order for nested map 2 deep", i, k[i], "!=", expectedKeys[i]) } } // nested map 3 deep vislice, _ = o.Get("multitype_array") vslice = vislice.([]interface{}) expectedKeys = []string{"inner"} vinnerslice := vslice[3].([]interface{}) vinnermap := vinnerslice[0].(orderedmap.OrderedMap) k = vinnermap.Keys() for i := range k { if k[i] != expectedKeys[i] { fmt.Printf("Key order for nested map 3 deep", i, k[i], "!=", expectedKeys[i]) } } }