about golang interface loop - loops

I wanto print a complicate dataset like bellowing
package main
import (
"fmt"
// "reflect"
)
func main() {
var initData []interface{}
initData[0] = map[string]interface{}{
"name": "k1",
"type": "line",
"data": []int{1, 2, 3, 4},
}
initData[1] = map[string]interface{}{
"name": "k2",
"type": "circle",
"data": []int{11, 12, 13, 14},
}
for _, data := range initData {
for k, v := range data {
fmt.Println(k)
for _, sv := range v {
fmt.Println(" " + sv)
}
}
}
}
but get an error
./maps-in-maps.go:56: cannot range over data (type interface {})
any help? to try to concert the interface typed dataset to interable like arry or slice

You must type assert each interface{} level first, in this case, use a type assertion at map[string]interface{} level since we know a definite type, and a type switch to check anticipated types for each nested value in the next level.
initData := []interface{}{
map[string]interface{}{
"name": "k1",
"type": "line",
"data": []int{1, 2, 3, 4},
},
map[string]interface{}{
"name": "k2",
"type": "circle",
"data": []int{11, 12, 13, 14},
},
}
for _, data := range initData {
for _, v := range data.(map[string]interface{}) {
switch t := v.(type) {
case string, []int:
fmt.Println(t)
default:
fmt.Println("wrong type")
}
}
}
}
Run playground here.

Related

Get consistent byte array output from json.Marshal

I'm working on a hashing function for a map[string]interface{}
Most of the hashing libs required []byte as input to compute the hash.
I tried to Marshal using the json.Marshal for simple maps it works correct but when i add some complexity and shuffled the items then json.Marshal fails to give me a consistent byte array output
package main
import (
"encoding/json"
"fmt"
)
func main() {
data := map[string]interface{}{
"id": "124",
"name": "name",
"count": 123456,
"sites": []map[string]interface{}{
{
"name": "123445",
"count": 234324,
"id": "wersfs",
},
{
"id": "sadcacasca",
"name": "sdvcscds",
"count": 22,
},
},
"list": []int{5, 324, 123, 123, 123, 14, 34, 52, 3},
}
data1 := map[string]interface{}{
"name": "name",
"id": "124",
"sites": []map[string]interface{}{
{
"id": "sadcacasca",
"count": 22,
"name": "sdvcscds",
},
{
"count": 234324,
"name": "123445",
"id": "wersfs",
},
},
"count": 123456,
"list": []int{123, 14, 34, 52, 3, 5, 324, 123, 123},
}
jsonStr, _ := json.Marshal(data)
jsonStr1, _ := json.Marshal(data1)
fmt.Println(jsonStr)
fmt.Println(jsonStr1)
for i := 0; i < len(jsonStr); i++ {
if jsonStr[i] != jsonStr1[i] {
fmt.Println("Byte arrays not equal")
}
}
}
This is what I have tried and it fails to give me a consistent output.
Moreover i was thinking to write a function which will do the sorting of the map and values as well, but then got stuck on how do I sort the
"sites": []map[string]interface{}
I tried json.Marshal and also sorting the map but got stuck
Your data sructures are not equivalent. According to JSON rules arrays are ordered, therefore [123, 14, 34, 52, 3, 5, 324, 123, 123] is not the same as [5, 324, 123, 123, 123, 14, 34, 52, 3]. No wonders the hashes are different. If you need different arrays with the same elements to produce the same hash, you need to canonicalize the arrays before hashing. E.g. sort them.
Here is how it could be done: https://go.dev/play/p/OHq7jsX_cNw
Before serilizing it recursively gos down the maps and arrays and prepares all arrays:
// Prepares data by sorting arrays in place
func prepare(data map[string]any) map[string]any {
for _, value := range data {
switch v := value.(type) {
case []int:
prepareIntArray(v)
case []string:
prepareStringArray(v)
case []map[string]any:
prepareMapArrayById(v)
for _, obj := range v {
prepare(obj)
}
case map[string]any:
prepare(v)
}
}
return data
}
// Sorts int array in place
func prepareIntArray(a []int) {
sort.Ints(a)
}
// Sorts string array in place
func prepareStringArray(a []string) {
sort.Strings(a)
}
// Sorts an array of objects by "id" fields
func prepareMapArrayById(mapSlice []map[string]any) {
sort.Slice(mapSlice, func(i, j int) bool {
return getId(mapSlice[i]) < getId(mapSlice[j])
})
}
// Extracts "id" field from JSON object. Returns empty string if there is no "id" or it is not a string.
func getId(v map[string]any) string {
idAny, ok := v["id"]
if !ok {
return ""
}
idStr, ok := idAny.(string)
if ok {
return idStr
} else {
return ""
}
}
As both the marshaled outputs are basically string representations of the same map in different sequences, if you sort their characters, they become equal.
following this logic, if you sort both jsonStr and jsonStr1, the sorted []byte(s) will be exactly equal. which then you can use to formulate your hash value.
check my solution here

How to format JSON correctly with arrays

