How to receive a json to insert - go

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"}]
}
]
}
}

Related

Mapping string to UUID in 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

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

MongoDB is not marshaling a value before storing it

I'm using a custom JSON marshaller/unmarshaller for a mapping between integers and strings in Go. The problem is that values are being stored in the database as integers instead of strings. In the example below, I would expect this to be stored in the MongoDB database:
{ "_id" : "id123", "desc" : "Red Delicious", "value" : "apple" }
Instead I get:
{ "_id" : "id123", "desc" : "Red Delicious", "value" : 1 }
As the test shows, marshalling and unmarshalling are working fine. What's going on?
Here's an example as a Go test (save to unmarshal_test.go and "go test").
package testunmarshal
import (
"fmt"
"testing"
"encoding/json"
mgo "gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
type Const int
const (
Apple Const = 1
Banana = 2
Cherry = 4
)
type Record struct {
Id string `bson:"_id" json:"id"`
Desc string `bson:"desc" json:"desc"`
Value Const `bson:"value" json:"value`
}
func (intValue Const) Code() string {
switch intValue {
case Apple: return "apple"
case Banana: return "banana"
case Cherry: return "cherry"
}
return "invalid"
}
func (intValue *Const) UnmarshalJSON(data []byte) (err error) {
switch string(data) {
case `"apple"`:
*intValue = Apple
case `"banana"`:
*intValue = Banana
case `"cherry"`:
*intValue = Cherry
default:
return fmt.Errorf("Invalid fruit %s", data)
}
return nil
}
func (intValue *Const) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%s"`, intValue.Code())), nil
}
func TestMarshalJSON(t *testing.T) {
var orig = Record {
Id: "id456",
Desc: "Cavendish",
Value: Banana,
}
var copy Record
bytes, err := json.Marshal(&orig)
if err != nil {
t.Errorf("Marshal failed: %s", err.Error())
return
}
err = json.Unmarshal(bytes, &copy)
if err != nil {
t.Errorf("Unmarshal failed: %s", err.Error())
return
}
if orig.Value != copy.Value {
t.Errorf("Expected %d=%s, got %d=%s", orig.Value, orig.Value.Code(), copy.Value, copy.Value.Code())
}
}
func TestMarshalBSON(t *testing.T) {
var orig = Record {
Id: "id456",
Desc: "Cavendish",
Value: Banana,
}
var copy Record
bytes, err := bson.Marshal(&orig)
if err != nil {
t.Errorf("Marshal failed: %s", err.Error())
return
}
err = bson.Unmarshal(bytes, &copy)
if err != nil {
t.Errorf("Unmarshal failed: %s", err.Error())
return
}
if orig.Value != copy.Value {
t.Errorf("Expected %d=%s, got %d=%s", orig.Value, orig.Value.Code(), copy.Value, copy.Value.Code())
}
}
func TestMongo(t *testing.T) {
var rec1 = Record {
Id: "id123",
Desc: "Red Delicious",
Value: Apple,
}
var rec2 Record
sess, err := mgo.Dial("localhost")
if err != nil {
t.Errorf(err.Error())
return
}
db := sess.DB("test")
if db == nil {
t.Fatal("Failed to connect to database")
return
}
col := db.C("fruit")
if col == nil {
t.Fatal("Failed to open collection")
return
}
// defer col.DropCollection()
err = col.Insert(&rec1)
if err != nil {
t.Fatal("Failed to insert: %s", err.Error())
return
}
err = col.Find(bson.M{}).One(&rec2)
if err != nil {
t.Fatal("Failed to retrieve stored object: %s", err.Error())
return
}
if rec1.Value != rec2.Value {
t.Errorf("Expected %d=%s, got %d=%s", rec1.Value, rec1.Value.Code(), rec1.Value, rec2.Value.Code())
}
}
Edit: Added more tests to demonstrate that marshalling and unmarshalling are working.
The bson encoder does not use the JSON marshaling interfaces. Implement the Getter interface:
func (intValue Const) GetBSON() (interface{}, error) {
return intValue.Code(), nil
}
You will also want to implement the Setter interface.
func (intValue *Const) SetBSON(raw bson.Raw) error {
var data int
if err := raw.Unmarshal(&data); err != nil {
return err
}
switch data {
case `"apple"`:
*intValue = Apple
case `"banana"`:
*intValue = Banana
case `"cherry"`:
*intValue = Cherry
default:
return fmt.Errorf("Invalid fruit %s", data)
}
return nil
}

Json data adapter

I want to take JSON in
{ name: "http://example.com", ...}
and map it to
{ url: "http://example.com", ...}
I want to do this mapping and be able to Marshal back to json as Url. The struct is rather large, so is it possible to do this with one struct instead creating multiple struct types?
This is the solution I have come to so far: http://play.golang.org/p/wBPbSjkTYF
After seeing answers and how I failed to accurately describe my use case. Updating to the actual mapping I'm trying to do:
{ "data": { "name": "http://example.com", "key": "value" } }
To:
{ "url": "http://example.com", { "data": "key": "value" } }
The json package doesn't have any tags that allows you to use one key name of a field on Marshal and a different on Unmarshal.
It is still possible to Marshal a struct to differently, but in order to do that you will have to implement your own Marshaller (MarshalJSON function). A working example:
package main
import (
"bytes"
"encoding/json"
"fmt"
)
type Data struct {
Url string `json:"name"`
}
// marshalObject takes a slice of key values
// (should be correctly escaped json strings without "")
// and another slice of interface{} values and marshals
// them into a JSON object string
func marshalObject(keys []string, values []interface{}) ([]byte, error) {
if len(keys) != len(values) {
panic("Different length of keys and values slices")
}
if len(keys) == 0 {
return []byte(`{}`), nil
}
var b bytes.Buffer
b.Write([]byte(`{"`))
for i, key := range keys {
if i != 0 {
b.Write([]byte(`,"`))
}
b.WriteString(key)
b.Write([]byte(`":`))
j, err := json.Marshal(values[i])
if err != nil {
return nil, err
}
b.Write(j)
}
b.Write([]byte(`}`))
return b.Bytes(), nil
}
func (d *Data) MarshalJSON() ([]byte, error) {
// Here you can add a list of keys and values.
// Currently it is only `url` mapped to d.Url
return marshalObject(
[]string{
`url`,
},
[]interface{}{
d.Url,
},
)
}
func main() {
i := []byte(`{"name":"http://example.com"}`)
fmt.Printf("Json Input: %+s\n", i)
var d *Data
err := json.Unmarshal(i, &d)
if err != nil {
panic(err)
}
fmt.Printf("Data: %#v\n", d)
o, err := json.Marshal(d)
if err != nil {
panic(err)
}
fmt.Printf("Json Output: %+s\n", o)
}
Result:
Json Input: {"name":"http://example.com"}
Data: &main.Data{Url:"http://example.com"}
Json Output: {"url":"http://example.com"}
Playground:
http://play.golang.org/p/u6ExI9V95D
If you unmarshal a JSON object into an map[string]interface{} variable, you will be given everything in the object rather than just a few fields. It should be fairly straight forward to make the required modifications, like so:
func nameToUrl(data []byte) ([]byte, error) {
var obj map[string]interface{}
if err := json.Unmarshal(data, &obj); err != nil {
return nil, err
}
if name, ok := obj["name"]; ok {
delete(obj, "name")
obj["url"] = name
}
return json.Marshal(obj)
}
You can experiment with this here: http://play.golang.org/p/0jz_HAGg3E

Resources