Home > Net >  Json unmarshal: force into string
Json unmarshal: force into string

Time:11-15

I have the following Go struct into which I want to unmarshal some Json data. It works perfectly except for the Values map, which is of type map[string]string.

type Data struct {
    Id          int               `jons:"id"`
    Values      map[string]string `json:"values"`

My Json data (which I can't change the format of), has the following structure and sample data:

{
   id: 1,
   values: {
      key1: "a string value",
      key2: 7
   }
}

Unmarshalling the json data fails because Go can't unmarshal the value 7 into a string.

json: cannot unmarshal number into Go struct field Data.Values of type string

Is there a way to implicitly convert the Json values into a string, regardless of the perceived type? Changing the Json data to format the value as a string, ie key2: "7" is not an option.

CodePudding user response:

Since you can have an integer or a string in the json, it would be better if you use an interface.

Something like this:

type Data struct {
    Id          int                       `jons:"id"`
    Values      map[string]interface{}    `json:"values"`
}

This should do the trick.

Sample code for reference: https://play.golang.org/p/PjxWeLTwsCC

CodePudding user response:

You can create own string type and implement UnmarshalJSON function to it.

type MadSrting string

type Data struct {
    Id          int                  `jons:"id"`
    Values      map[string]MadSrting `json:"values"`
}

func (mad *MadSrting) UnmarshalJSON(data []byte) error {
    if n := len(data); n > 1 && data[0] == '"' && data[n-1] == '"' {
        return json.Unmarshal(data, (*string)(mad))
    }   
    
    *mad = MadSrting(data)

    return nil
}

Sample: https://play.golang.org/p/PsJRsvQJPMZ

CodePudding user response:

You can use interface and type assertions

package main

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

func main() {
    type Data struct {
        Id          int               `jons:"id"`
        Values      map[string]interface{} `json:"values"`
    }

    jsonData := []byte(`{"id": 1, "values": {"key1": "a string value", "key2": 7}}`)

    data := new(Data)
    err := json.Unmarshal(jsonData, &data)
    if err != nil {
        fmt.Println(err)
    }

    for _, value := range data.Values {
        fmt.Printf("%T\n", ToString(value))
    }
}

func ToString(value interface{}) string {
    str := ""

    switch value.(type) {
    case float64:
        str = strconv.FormatFloat(value.(float64), 'f', 0, 64)
    case int64:
        str = strconv.FormatInt(value.(int64), 10)
    case int:
        str = strconv.Itoa(value.(int))
    case string:
        str = value.(string)
    }

    return str
}

https://play.golang.org/p/r9_6IKRgPst

CodePudding user response:

You can use the empty interface on your maps values. An then convert it to a string with Sprintf. Here is an example of how you could unmarshal your data:

package main

import (
    "encoding/json"
    "fmt"
)

type Data struct {
    Id     int                    `jons:"id"`
    Values map[string]interface{} `json:"values"`
}

func main() {
    data := `
{
    "id": 1,
    "values": {
        "key1": "a string value",
        "key2": 7
    }
}
`
    var d Data
    err := json.Unmarshal([]byte(data), &d)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(d)

    s1 := fmt.Sprintf("%v", d.Values["key1"])
    fmt.Println("key1", s1)

    s2 := fmt.Sprintf("%v",  d.Values["key2"])
    fmt.Println("key2", s2)
}
  • Related