I'm trying to send a JSON payload in my POST request but I'm not sure on how to format it correctly to use arrays. This below is what the correct JSON itself looks like:
{
"animal": "dog",
"contents": [{
"name": "daisy",
"VAL": "234.92133",
"age": 3
}]
}
I have this so far:
group := map[string]interface{}{
"animal": "dog",
"contents": map[string]interface{}{
"name": "daisy",
"VAL": "234.92133",
"age": 3,
},
}
But I can't figure out how to do array of contents (the square brackets), only the curly brackets from "contents".
The quick answer:
group := map[string]interface{}{
"animal": "dog",
"contents": []map[string]interface{}{
{
"name": "daisy",
"VAL": "234.92133",
"age": 3,
},
},
}
But as already said in the comments it is better (type safety) to use structs instead:
type Animal struct {
Type string `json:"animal"`
Contents []AnimalContent `json:"contents"`
}
type AnimalContent struct {
Name string `json:"name"`
Value string `json:"VAL"`
Age int `json:"age"`
}
Then create with:
group := Animal{
Type: "dog",
Contents: []AnimalContent{
{
Name: "daisy",
Value: "234.92133",
Age: 3,
},
},
}
// to transform to json format
bts, err := json.Marshal(group)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(bts))

How to dynamically assign objects to a string key in slices in Go?

I am trying to create an array from already created array. Array that I have is
{
"id": 1,
"category": "fruits",
"name": "Apple",
"description": "Apple is my favorite fruit."
}
{
"id": 2,
"category": "colors",
"name": "Red",
"description": "Red color is always charming."
}
{
"id": 3,
"category": "flowers",
"name": "Lotus",
"description": "It is one of the most beautiful flowers in this world."
}
{
"id": 4,
"category": "colors",
"name": "Pink",
"description": "A romantic color, mostly liked by women."
}
{
"id": 5,
"category": "flowers",
"name": "Rose",
"description": "I love roses."
}
{
"id": 6,
"category": "fruits",
"name": "Mango",
"description": "Mango is one of my favorite fruits."
}
Now I need to create an array and populate data like:
"elements":{
"fruits":{
0:{
"id": 1,
"category": "fruits",
"name": "Apple",
"description": "Apple is my favorite fruit."
}
1:{
"id": 6,
"category": "fruits",
"name": "Mango",
"description": "Mango is one of my favorite fruits."
}
}
"flowers":{
0:{
"id": 3,
"category": "flowers",
"name": "Lotus",
"description": "It is one of the most beautiful flowers in this world."
}
1:{
"id": 5,
"category": "flowers",
"name": "Rose",
"description": "I love roses."
}
}
"colors":{
0:{
"id": 2,
"category": "colors",
"name": "Red",
"description": "Red color is always charming."
}
1:{
"id": 4,
"category": "colors",
"name": "Pink",
"description": "A romantic color, mostly liked by women."
}
}
}
What I have tried is:
arr := make(map[string]interface{})
arrCate := make(map[string]interface{})
arrCateFlower := make(map[int]interface{})
arrCateColor := make(map[int]interface{})
arrCateFruit := make(map[int]interface{})
for index, data := range dataVals{
if(data.Category == "flower"){
arrCateFlower[index] = data
}
if(data.Category == "colors"){
arrCateColor[index] = data
}
if(data.Category == "fruits"){
arrCateFruit[index] = data
}
}
arrCate["flowers"] = arrCateFlower
arrCate["colors"] = arrCateColor
arrCate["fruits"] = arrCateFruit
arr["elements"] = arrCate
Where dataVals contain the unformatted data given at the top. By applying the above code I am able to get the proper output. But I don't think it is efficient way. If I try something like
arr := make(map[string]interface{})
arrCate := make(map[string]interface{})
for _, data := range dataVals{
arrCate[data.Category] = data
}
arr["elements"] = arrCate
Then I get something like:
"elements":{
"fruits":{
"id": 6,
"category": "fruits",
"name": "Mango",
"description": "Mango is one of my favorite fruits."
}
"flowers":{
"id": 5,
"category": "flowers",
"name": "Rose",
"description": "I love roses."
}
"colors":{
"id": 4,
"category": "colors",
"name": "Pink",
"description": "A romantic color, mostly liked by women."
}
}
the last elements of that particular category in the loop. I don't understand how can I get all the elements in the array without using any static values in code.
I have already spent hours in this. Can anyone please tell what am i missing in it?
https://play.golang.org/p/y-I6Fb_61R
I hope you can live with the additional outer {} pair.
And without the outer {} pair: https://play.golang.org/p/SSTgln0qJc
To not just have a bunch of links and to enable easy criticism of my solution by others, I include the code here, slightly redacted:
package main
import (
"fmt"
"encoding/json"
"log"
"strings"
)
var dataAsString = `` //put data between the ``
type Item struct {
Id int `json:"id"`
Category string `json:"category"`
Name string `json:"name"`
Description string `json:"description"`
}
type CategoryToItemSliceMap map[string][]Item
type CategoryToIndexItemMap map[string]map[int]Item
func main() {
// first read the data, we use a decoder as the input was given
// as a stream of seperate json objects and not a big single one.
decoder := json.NewDecoder(strings.NewReader(dataAsString))
var ourData []Item
for decoder.More() {
var it Item
err := decoder.Decode(&it)
if err != nil {
log.Fatalln(err)
}
ourData = append(ourData, it)
}
// collect items according to categories
catToItemSlice := CategoryToItemSliceMap{}
for _,v := range ourData {
catToItemSlice[v.Category] = append(catToItemSlice[v.Category],v)
}
// turn those slices into int -> Item maps so we get the index numbers
// in the encoded json
catToIndexItemMap := CategoryToIndexItemMap{}
for k,v := range catToItemSlice {
if catToIndexItemMap[k] == nil {
catToIndexItemMap[k] = map[int]Item{}
}
for index, item := range v {
catToIndexItemMap[k][index] = item
}
}
// easiest way to get the "elements: " without an additional outer {}
// brace pair
fmt.Printf("elements: ")
// We only have one json object in the output and that is a map, so we
// can use Unmarshal and don't need a streaming encoder. And get nice
// indentation with MarshalIndent.
out, err := json.MarshalIndent(catToIndexItemMap, "", " ")
if err != nil {
log.Fatalln(err)
}
fmt.Println(string(out))
}
// If atributes names equals , set value to fields
// target receive values of object
func Assign(target interface{}, object interface{}) {
t := reflect.ValueOf(target).Elem()
o := reflect.ValueOf(object).Elem()
for i := 0; i < o.NumField(); i++ {
for j := 0; j < t.NumField(); j++ {
if o.Type().Field(i).Name == t.Type().Field(j).Name {
t.Field(j).Set(o.Field(i))
}
}
}
}
// Use this exemple objects interfaces diffrents but fields are equals
// Assign(&target, &object)

