143

I tried to convert my Go map to a json string with encoding/json Marshal, but it resulted in a empty string.

Here's my code :

package main

import (
    "encoding/json"
    "fmt"
)

type Foo struct {
    Number int    `json:"number"`
    Title  string `json:"title"`
}

func main() {
    datas := make(map[int]Foo)

    for i := 0; i < 10; i++ {
        datas[i] = Foo{Number: 1, Title: "test"}
    }

    jsonString, _ := json.Marshal(datas)

    fmt.Println(datas)
    fmt.Println(jsonString)
}

My output is :

map[9:{1 test} 2:{1 test} 7:{1 test} 3:{1 test} 4:{1 test} 5:{1 test} 6:{1 test} 8:{1 test} 0:{1 test} 1:{1 test}]

[]

I really don't know where I'm wrong. Thank you for your help.

Rick-777
  • 9,714
  • 5
  • 34
  • 50
Cronos87
  • 1,816
  • 3
  • 12
  • 14
  • 56
    Please don't downvote without giving a comment. I think the question is a good question (+1): it contains all the code, it contains a precise question, the output, ... It's totally on topic and the OP has made much effort to ask a good question. It's really a shame to have the downvotes here! – topskip Jul 09 '14 at 11:56
  • 5
    The problem does stem from the fact that the OP explicitly ignores the error that would have answered the question imediately. – JimB Jul 09 '14 at 12:34
  • 4
    I'm clearly conscientious I was wrong. Two errors in one question. You can be sure that I will not repeat them. – Cronos87 Jul 09 '14 at 12:42

5 Answers5

161

If you had caught the error, you would have seen this:

jsonString, err := json.Marshal(datas)
fmt.Println(err)

// [] json: unsupported type: map[int]main.Foo

The thing is you cannot use integers as keys in JSON; it is forbidden. Instead, you can convert these values to strings beforehand, for instance using strconv.Itoa.

See this post for more details: https://stackoverflow.com/a/24284721/2679935

Community
  • 1
  • 1
