unmarshal json field is int or string [duplicate] - go

This question already has an answer here:
How to unmarshal a field that can be an array or a string in Go?
(1 answer)
Closed 3 years ago.
I have an app where the type is
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
But we have legacy clients that send the Age field as either a string or an integer, so...
{
"name": "Joe",
"age": "42"
}
OR
{
"name": "Joe",
"age": 42
}
I know I can annotate the "age" field with ",string" if it's a string that I want coerced into an integer, but what if the field could be either one?

Check out "json - raw Message" in the docs, from there on out you can try to parse it however you want. Example below and on GoPlayground
package main
import (
"encoding/json"
"fmt"
"log"
"strconv"
"unicode/utf8"
)
type Person struct {
Name string `json:"name"`
Age json.RawMessage `json:"age"`
}
func main() {
var j = []byte(`{"name": "Joe","age": "42"}`)
var j2 = []byte(`{"name": "Joe","age": 42}`)
stringOrInt(j)
stringOrInt(j2)
}
func stringOrInt(bytes []byte) {
var p Person
err := json.Unmarshal(bytes, &p)
if err != nil {
log.Fatal(err)
}
if utf8.Valid(p.Age) {
i, err := strconv.Atoi(string(p.Age))
if err != nil {
fmt.Println("got int " + strconv.Itoa(i))
} else {
fmt.Println("got string")
}
} else {
fmt.Println("whoops")
}
}