appending non slice to map of slices

My current code is this:
name := "John"
id := "1234"
c := make(map[string][]string)
c["d"] = make([]string, len(d))
c["l"] = make([]string, len(l))
copy(c["d"], d)
copy(c["l"], l)
c["test"] = name
c["id"] = id
Assuming d & l are both []string. Go does not let me do this. Is there a way where I would be able to achieve a JSON like this:
{
"name": "John",
"id": "1234",
"d": [
123,
456
],
"l": [
123,
456
]
}
You need to use map[string]interface{} instead.
Also you don't need to copy your slices.
Example with map[string]interface{}:
name := "John"
id := "1234"
l, d := []string{"123", "456"}, []string{"789", "987"}
c := map[string]interface{}{
"d": d,
"l": l,
"test": name,
"id": id,
}
playground

AppEngine Datastore Encoding a Slice of Slices with Go

I have a need to store a slice of slices in that Datastore. Since the Datastore doesn't support two levels of slices, I have encode the child slice as JSON and store it as a []byte. I'm using the PropertyLoadSaver to accomplish this. The saving and loading works, but I've noticed that the saved entity has a empty []byte for every parent element. After some investigation I discovered this is caused by the Variants.OptionsJSON Property being referenced in the Save() method and also an attribute of the struct.
So my question are:
Is this a bug or am I doing something wrong?
Is there a better way to accomplish this?
The one constraint I have is that I need to be able to query the entity by one of the "Variant" attributes, but not the "Variant.Options".
Thanks!
Example:
Given JSON
{
"variants": [{
"options": [
{
"name": "One",
"value": "one"
},{
"name": "Two",
"value": "two"
}]
}, {
"options": [
{
"name": "Three",
"value": "three"
},{
"name": "Four",
"value": "four"
}]
}]
}]
}
How it's stored in the datastore, notice the two empty '', '' attributes at the end:
Entity Kind: Products
Entity Key: ahBkZXZ-c3RvcmVza2ktY29tchULEghQcm9kdWN0cxiAgICAgNDHCww
ID: 6507459568992256
Variants.OptionsJSON (list): `['[{"name":"One","value":"one"},{"name":"Two","value":"two"}]', '[{"name":"Three","value":"three"},{"name":"Four","value":"four"}]', '', '']`
model.go
import (
"appengine/datastore"
"encoding/json"
)
type Option struct {
Name string `json:"name"`
Value string `json:"value"`
}
type Variant struct {
OptionsJSON []byte `json:"-" datastore:"OptionsJSON"`
Options []Option `json:"options" datastore:"-"`
}
type Product struct {
Variants []Variant `json:"variants"`
}
func (x *Product) Load(c <-chan datastore.Property) error {
if err := datastore.LoadStruct(x, c); err != nil {
return err
}
for i, v := range x.Variants {
var opts []Option
json.Unmarshal(v.OptionsJSON, &opts)
x.Variants[i].Options = opts
}
return nil
}
func (x *Product) Save(c chan<- datastore.Property) error {
for _, v := range x.Variants {
b, _ := json.Marshal(v.Options)
c <- datastore.Property{
Name: "Variants.OptionsJSON",
Value: b,
NoIndex: true,
Multiple: true,
}
}
return datastore.SaveStruct(x, c)
}

Resources