back


Sometimes a client-side application could send a valid integer as a string while the application expects an integer. Unfortunately, that leads to an error when converting the JSON into a struct.

  • json: cannot unmarshal string into Go struct field Item.item_id of type int

For example consider the snippet below 1:

package main

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

func main() {
  type Item struct {
    Name   string `json:"name"`
    ItemId int    `json:"item_id"`
  }
  jsonData := []byte(`{"name":"item 1","item_id":"30"}`)
  var item Item
  err := json.Unmarshal(jsonData, &item)
  if err != nil {
    log.Fatal(err)
  }
  fmt.Println("vim-go")
}

The expectation is the application converting the string “30” to an integer 30 since it’s a valid integer.

However, what happens is the following error instead:

  • json: cannot unmarshal string into Go struct field Item.item_id of type int

How to solve this issue, though?

The good news is that Golang allows writing a custom JSON marshaller and unmarshalers. It allows creating a new type alias for the int type and adding a custom marshaller and unmarshaler for the JSON; this way, it can check the data and convert the string to int before unmarshal. 2

// StringInt create a type alias for type int
type StringInt int

// UnmarshalJSON create a custom unmarshal for the StringInt
// this helps us check the type of our value before unmarshalling it

func (st *StringInt) UnmarshalJSON(b []byte) error {
  // convert the bytes into an interface
  // this will help us check the type of our value
  // if it is a string that can be converted into a int we convert it
  // otherwise we return an error
  var item interface{}
  if err := json.Unmarshal(b, &item); err != nil {
    return err
  }
  switch v := item.(type) {
  case int:
    *st = StringInt(v)
  case float64:
    *st = StringInt(int(v))
  case string:
    // here convert the string into
    // an integer
    i, err := strconv.Atoi(v)
    if err != nil {
      // the string might not be of integer type
      // so return an error
      return err
    }
    *st = StringInt(i)
  }
  return nil
}

type Item struct {
  Name   string    `json:"name"`
  ItemId StringInt `json:"item_id"`
}

a full code snippet

A full code snippet below 2:

package main

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

// StringInt create a type alias for type int
type StringInt int

// UnmarshalJSON create a custom unmarshal for the StringInt
// this helps us check the type of our value before unmarshalling it

func (st *StringInt) UnmarshalJSON(b []byte) error {
  // convert the bytes into an interface
  // this will help us check the type of our value
  // if it is a string that can be converted into a int we convert it
  // otherwise we return an error
  var item interface{}
  if err := json.Unmarshal(b, &item); err != nil {
    return err
  }
  switch v := item.(type) {
  case int:
    *st = StringInt(v)
  case float64:
    *st = StringInt(int(v))
  case string:
    // here convert the string into
    // an integer
    i, err := strconv.Atoi(v)
    if err != nil {
      // the string might not be of integer type
      // so return an error
      return err
    }
    *st = StringInt(i)
  }
  return nil
}

type Item struct {
  Name   string    `json:"name"`
  ItemId StringInt `json:"item_id"`
}

func main() {
  fmt.Println("-----------------")
  printStruct([]byte(`{"name":"item 1","item_id":"30"}`))
  fmt.Println("-----------------")
  printStruct([]byte(`{"name":"item 2","item_id":40}`))
  fmt.Println("-----------------")

  var item Item
  jsonData := []byte(`{"name":"item 3","item_id":"50"}`)
  err := json.Unmarshal(jsonData, &item)
  if err != nil {
    log.Fatal(err)
  }
  printIndentedJSON(jsonData)
  fmt.Printf("%+v\n", item)
  itemId := int(item.ItemId)
  fmt.Printf("%+v\n", itemId)

  fmt.Println("-----------------")
  jsonData = []byte(`{"name":"item 4","item_id":60}`)
  err = json.Unmarshal(jsonData, &item)
  if err != nil {
    log.Fatal(err)
  }
  printIndentedJSON(jsonData)
  fmt.Printf("%+v\n", item)
  itemId = int(item.ItemId)
  fmt.Printf("%+v\n", itemId)

  fmt.Println("-----------------")

  /** OUTPUT
  -----------------
  {
    "item_id": "30",
    "name": "item 1"
  }
  {Name:item 1 ItemId:30}
  30
  -----------------
  {
    "item_id": 40,
    "name": "item 2"
  }
  {Name:item 2 ItemId:40}
  40
  -----------------
  {
    "item_id": "50",
    "name": "item 3"
  }
  {Name:item 3 ItemId:50}
  50
  -----------------
  {
    "item_id": 60,
    "name": "item 4"
  }
  {Name:item 4 ItemId:60}
  60
  -----------------
  */
}

func printStruct(jsonData []byte) Item {
  var item Item
  err := json.Unmarshal(jsonData, &item)
  if err != nil {
    log.Fatal(err)
  }
  printIndentedJSON(jsonData)
  fmt.Printf("%+v\n", item)
  itemId := int(item.ItemId)
  fmt.Printf("%+v\n", itemId)
  return item
}

func printIndentedJSON(jsonData []byte) {
  var obj map[string]interface{}
  json.Unmarshal(jsonData, &obj)
  str, _ := json.MarshalIndent(obj, "", "  ")
  fmt.Printf("%+v\n", string(str))
}