julienc
  • 19,087
  • 17
  • 82
  • 82
  • 3
    Here you can see how the types map: http://golang.org/pkg/encoding/json/#Unmarshal You could use a slice instead, which will map to a JSON array. Also: always check for errors ;) – seong Jul 09 '14 at 11:50
  • 2
    I guess the behaviour has changed. See https://golang.org/pkg/encoding/json/#Unmarshal for "The map's key type must either be a string, an integer type, or implement encoding.TextMarshaler." – Ashhar Hasan Oct 12 '17 at 05:31
  • @AshharHasan Apparently it changed in Go 1.7 (https://golang.org/doc/go1.7#encoding_json), but it still does not do what you would expect: https://play.golang.org/p/0aFaQ_ByOk – julienc Oct 13 '17 at 15:49
  • is there a way to do this with a sync.Map? – Shahrukh Mohammad Nov 22 '19 at 13:40
  • @ShahrukhMohammad I've not used Go in years, I won't be able to answer your question... Maybe try creating a new question on SO! – julienc Nov 22 '19 at 13:48
32

It actually tells you what's wrong, but you ignored it because you didn't check the error returned from json.Marshal.

json: unsupported type: map[int]main.Foo

JSON spec doesn't support anything except strings for object keys, while javascript won't be fussy about it, it's still illegal.

You have two options:

1 Use map[string]Foo and convert the index to string (using fmt.Sprint for example):

datas := make(map[string]Foo, N)

for i := 0; i < 10; i++ {
    datas[fmt.Sprint(i)] = Foo{Number: 1, Title: "test"}
}
j, err := json.Marshal(datas)
fmt.Println(string(j), err)

2 Simply just use a slice (javascript array):

datas2 := make([]Foo, N)
for i := 0; i < 10; i++ {
    datas2[i] = Foo{Number: 1, Title: "test"}
}
j, err = json.Marshal(datas2)
fmt.Println(string(j), err)

playground

OneOfOne
  • 95,033
  • 20
  • 184
  • 185
  • 4
    You're right. It's a shamefully error... I don't really know why I used a int for a json key... Thank you for your examples. – Cronos87 Jul 09 '14 at 12:27
5

This behaviour has been changed over time. I am using go v1.16.5 and I can happily pass int type in as JSON key. I have tried the same problem now and I can see the below result. Type conversion of a non-string key has been added by textMarshaler and textUnmarshaler interfaces. For more info, you can visit this https://golang.org/pkg/encoding/json/#Marshal

type Foo struct {
    Number int    `json:"number"`
    Title  string `json:"title"`
}

datas := make(map[int]Foo)

for i := 0; i < 5; i++ {
    datas[i] = Foo{Number: 1, Title: "test"}
}

jsonString, _ := json.Marshal(datas)

fmt.Println("Datasets Result : ", datas)
fmt.Println("Marshal Datasets Result : ", string(jsonString), err)

m := make(map[int]Foo)
err = json.Unmarshal(jsonString, &m)
if err != nil {
    panic(err)
}

fmt.Println("Unmarshal JSON Result : ", m)

Output:

Datasets Result map : [0:{1 test} 1:{1 test} 2:{1 test} 3:{1 test} 4:{1 test}]

Marshal Datasets Result : {"0":{"number":1,"title":"test"},"1":{"number":1,"title":"test"},"2":{"number":1,"title":"test"},"3":{"number":1,"title":"test"},"4":{"number":1,"title":"test"}} <nil>

Unmarshal JSON Result :  map[0:{1 test} 1:{1 test} 2:{1 test} 3:{1 test} 4:{1 test}]
Kamal Kant
  • 1,031
  • 1
  • 12
  • 22
4

Since this question was asked/last answered, support for non string key types for maps for json Marshal/UnMarshal has been added through the use of TextMarshaler and TextUnmarshaler interfaces here. You could just implement these interfaces for your key types and then json.Marshal would work as expected.

package main

import (
    "encoding/json"
    "fmt"
    "strconv"
)

// Num wraps the int value so that we can implement the TextMarshaler and TextUnmarshaler 
type Num int

func (n *Num) UnmarshalText(text []byte) error {
    i, err := strconv.Atoi(string(text))
    if err != nil {
        return err
    }
    *n = Num(i)
    return nil
}

func (n Num) MarshalText() (text []byte, err error) {
    return []byte(strconv.Itoa(int(n))), nil
}

type Foo struct {
    Number Num    `json:"number"`
    Title  string `json:"title"`
}

func main() {
    datas := make(map[Num]Foo)

    for i := 0; i < 10; i++ {
        datas[Num(i)] = Foo{Number: 1, Title: "test"}
    }

    jsonString, err := json.Marshal(datas)
    if err != nil {
        panic(err)
    }

    fmt.Println(datas)
    fmt.Println(jsonString)

    m := make(map[Num]Foo)
    err = json.Unmarshal(jsonString, &m)
    if err != nil {
        panic(err)
    }

    fmt.Println(m)
}

Output:

map[1:{1 test} 2:{1 test} 4:{1 test} 7:{1 test} 8:{1 test} 9:{1 test} 0:{1 test} 3:{1 test} 5:{1 test} 6:{1 test}]
[123 34 48 34 58 123 34 110 117 109 98 101 114 34 58 34 49 34 44 34 116 105 116 108 101 34 58 34 116 101 115 116 34 125 44 34 49 34 58 123 34 110 117 109 98 101 114 34 58 34 49 34 44 34 116 105 116 108 101 34 58 34 116 101 115 116 34 125 44 34 50 34 58 123 34 110 117 109 98 101 114 34 58 34 49 34 44 34 116 105 116 108 101 34 58 34 116 101 115 116 34 125 44 34 51 34 58 123 34 110 117 109 98 101 114 34 58 34 49 34 44 34 116 105 116 108 101 34 58 34 116 101 115 116 34 125 44 34 52 34 58 123 34 110 117 109 98 101 114 34 58 34 49 34 44 34 116 105 116 108 101 34 58 34 116 101 115 116 34 125 44 34 53 34 58 123 34 110 117 109 98 101 114 34 58 34 49 34 44 34 116 105 116 108 101 34 58 34 116 101 115 116 34 125 44 34 54 34 58 123 34 110 117 109 98 101 114 34 58 34 49 34 44 34 116 105 116 108 101 34 58 34 116 101 115 116 34 125 44 34 55 34 58 123 34 110 117 109 98 101 114 34 58 34 49 34 44 34 116 105 116 108 101 34 58 34 116 101 115 116 34 125 44 34 56 34 58 123 34 110 117 109 98 101 114 34 58 34 49 34 44 34 116 105 116 108 101 34 58 34 116 101 115 116 34 125 44 34 57 34 58 123 34 110 117 109 98 101 114 34 58 34 49 34 44 34 116 105 116 108 101 34 58 34 116 101 115 116 34 125 125]
map[4:{1 test} 5:{1 test} 6:{1 test} 7:{1 test} 0:{1 test} 2:{1 test} 3:{1 test} 1:{1 test} 8:{1 test} 9:{1 test}]
vim
  • 1,098
  • 9
  • 21
3
data := map[string]interface{}
jsonByte, err := json.Marshal(data)
if err != nil{
    log.Fatal(err)
}
jsonString := string(jsonByte)
fmt.Println(jsonString) // will be printing data map in json formate.
Aman Agarwal
  • 396
  • 3
  • 5
  • 1
    While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. – mufazmi May 18 '21 at 19:54
  • 1
    @mufazmi i think the code is self explanatory , still json.Marshal converts the data into json byte array which we use for json format by converting it into string . You can also look for json.Encode method for same problem. – Aman Agarwal May 20 '21 at 06:28