Search code examples
jsongoreflect

JSON marshal of slice made from reflect.New() gives null in golang


I'm using reflect in golang to make a new initialized slice. But the moment I json.Marshal this new reflected slice, I get a JSON null value instead of a []. See this example here where I compare two cases:

package main

import (
  "encoding/json"
  "reflect"
  "log"
)
func makeslice(slice interface{}) interface{} {

  return reflect.New(reflect.TypeOf(slice)).Elem().Interface()
}
func main() {

  s0 := []int{2,3,5}

  s1 := makeslice(s0).([]int)
  js1,e1 := json.Marshal(s1)
  log.Println("case 1:",reflect.TypeOf(s1),reflect.ValueOf(s1),e1,string(js1))

  s2 := []int{}
  js2,e2 := json.Marshal(s2)
  log.Println("case 2:",reflect.TypeOf(s2),reflect.ValueOf(s2),e2,string(js2))

}

This gives output of:

case 1: []int [] <nil> null
case 2: []int [] <nil> []

Notice that case 1 and case 2 produce exactly the same log output except for the final json string, where the first case shows null and the second case shows [].

Why is this the case? What I want is for case 1 to show [] instead because my friend's client side application is always expecting an array, and a zero length array should not be shown as a null.

What am I doing wrong?


Solution

  • What reflect.New() does as per documentation:

    New returns a Value representing a pointer to a new zero value for the specified type. That is, the returned Value's Type is PtrTo(typ).

    In your code, both s0 and s2 are not nil. But s1 will be pointer of zero value of the []int type, which is nil; since the variable created using reflect.New().

    Code below is a proof that slice zero value is nil.

    var a []int = []int{}
    log.Printf("%#v \n", a) // ====> []int{}
    
    var b []int
    log.Printf("%#v \n", b) // ====> []int(nil)
    

    I suggest to use reflect.MakeSlice() instead for making a slice. The generated value won't be a nil.

    func makeslice(slice interface{}) interface{} {
        return reflect.MakeSlice(reflect.TypeOf(slice), 0, 0).Interface()
    }
    

    Then based on your code the output would be:

    case 1: []int [] <nil> []
    case 2: []int [] <nil> []
    

    Working playground: https://play.golang.org/p/tL3kFqVwOtC


    Converting []int with nil value into JSON will result null. But converting []int{} data into JSON will result [] because the data is empty slice (not a nil slice).


    As followup to comment, this method produces an addressable slice that can be modified further with reflect:

    func makeslice(slice interface{}) interface{} {
        newsliceval := reflect.MakeSlice(reflect.TypeOf(slice),0,0)
        newslice := reflect.New(newsliceval.Type()).Elem()
        newslice.Set(newsliceval)
    
        /*
         * make any desired changes to newslice with reflect package
         */
    
        return newslice.Interface()
    }
    

    More explanation here Why golang reflect.MakeSlice returns un-addressable Value.