Mapping string to UUID in go - go

I'm using mitchellh/mapstructure to map from map[string]interface{} to struct
Is there any way to tell mapstructure to convert string to uuid.UUID?
map[string]interface{}:
{
"id": "af7926b1-98eb-4c96-a2ba-7e429085b2ad",
"title": "new title",
}
struct
package entities
import (
"github.com/google/uuid"
)
type Post struct {
Id uuid.UUID `json:"id"`
Title string `json:"title"`
}

You could add a DecodeHookFunc:
func decode(input, output interface{}) error {
config := &mapstructure.DecoderConfig{
DecodeHook: mapstructure.ComposeDecodeHookFunc(
stringToUUIDHookFunc(),
),
Result: &output,
}
decoder, err := mapstructure.NewDecoder(config)
if err != nil {
return err
}
return decoder.Decode(input)
}
func stringToUUIDHookFunc() mapstructure.DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(uuid.UUID{}) {
return data, nil
}
return uuid.Parse(data.(string))
}
}
Source: https://github.com/mitchellh/mapstructure/issues/236
Documentation:
https://pkg.go.dev/github.com/mitchellh/mapstructure#DecodeHookFunc

Related

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 can I reduce repetitive code associated with unmarshalling dynamic types from JSON and sending to a related channel?

Consider the example below, it accepts a JSON message which is eventually unmarshalled into several possible types. How can I reduce or remove the boilerplate code associated with adding another event type.
package main
import (
"encoding/json"
"fmt"
)
const input = `
{
"type": "hello",
"event": {
"name": "Picard"
}
}
`
type EventEnvelope struct {
Type string
Event interface{}
}
type EventHello struct {
Name string
}
type EventHowdy struct {
Name string
}
type Display struct {
formal chan EventHello
western chan EventHowdy
}
func newDisplay() *Display {
return &Display{
formal: make(chan EventHello),
western: make(chan EventHowdy),
}
}
func (display *Display) run() {
for {
select {
case formal := <-display.formal:
fmt.Println("Hello", formal.Name)
case western := <-display.western:
fmt.Println("Howdy", western.Name)
}
}
}
func main() {
var event json.RawMessage
env := EventEnvelope{
Event: &event,
}
if err := json.Unmarshal([]byte(input), &env); err != nil {
fmt.Print(err)
}
display := newDisplay()
go display.run()
events(display, event, env.Type)
}
func events(display *Display, raw json.RawMessage, event string) {
switch event {
case "hello":
hello := EventHello{}
if err := json.Unmarshal(raw, &hello); err != nil {
fmt.Println(err)
} else {
display.formal <- hello
}
case "howdy":
howdy := EventHowdy{}
if err := json.Unmarshal(raw, &howdy); err != nil {
fmt.Println(err)
} else {
display.western <- howdy
}
default:
fmt.Println("No event handler")
}
}
After unmarshalling the EventEnvelope, the actual event is left as a RawMessage.
The event RawMessage is then unmarshalled into a specific type. Can this be dynamic?
Only if there were no errors should we send it to the channel.
hello := EventHello{}
if err := json.Unmarshal(raw, &hello); err != nil {
fmt.Println(err)
} else {
display.formal <- hello
}
Same code in the playground: https://play.golang.org/p/WPC8JAFyxgq
You can use a type factory:
var types map[string]func() interface{} {"hello":func() interface{} {return &EventHello{}},
"howdy": func() interface{} {return &EventHowdy{}}}
The factory can be used to create a new instance of a type using its name. You can add new functions to those types so they can select their own channels:
type sender interface {
send(Display)
}
func (e EventHello) send(d Display) {
d.formal<-e
}
Then events function can be written to deal with all possible event types:
func events(display *Display, raw json.RawMessage, event string) {
evInstance:=types[event]()
if err := json.Unmarshal(raw, evInstance); err != nil {
fmt.Println(err)
} else {
evInstance.(sender).send(display)
}
}

unmarshal json field is int or string [duplicate]

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

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