package main
import (
"encoding/json"
"fmt"
"strconv"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func (p *Person) UnmarshalJSON(b []byte) error {
var objMap map[string]*json.RawMessage
err := json.Unmarshal(b, &objMap)
if err != nil {
return err
}
var name string
err = json.Unmarshal(*objMap["name"], &name)
if err != nil {
return err
}
var ageInt int
err = json.Unmarshal(*objMap["age"], &ageInt)
if err != nil {
// age is string
var ageString string
err = json.Unmarshal(*objMap["age"], &ageString)
if err != nil {
return err
}
aI, err := strconv.Atoi(ageString)
if err != nil {
return err
}
p.Age = aI
} else {
p.Age = ageInt
}
p.Name = name
fmt.Printf("%+v", *p)
return nil
}
func main() {
p := `{"name": "John", "age": "10"}`
// p := `{"name": "John", "age": 10}`
newP := Person{}
err := newP.UnmarshalJSON([]byte(p))
if err != nil {
fmt.Printf("Error %+v", err)
}
}
https://play.golang.org/p/AK8H_wdNqmt
You can try something like this. Try to read and parse Into int, if that fails check for string value, parse it to int and then assign int value to person struct

Related

String to float64 receiving format ".01"

If I receive from an API a string obeying the format of ".01", and I have a struct like this:
type Mystruct struct {
Val float64 json:"val,string"
}
In this case, I receive trying to unmarshal val into float64. Is there a way I can accomplish this?
Add a string field to capture the string value:
type Mystruct struct {
Val float64 `json:"-"`
XVal string `json:"val"`
}
Unmarshal the JSON document. Convert the string value to a float value:
var v Mystruct
err := json.Unmarshal([]byte(data), &v)
if err != nil {
log.Fatal(err)
}
v.Val, err = strconv.ParseFloat(v.XVal, 64)
if err != nil {
log.Fatal(err)
}
I recommand defining a type alias which you can use it anywhere.
package main
import (
"encoding/json"
"fmt"
"strconv"
"strings"
)
type MyFloat64 float64
func (f *MyFloat64) UnmarshalJSON(data []byte) error {
raw := string(data)
raw = strings.TrimPrefix(raw, "\"")
raw = strings.TrimSuffix(raw, "\"")
if parsedFloat, err := strconv.ParseFloat(raw, 64); err != nil {
return err
} else {
*f = MyFloat64(parsedFloat)
return nil
}
}
type MyObj struct {
Val1 MyFloat64
Val2 string
}
func main() {
j := `{"Val1":"0.01", "Val2":"0.01"}`
o := MyObj{}
err := json.Unmarshal([]byte(j), &o)
if err != nil {
fmt.Println(err)
} else {
b, _ := json.Marshal(o)
fmt.Println("in:", j)
fmt.Println("out:", string(b))
}
}
output:
in: {"Val1":"0.01", "Val2":"0.01"}
out: {"Val1":0.01,"Val2":"0.01"}

How to receive a json to insert

I am receiving some values per post and I have a json type field but it arrives empty and if I enter a normal text it works and I do not see the error in the field
the model was updated so that it receives the fields and allows inserting in mysql
POSTman
{
"Code":"1234",//it works
"Desc":"desc",//it works
"Config":{"link":"https://stackoverflow.com/" }, //not works
"Dev":[ {"item":1},{"item":2}]//not works
}
type User struct {
gorm.Model
Code string `gorm:"type:varchar(100);unique_index"`
Desc string `gorm:"type:varchar(255);"`
Config JSON `json:"currencies" gorm:"type:varchar(255);"`
Dev JSON `json:"currencies" gorm:"type:varchar(255);"`
}
func CreateUser(c *gin.Context) {
var usuario models.User
var bodyBytes []byte
if c.Request.Body != nil {
bodyBytes, _ = ioutil.ReadAll(c.Request.Body)
}
data := bytes.NewBuffer(bodyBytes)
fmt.Println(data.Config)
c.BindJSON(&usuario)
db.DB.Create(&usuario)
c.JSON(200, usuario)
}
Model update. receive post form with json fields and insert in mysql
package models
import (
"bytes"
"database/sql/driver"
"errors"
)
type JSON []byte
func (j JSON) Value() (driver.Value, error) {
if j.IsNull() {
return nil, nil
}
return string(j), nil
}
func (j *JSON) Scan(value interface{}) error {
if value == nil {
*j = nil
return nil
}
s, ok := value.([]byte)
if !ok {
errors.New("error")
}
*j = append((*j)[0:0], s...)
return nil
}
func (m JSON) MarshalJSON() ([]byte, error) {
if m == nil {
return []byte("null"), nil
}
return m, nil
}
func (m *JSON) UnmarshalJSON(data []byte) error {
if m == nil {
return errors.New("error")
}
*m = append((*m)[0:0], data...)
return nil
}
func (j JSON) IsNull() bool {
return len(j) == 0 || string(j) == "null"
}
func (j JSON) Equals(j1 JSON) bool {
return bytes.Equal([]byte(j), []byte(j1))
}
Thank you very much to everyone who helped me, I consider that the functionality of receiving a json and saving it in mysql is very common and this can be useful to many people
You can change the JSON like below or You can change the Struct like below (I prefer struct approach)
{
"Code": "1234",
"Desc": "desc",
"Config": {
"Link": "https://stackoverflow.com/"
},
"Dev": [
{
"Item": 1
},
{
"Item": 2
}
]
}
Struct:
type User struct {
gorm.Model
Code string `json:"Code" gorm:"type:varchar(100);unique_index"`
Desc string `json:"Desc" gorm:"type:varchar(255);"`
Config []struct {
Link string `json:"link" gorm:"type:varchar(255);"`
Title string `json:"title" gorm:"type:varchar(255);"`
}
Dev []struct {
Item string `json:"item" gorm:"type:varchar(255);"`
}
}
You have made two kind of mistakes
Your json decoding cannot work because your struct does not match your json. Config is defined as a array of something but in your json you have an object not array, and in Dev the property Item is a int not a string
Your model may not be well defined as you have not defined you joined table. Well I never seen a working example with this kind of definition. I suggest you to declare your nested struct as independent struct.
Here a full working example :
package main
import (
"database/sql"
"encoding/json"
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
const data = `{
"Code":"1234",
"Desc":"desc",
"Config":{"link":"https://stackoverflow.com/" },
"Dev":[ {"item":1},{"item":2}]
}`
type Config struct {
Id int `gorm:"primaryKey"`
Link string `json:"link"`
Title string
UserId int
}
type Item struct {
Id int `gorm:"primaryKey"`
Item int `json:"item"`
UserId int
}
type User struct {
Id int `gorm:"primaryKey"`
Code string
Desc string
Config Config `gorm:"foreignkey:UserId"`
Dev []Item `gorm:"foreignkey:UserId"`
}
func initDb(url string) (*gorm.DB, *sql.DB, error) {
connexion := sqlite.Open(url)
db, err := gorm.Open(connexion, &gorm.Config{})
if err != nil {
return nil, nil, err
}
sql, err := db.DB()
if err != nil {
return nil, nil, err
}
err = db.AutoMigrate(&User{})
if err != nil {
return nil, nil, err
}
err = db.AutoMigrate(&Item{})
if err != nil {
return nil, nil, err
}
err = db.AutoMigrate(&Config{})
if err != nil {
return nil, nil, err
}
return db, sql, nil
}
func run() error {
db, sql, err := initDb("file::memory:?cache=shared")
if err != nil {
return err
}
defer sql.Close()
var user User
err = json.Unmarshal([]byte(data), &user)
fmt.Printf("%#v\n", user)
err = db.Create(&user).Error
if err != nil {
return err
}
var loaded User
db.Preload("Config").Preload("Dev").First(&loaded)
fmt.Printf("%#v\n", loaded)
return nil
}
func main() {
if err := run(); err != nil {
fmt.Println("failed", err)
}
}
try adding this JSON Field in your model
import (
"errors"
"database/sql/driver"
"encoding/json"
)
// JSON Interface for JSON Field of yourTableName Table
type JSON interface{}
// Value Marshal
func (a JSON) Value() (driver.Value, error) {
return json.Marshal(a)
}
// Scan Unmarshal
func (a *JSON) Scan(value interface{}) error {
b, ok := value.([]byte)
if !ok {
return errors.New("type assertion to byte failed")
}
return json.Unmarshal(b,&a)
}
All these answers didn't work for me, but this will work for everyone
Model
// This is the max Thing you need
import "gorm.io/datatypes"
import "encoding/json"
type CMSGenericModel struct {
gorm.Model
//... Other Posts
ExtraData datatypes.JSON `json:"data"`
}
In Handler Function
type CmsReqBody struct {
// ============= RAW ========
Data json.RawMessage `json:"data"`
// other props...
}
cmsBodyRecord := new(models.CMSGenericModel)
cmsBodyPayload := new(CmsReqBody)
if err := c.BodyParser(cmsBodyPayload); err != nil {
return c.Status(503).SendString(err.Error())
}
cmsBodyRecord.ExtraData = datatypes.JSON(cmsBodyPayload.Data)
My Sample Data
{
"title": "Blog Post 1",
"subtitle": "first",
"description": "Updated",
"type": "blog",
"isActive": true,
"uuid": "new",
"data": {
"complex1": ["kkkk", "yyyy"],
"complex2": [
{
"name": "sourav"
},
{
"name": "yahooo"
},
{
"yahoo": "name",
"kjk": ["abbsb", {"data": "abcd"}]
}
]
}
}

How to convert the dynamically produced array object data into JSON format string in golang?

On the data retrieval the data is in the form of array object like this:
[{1 fruits Apple Apple is my favorite fruit.} {2 colors Red Red color is always charming.} {3 flowers Lotus It is one of the most beautiful flowers in this world.}]
how will I change it in JSON. I just need to break the array object braces [].
I have tried to Marshal it. But It gives me like:
[{"id":1,"category":"fruits","name":"Apple","description":"Apple is my favorite fruit."},{"id":2,"category":"colors","name":"Red","description":"Red color is always charming."},{"id":3,"category":"flowers","name":"Lotus","description":"It is one of the most beautiful flowers in this world."}]
The code I'd tried
Struct
type Item struct {
Id int `json:"id"`
Category string `json:"category"`
Name string `json:"name"`
Description string `json:"description"`
}
type Items []Item
Here the function for retrieving data
func GetData(productQuery interface{}) (result Items, err error) {
mongoSession := ConnectDb()
sessionCopy := mongoSession.Copy()
defer sessionCopy.Close()
getCollection := mongoSession.DB("custom").C("custom")
err = getCollection.Find(productQuery).Select(bson.M{"password": 0}).All(&result) //.Skip(skip).Limit(limit)
if err != nil {
return result, err
}
return result, nil
}
/*
*
* Retrieve the data used by main function
*
*
*/
func retrieve(c *gin.Context) {
conditions := bson.M{}
data, err :=GetData(conditions)
if err != nil {
fmt.Println("There is somthing wrong")
}
fmt.Println("--------------------")
fmt.Println(data)
fmt.Println("--------------------")
arrange(data)
return
}
func arrange(data Items) {
pagesJson, err := json.Marshal(data)
if err != nil {
log.Fatal("Cannot encode to JSON ", err)
}
fmt.Println(string(pagesJson))
}
I want to make the output like
{"id": 1,"category": "fruits","name": "Apple","description": "Apple is my favorite fruit."} {"id": 2,"category": "colors","name": "Red",description": "Red color is always charming."} {"id": 3,"category": "flowers","name": "Lotus","description": "It is one of the most beautiful flowers in this world."}
can anyone help me I tried it lot of times but don't take any success.
Simply encode each element of your slice individually:
package main
import (
"encoding/json"
"log"
"os"
)
type T struct {
ID int `json:"id"`
Category string `json:"category"`
Name string `json:"name"`
Description string `json:"description"`
}
func main() {
var data []T
enc := json.NewEncoder(os.Stdout)
for _, x := range data {
if err := enc.Encode(x); err != nil {
log.Fatal(err)
}
}
}
This code will works
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"strings"
)
type Item struct {
Id int `json:"id"`
Category string `json:"category"`
Name string `json:"name"`
Description string `json:"description"`
}
type Items []Item
var myJson = []byte(`[{
"id":1,
"category":"fruits",
"name":"Apple",
"description":"Apple is my favorite fruit."
},
{
"id":2,
"category":"colors",
"name":"Red",
"description":"Red color is always charming."
},
{
"id":3,
"category":"flowers",
"name":"Lotus",
"description":"It is one of the most beautiful flowers in this world."
}]`)
func main() {
var items Items
err := json.Unmarshal(myJson, &items)
if err != nil {
log.Fatal(err)
}
s, err := getMyString(items)
if err != nil {
log.Fatal(err)
}
fmt.Println(s)
}
func getMyString(items Items) (string, error) {
var buffer bytes.Buffer
var err error
var b []byte
for _, item := range items {
b, err = json.Marshal(item)
if err != nil {
return "", err
}
buffer.WriteString(string(b) + " ")
}
s := strings.TrimSpace(buffer.String())
return s, nil
}

Is there a reason why bb.person gets filled but a.Person does not?

package main
import (
"encoding/json"
"fmt"
)
type Person struct {
First string `json:"name"`
}
type person struct {
Last string `json:"name"`
}
type A struct {
*Person `json:"person"`
}
func (a *A) UnmarshalJSON(b []byte) error {
type alias A
bb := struct {
*person `json:"person"`
*alias
}{
alias: (*alias)(a),
}
if err := json.Unmarshal(b, &bb); err != nil {
return err
}
fmt.Printf("%+v\n", bb.person)
return nil
}
func main() {
b := []byte(`{"person": {"name": "bob"}}`)
a := &A{}
if err := json.Unmarshal(b, a); err != nil {
fmt.Println(err)
}
fmt.Printf("%+v\n", a.Person)
}
results in:
&{Last:bob}
<nil>
Why does bb.person and a.Person have the same struct tag but only bb.person gets filled in? I haven't been able to find the appropriate documentation but why does this happen and is it guaranteed to always happen?
https://play.golang.org/p/Fvo_hg3U6r

Converting map to struct

I am trying to create a generic method in Go that will fill a struct using data from a map[string]interface{}. For example, the method signature and usage might look like:
func FillStruct(data map[string]interface{}, result interface{}) {
...
}
type MyStruct struct {
Name string
Age int64
}
myData := make(map[string]interface{})
myData["Name"] = "Tony"
myData["Age"] = 23
result := &MyStruct{}
FillStruct(myData, result)
// result now has Name set to "Tony" and Age set to 23
I know this can be done using JSON as an intermediary; is there another more efficient way of doing this?
The simplest way would be to use https://github.com/mitchellh/mapstructure
import "github.com/mitchellh/mapstructure"
mapstructure.Decode(myData, &result)
If you want to do it yourself, you could do something like this:
http://play.golang.org/p/tN8mxT_V9h
func SetField(obj interface{}, name string, value interface{}) error {
structValue := reflect.ValueOf(obj).Elem()
structFieldValue := structValue.FieldByName(name)
if !structFieldValue.IsValid() {
return fmt.Errorf("No such field: %s in obj", name)
}
if !structFieldValue.CanSet() {
return fmt.Errorf("Cannot set %s field value", name)
}
structFieldType := structFieldValue.Type()
val := reflect.ValueOf(value)
if structFieldType != val.Type() {
return errors.New("Provided value type didn't match obj field type")
}
structFieldValue.Set(val)
return nil
}
type MyStruct struct {
Name string
Age int64
}
func (s *MyStruct) FillStruct(m map[string]interface{}) error {
for k, v := range m {
err := SetField(s, k, v)
if err != nil {
return err
}
}
return nil
}
func main() {
myData := make(map[string]interface{})
myData["Name"] = "Tony"
myData["Age"] = int64(23)
result := &MyStruct{}
err := result.FillStruct(myData)
if err != nil {
fmt.Println(err)
}
fmt.Println(result)
}
Hashicorp's https://github.com/mitchellh/mapstructure library does this out of the box:
import "github.com/mitchellh/mapstructure"
mapstructure.Decode(myData, &result)
The second result parameter has to be an address of the struct.
the simplest way to do that is using encoding/json package
just for example:
package main
import (
"fmt"
"encoding/json"
)
type MyAddress struct {
House string
School string
}
type Student struct {
Id int64
Name string
Scores float32
Address MyAddress
Labels []string
}
func Test() {
dict := make(map[string]interface{})
dict["id"] = 201902181425 // int
dict["name"] = "jackytse" // string
dict["scores"] = 123.456 // float
dict["address"] = map[string]string{"house":"my house", "school":"my school"} // map
dict["labels"] = []string{"aries", "warmhearted", "frank"} // slice
jsonbody, err := json.Marshal(dict)
if err != nil {
// do error check
fmt.Println(err)
return
}
student := Student{}
if err := json.Unmarshal(jsonbody, &student); err != nil {
// do error check
fmt.Println(err)
return
}
fmt.Printf("%#v\n", student)
}
func main() {
Test()
}
You can do it ... it may get a bit ugly and you'll be faced with some trial and error in terms of mapping types .. but heres the basic gist of it:
func FillStruct(data map[string]interface{}, result interface{}) {
t := reflect.ValueOf(result).Elem()
for k, v := range data {
val := t.FieldByName(k)
val.Set(reflect.ValueOf(v))
}
}
Working sample: http://play.golang.org/p/PYHz63sbvL
There are two steps:
Convert interface to JSON Byte
Convert JSON Byte to struct
Below is an example:
dbByte, _ := json.Marshal(dbContent)
_ = json.Unmarshal(dbByte, &MyStruct)
You can roundtrip it through JSON:
package main
import (
"bytes"
"encoding/json"
)
func transcode(in, out interface{}) {
buf := new(bytes.Buffer)
json.NewEncoder(buf).Encode(in)
json.NewDecoder(buf).Decode(out)
}
Example:
package main
import "fmt"
type myStruct struct {
Name string
Age int64
}
func main() {
myData := map[string]interface{}{
"Name": "Tony",
"Age": 23,
}
var result myStruct
transcode(myData, &result)
fmt.Printf("%+v\n", result) // {Name:Tony Age:23}
}
I adapt dave's answer, and add a recursive feature. I'm still working on a more user friendly version. For example, a number string in the map should be able to be converted to int in the struct.
package main
import (
"fmt"
"reflect"
)
func SetField(obj interface{}, name string, value interface{}) error {
structValue := reflect.ValueOf(obj).Elem()
fieldVal := structValue.FieldByName(name)
if !fieldVal.IsValid() {
return fmt.Errorf("No such field: %s in obj", name)
}
if !fieldVal.CanSet() {
return fmt.Errorf("Cannot set %s field value", name)
}
val := reflect.ValueOf(value)
if fieldVal.Type() != val.Type() {
if m,ok := value.(map[string]interface{}); ok {
// if field value is struct
if fieldVal.Kind() == reflect.Struct {
return FillStruct(m, fieldVal.Addr().Interface())
}
// if field value is a pointer to struct
if fieldVal.Kind()==reflect.Ptr && fieldVal.Type().Elem().Kind() == reflect.Struct {
if fieldVal.IsNil() {
fieldVal.Set(reflect.New(fieldVal.Type().Elem()))
}
// fmt.Printf("recursive: %v %v\n", m,fieldVal.Interface())
return FillStruct(m, fieldVal.Interface())
}
}
return fmt.Errorf("Provided value type didn't match obj field type")
}
fieldVal.Set(val)
return nil
}
func FillStruct(m map[string]interface{}, s interface{}) error {
for k, v := range m {
err := SetField(s, k, v)
if err != nil {
return err
}
}
return nil
}
type OtherStruct struct {
Name string
Age int64
}
type MyStruct struct {
Name string
Age int64
OtherStruct *OtherStruct
}
func main() {
myData := make(map[string]interface{})
myData["Name"] = "Tony"
myData["Age"] = int64(23)
OtherStruct := make(map[string]interface{})
myData["OtherStruct"] = OtherStruct
OtherStruct["Name"] = "roxma"
OtherStruct["Age"] = int64(23)
result := &MyStruct{}
err := FillStruct(myData,result)
fmt.Println(err)
fmt.Printf("%v %v\n",result,result.OtherStruct)
}
Here function to convert map to struct by tag. If tag not exist it will find by fieldByName.
Thanks to https://gist.github.com/lelandbatey/a5c957b537bed39d1d6fb202c3b8de06
type MyStruct struct {
Name string `json:"name"`
ID int `json:"id"`
}
myStruct := &MyStruct{}
for k, v := range mapToConvert {
err := MapToStruct(myStruct, k, v)
if err != nil {
fmt.Println(err)
}
}
func MapToStruct(s interface{}, k string, v interface{}) error {
var jname string
structValue := reflect.ValueOf(s).Elem()
fieldByTagName := func(t reflect.StructTag) (string, error) {
if jt, ok := t.Lookup("keyname"); ok {
return strings.Split(jt, ",")[0], nil
}
return "", fmt.Errorf("tag provided %s does not define a json tag", k)
}
fieldNames := map[string]int{}
for i := 0; i < structValue.NumField(); i++ {
typeField := structValue.Type().Field(i)
tag := typeField.Tag
if string(tag) == "" {
jname = toMapCase(typeField.Name)
} else {
jname, _ = fieldByTagName(tag)
}
fieldNames[jname] = i
}
fieldNum, ok := fieldNames[k]
if !ok {
return fmt.Errorf("field %s does not exist within the provided item", k)
}
fieldVal := structValue.Field(fieldNum)
fieldVal.Set(reflect.ValueOf(v))
return nil
}
func toMapCase(s string) (str string) {
runes := []rune(s)
for j := 0; j < len(runes); j++ {
if unicode.IsUpper(runes[j]) == true {
if j == 0 {
str += strings.ToLower(string(runes[j]))
} else {
str += "_" + strings.ToLower(string(runes[j]))
}
} else {
str += strings.ToLower(string(runes[j]))
}
}
return str
}
Simple way just marshal it json string
and then unmarshat it to struct
here is the link

Resources