Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save stnguyen90/06680ec293282d4e7249a3f548b283ab to your computer and use it in GitHub Desktop.
Save stnguyen90/06680ec293282d4e7249a3f548b283ab to your computer and use it in GitHub Desktop.
Handling Null JSON Arrays in Go - Performance
package main
import (
"encoding/json"
"fmt"
"reflect"
"time"
)
// Bag holds items
type Bag struct {
Name string
Items []string
}
// BagAlias is a bag that does not implement the Marshaler interface
type BagAlias Bag
// MarshalJSON initializes nil slices and then marshals the bag to JSON
func (b Bag) MarshalJSON() ([]byte, error) {
a := struct {
BagAlias
}{
BagAlias: (BagAlias)(b),
}
if a.Items == nil {
a.Items = make([]string, 0)
}
return json.Marshal(a)
}
// NilSliceToEmptySlice recursively sets nil slices to empty slices
func NilSliceToEmptySlice(inter interface{}) interface{} {
// original input that can't be modified
val := reflect.ValueOf(inter)
switch val.Kind() {
case reflect.Slice:
newSlice := reflect.MakeSlice(val.Type(), 0, val.Len())
if !val.IsZero() {
// iterate over each element in slice
for j := 0; j < val.Len(); j++ {
item := val.Index(j)
var newItem reflect.Value
switch item.Kind() {
case reflect.Struct:
// recursively handle nested struct
newItem = reflect.Indirect(reflect.ValueOf(NilSliceToEmptySlice(item.Interface())))
default:
newItem = item
}
newSlice = reflect.Append(newSlice, newItem)
}
}
return newSlice.Interface()
case reflect.Struct:
// new struct that will be returned
newStruct := reflect.New(reflect.TypeOf(inter))
newVal := newStruct.Elem()
// iterate over input's fields
for i := 0; i < val.NumField(); i++ {
newValField := newVal.Field(i)
valField := val.Field(i)
switch valField.Kind() {
case reflect.Slice:
// recursively handle nested slice
newValField.Set(reflect.Indirect(reflect.ValueOf(NilSliceToEmptySlice(valField.Interface()))))
case reflect.Struct:
// recursively handle nested struct
newValField.Set(reflect.Indirect(reflect.ValueOf(NilSliceToEmptySlice(valField.Interface()))))
default:
newValField.Set(valField)
}
}
return newStruct.Interface()
case reflect.Map:
// new map to be returned
newMap := reflect.MakeMap(reflect.TypeOf(inter))
// iterate over every key value pair in input map
iter := val.MapRange()
for iter.Next() {
k := iter.Key()
v := iter.Value()
// recursively handle nested value
newV := reflect.Indirect(reflect.ValueOf(NilSliceToEmptySlice(v.Interface())))
newMap.SetMapIndex(k, newV)
}
return newMap.Interface()
case reflect.Ptr:
// dereference pointer
return NilSliceToEmptySlice(val.Elem().Interface())
default:
return inter
}
}
// TimeIt runs f and returns how long it took
func TimeIt(f func()) time.Duration {
startTime := time.Now()
f()
endTime := time.Now()
return endTime.Sub(startTime)
}
func main() {
var diff time.Duration
length := 100000
items := []string{"item1", "item2", "item3"}
lotsOfBags := make([]BagAlias, length)
for i := range lotsOfBags {
lotsOfBags[i] = BagAlias{Items: items}
}
diff = TimeIt(func() {
newLotsOfBags := NilSliceToEmptySlice(lotsOfBags)
json.Marshal(newLotsOfBags)
})
fmt.Printf("NilSliceToEmptySlice() took %d milliseconds to process %d bags\n", diff.Milliseconds(), length)
bags := make([]Bag, length)
for i := range bags {
bags[i] = Bag{Items: items}
}
diff = TimeIt(func() {
json.Marshal(bags)
})
fmt.Printf("MarshalJSON() took %d milliseconds to process %d bags\n", diff.Milliseconds(), length)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment