diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c7ae8c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Ignore any generated xlsx documents +*.xlsx \ No newline at end of file diff --git a/Onkar.xlsx b/Onkar.xlsx deleted file mode 100755 index 95f53d5..0000000 Binary files a/Onkar.xlsx and /dev/null differ diff --git a/go.mod b/go.mod index 7d111ca..7279963 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,21 @@ module json2excel go 1.19 -require github.com/iancoleman/orderedmap v0.2.0 +require ( + github.com/iancoleman/orderedmap v0.2.0 + github.com/xuri/excelize/v2 v2.7.0 +) + +require ( + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/richardlehane/mscfb v1.0.4 // indirect + github.com/richardlehane/msoleps v1.0.3 // indirect + github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect + github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect + golang.org/x/crypto v0.5.0 // indirect + golang.org/x/net v0.5.0 // indirect + golang.org/x/text v0.6.0 // indirect +) //require internal/ordered v1.0.0 diff --git a/go.sum b/go.sum index 1c0ad2a..5681125 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,62 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6jq8pNA= github.com/iancoleman/orderedmap v0.2.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= +github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= +github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM= +github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c= +github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/excelize/v2 v2.7.0 h1:Hri/czwyRCW6f6zrCDWXcXKshlq4xAZNpNOpdfnFhEw= +github.com/xuri/excelize/v2 v2.7.0/go.mod h1:ebKlRoS+rGyLMyUx3ErBECXs/HNYqyj+PbkkKRK5vSI= +github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M= +github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= +golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= +golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY= +golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 4f35b26..49dab05 100644 --- a/main.go +++ b/main.go @@ -3,28 +3,98 @@ package main import ( "encoding/json" "fmt" - "io/ioutil" + "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" - s, err := ioutil.ReadFile("test.json") - if err != nil { - panic(err) + 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) + } + } } - //TestUnmarshalJSON() + // 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", err) + 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) @@ -38,22 +108,63 @@ func main() { 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) + //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("'%s' : '%v'\n", k[j], e) + //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() @@ -71,50 +182,14 @@ func main() { fmt.Printf("%-12s: %v\n", pair.Key, pair.Value) } */ +} - /* - var data interface{} - json.Unmarshal(file, &data) // reading all json data - //fmt.Println(data) - t, ok := data.([]interface{}) // assertion Interface - _ = ok - //fmt.Printf("%[1]v %[1]T\n", t) //map[string]interface {} - - myMap := t[0] // aim here to get APP, Company and Category which will be the column name of Excel sheet - fmt.Printf("myMap: %v\n", myMap) - columnData, _ := myMap.(map[string]interface{}) // extract the underlying concrete data from interface - fmt.Printf("columnData: %v\n", columnData) - keys := make([]string, 0, len(columnData)) // creating and initializing slice to store column - - for k := range columnData { - fmt.Printf("%[1]v %[1]T\n", k) - keys = append(keys, k) - } - - return - - xlsx := excelize.NewFile() - sheetName := "Sheet1" - xlsx.SetSheetName(xlsx.GetSheetName(1), sheetName) - c := 'A' - asciiValue := int(c) - var a string - - for i := 0; i < len(keys); i++ { - a = string(asciiValue) - xlsx.SetCellValue(sheetName, a+"1", keys[i]) - asciiValue++ - } - - err = xlsx.SaveAs("./Onkar.xlsx") - - if err != nil { - fmt.Println(err) - return - } - - fmt.Println("Excel file generated sucessfully") - */ +func fileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() } func TestUnmarshalJSON() {