I'm creating a Golang API, but hit a blocker. For every POST, this is what I get:
"error": "sql: converting argument $2 type: unsupported type main.Data, a struct"
I'd like my data to be of format
"name": "test",
"other": {
"age": "",
"height": ""
}
}
How can I achieve this? See the code below, what I've tried so far.
model.go
type Data struct {
ID int `json:"id"`
Name string `json:"name,omitempty"`
Other *Other `json:"other,omitempty"`
}
type Other struct {
Age string `json:"host,omitempty"`
Height string `json:"database,omitempty"`
}
func (d *Data) Create(db *sql.DB) error {
err := db.QueryRow(
"INSERT INTO configs(name, other) VALUES($1, $2) RETURNING id",
d.Name, &d.Other).Scan(&d.ID)
if err != nil {
return err
}
return nil
}
controller.go
...
func (a *App) createData(w http.ResponseWriter, r *http.Request) {
var d Data
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&d); err != nil {
fmt.Print(err)
fatalError(w, http.StatusBadRequest, "Invalid request")
return
}
defer r.Body.Close()
if err := d.Create(a.DB); err != nil {
fatalError(w, http.StatusInternalServerError, err.Error())
return
}
jsonResponse(w, http.StatusCreated, d)
}
I expected the db to be populated with the data of format
"name": "test",
"other": {
"age": "",
"height": ""
}
}
But fails with error:
"error": "sql: converting argument $2 type: unsupported type main.Data, a struct"
You can make this modification:
import "encoding/json"
func (d *Data) Create(db *sql.DB) error {
var jsonOther string
if d.Other != nil {
jsonOther, _ = json.Marshal(d.Other)
err := db.QueryRow(
"INSERT INTO configs(name, other) VALUES($1, $2) RETURNING id",
d.Name, jsonOther).Scan(&d.ID)
if err != nil {
return err
}
return nil
}
Notice that the Other object is explicitly serialized to JSON and passed as a string
Related
For example, I have this structure:
type Overview struct {
Symbol string `json:"Symbol,omitempty"`
AssetType string `json:"AssetType,omitempty"`
Name string `json:"Name,omitempty"`
Description string `json:"Description,omitempty"`
...
...
}
In addition to this, I have several other structures.
My function selects a suitable structure for Decode(), but when I try to get data from the database, I get the result in this form:
[
{
"Key": "_id",
"Value": "618aa6f2a64cb8105a9c7984"
},
{
"Key": "Symbol",
"Value": "IBM"
},
{
"Key": "FiscalYearEnd",
"Value": "December"
},
...
...
]
I expect a response in the form of my structure, but I get such an array. I tried declaring the structure for the response myself: var result models.Overview. After that, the problem disappeared, but this is not the solution for my problem
There is my function:
var (
models map[string]interface{}
)
func init() {
models = make(map[string]interface{})
models["Overview"] = models.Overview{}
models["Earnings"] = models.Earnings{}
...
...
}
func GetDbData(collection string, db *mongo.Database, filter bson.D) (interface{}, error) {
var result = models[collection] // Choosing a structure
res := db.Collection(collection).FindOne(context.TODO(), filter)
err := res.Decode(&result)
if err != nil {
return nil, err
}
return result, nil
}
I can't understand why this is happening, I hope that someone has already encountered this problem and will be able to help me
https://jira.mongodb.org/browse/GODRIVER-988
Another approach to solve this can be by first decoding it into bson.M type and then unmarshalling it to your struct. Yes, this is not optimal.
eg:
func GetMonthStatusByID(ctx context.Context, id string) (interface{}, error) {
var monthStatus interface{}
filter := bson.M\{"_id": id}
err := db.Collection("Months").FindOne(ctx, filter).Decode(&monthStatus)
return monthStatus, err
}
The above snippet should be changed to:
func GetMonthStatusByID(ctx context.Context, id string) (interface{}, error) {
var monthStatus interface{}
filter := bson.M\{"_id": id}
tempResult := bson.M{}
err := db.Collection("Months").FindOne(ctx, filter).Decode(&tempResult)
if err == nil {
obj, _ := json.Marshal(tempResult)
err= json.Unmarshal(obj, &monthStatus)
}
return monthStatus, err
}
I am trying to query using bison all JSON data in MongoDB with two fields but am getting null as result.
{
"allowedList": [
{
"List": [
{
"allow": {
"ss": 1,
},
"Information": [
{
"Id": "Id1"
}
]
}
]
}
]
}
I was able to filter all using the MongoDB at command line using
db.slicedb.find({"allowedList.List.allow.ss":1,"allowedList.List.Information.nsiId":"Id-Id21"})
but using
query := bson.M{"allowedList.List.allow": bson.M{"ss": sst}, "allowedList.List.Information": bson.M{"Id": Id}}
sst and Id are integer and string input to the query function
err := db.C(COLLECTION).Find(query).All(&specificSlices)
but is not working, am getting null even though there are json data that match the two field. Can someone help point out what was wrong with my query?
Server and database config
type SliceDataAccess struct {
Server string
Database string
}
var db *mgo.Database
const (
COLLECTION = "slicedb"
)
Establish a connection to database
func (m *SliceDataAccess) Connect() {
session, err := mgo.DialWithTimeout(m.Server, 20*time.Second)
if err != nil {
log.Fatal(err)
}
db = session.DB(m.Database)
}
Structs fields
type InstanceInfo struct {
ID string `json:"nfId" bson:"_id"`
AllowedList []AllowedNssai `json:"allowedList" bson:"allowedList"`
}
type AllowedNssai struct {
List []AllowedSnssai `json:"List,omitempty" bson:"List"`
...
}
type AllowedSnssai struct {
Allow *Snssai `json:"allow,omitempty" bson:"allow"`
Information []NsiInformation `json:"Information,omitempty" bson:"Information"`
}
type NsiInformation struct {
Id string `json:"Id" bson:"Id"`
}
type Snssai struct {
Ss int32 `json:"sst" bson:"ss"`
}
Query function defined
func (m *SliceDataAccess) FindAll(sst int32, nsiId string ([]InstanceInfo, error) {
var specificSlices []InstanceInfo
query := bson.M{"allowedList.List.allow": bson.M{"ss": sst}, "allowedList.List.Information": bson.M{"Id": nsiId}}
err := db.C(COLLECTION).Find(query).All(&specificSlices)
if err != nil {
return specificSlices, err
}
return specificSlices, nil
}
HTTP handler function for request and response
func AvailabilityGet(w http.ResponseWriter, r *http.Request)
var slice InstanceInfo
err := json.NewDecoder(r.Body).Decode(&slice)
if err != nil {
respondWithError(w, http.StatusBadRequest, "Object body not well decoded")
return
}
sst := slice.AllowedList[0].List[0].Allow.Sst
nsiId := slice.AllowedList[0].List[0].Information[0].Id
specificSlices, err := da.FindAll(sst, nsiId)
json.NewEncoder(w).Encode(specificSlices)
}
Attached is my the full go code i have done.
this worked
query := bson.M{"allowedNssaiList.allowedSnssaiList.allowedSnssai.sst": sst, "allowedNssaiList.allowedSnssaiList.nsiInformationList.nsiId": nsiId}
After a POST request, i was expecting to have a last inserted record marshalled into json, but instead returns an empty body. What am i not doing well?
package models
import (
"encoding/json"
"errors"
"flag"
"fmt"
"log"
"net/http"
"strconv"
"github.com/go-chi/chi"
"github.com/google/jsonapi"
"github.com/thedevsaddam/renderer"
"github.com/xo/dburl"
)
var rnd = renderer.New()
var flagVerbose = flag.Bool("v", false, "verbose")
var FlagURL = flag.String("url", "postgres://postgres:#127.0.0.1/sweb", "url")
// Page represents a row from 'public.pages'.
type Page struct {
Tag string `jsonapi:"attr,tag"` // tag
Body string `jsonapi:"attr,body"` // body
Slug string `jsonapi:"attr,slug"` // slug
Title string `jsonapi:"attr,title"` // title
ID int `jsonapi:"primary,pages"` // id
Link string `jsonapi:"attr,link"` // link
// xo fields
_exists, _deleted bool
}
func (page Page) JSONAPILinks() *jsonapi.Links {
return &jsonapi.Links{
"self": fmt.Sprintf("https://%d", page.ID),
}
}
I believe this is the culprit. After inserting a record, it should return the last inserted record as specified.
func (p *Page) PInsert(db XODB) (*Page, error) {
var err error
// if already exist, bail
if p._exists {
return p, errors.New("insert failed: already exists")
}
// sql insert query, primary key provided by sequence
const sqlstr = `INSERT INTO public.pages (` +
`tag, body, slug, title` +
`) VALUES (` +
`$1, $2, $3, $4` +
`) RETURNING id, tag, body, title`
// run query
XOLog(sqlstr, p.Tag, p.Body, p.Slug, p.Title)
err = db.QueryRow(sqlstr, p.Tag, p.Body, p.Slug, p.Title).Scan(&p.ID, &p.Tag, &p.Body, &p.Title)
if err != nil {
return p, err
}
// set existence
p._exists = true
return p, nil
}
Update updates the Page in the database and return last inserted records.
The same should apply for the Update function
func (p *Page) Update(db XODB) (*Page, error) {
var err error
// if doesn't exist, bail
if !p._exists {
return p, errors.New("update failed: does not exist")
}
// if deleted, bail
if p._deleted {
return p, errors.New("update failed: marked for deletion")
}
// sql query
const sqlstr = `UPDATE public.pages SET (` +
`tag, body, slug, title` +
`) = ( ` +
`$1, $2, $3, $4` +
`) WHERE id = $5`
// run query
XOLog(sqlstr, p.Tag, p.Body, p.Slug, p.Title, p.ID)
_, err = db.Exec(sqlstr, p.Tag, p.Body, p.Slug, p.Title, p.ID)
return p, err
}
func (p *Page) PSave(db XODB) (*Page, error) {
if p.Exists() {
return p.Update(db)
}
return p.PInsert(db)
}
func NewPage(w http.ResponseWriter, r *http.Request) {
db, err := dburl.Open(*FlagURL)
defer db.Close()
if err != nil {
log.Fatal(err)
}
var page Page
//page := new(Page)
if err := jsonapi.UnmarshalPayload(r.Body, &page); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
p, err := page.PSave(db)
if err != nil {
fmt.Println(err)
if err := jsonapi.MarshalPayload(w, p); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
fmt.Println(err)
}
}
w.Header().Set("Content-Type", jsonapi.MediaType)
w.WriteHeader(http.StatusCreated)
}
This is the last function i believe the issue is happening from. the last inserted record supposed to be marshalled into json.
Your last section of code contains a number of mistakes. The relevant section (without the useless and obfuscating Printlns) is:
p, err := page.PSave(db)
if err != nil {
if err := jsonapi.MarshalPayload(w, p); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
w.Header().Set("Content-Type", jsonapi.MediaType)
w.WriteHeader(http.StatusCreated)
And the primary mistake is that json.MarshalPayload is only called when err != nil. In other words, you only serialize the page if you failed to save it.
The secondary mistake is that jsonapi.MarshalPayload will call Write on the http.ResponseWriter. This turns all subsequent calls to Header().Set and WriteHeader into no-ops.
More correct code would look like this.
// 1. Save the page in the database, bail on error
p, err := page.PSave(db)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 2. Marshal the page into an intermediate buffer, bail on error
var buf bytes.Buffer
if err := jsonapi.MarshalPayload(&buf, p); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 3. Write the entire response; failures to write the intermediate buffer
// cannot be communicated over HTTP
w.Header().Set("Content-Type", jsonapi.MediaType)
w.WriteHeader(http.StatusCreated)
if _, err := buf.WriteTo(w); err != nil {
log.Printf("failed to write response: %v", err)
return
}
I am working on REST api build with golang when I am returning a json of employees like
{
"data":{
"10":{
"1517616000":[1000]
},
"15":{
"1517616000":[1200]
},
"29":{
"1517616000":[1200]
},
"42":{
"1517616000":[1200]
}
}
}
it should be showing in 42,15,29,10 order as I am sorting the data with priority parameter. When I check with console it shows me correct output but not on postman and browser
Used following function to return json
c.JSON(200, gin.H{
"status": response,
})
If you store the 42,15,29,10 as keys in a Go map, the JSON mapEncoder is going to sort them by key when printing to JSON.
Instead we can store the data as elements in a slice, which preserves the order we specify. In order to have the slice print as a JSON map rather than a JSON array we need to plug in a bit of our own JSON marshaling logic.
package main
import (
"bytes"
"encoding/json"
"fmt"
)
type Response struct {
Data OrderedMap `json:"data"`
}
type OrderedMap []Entry
type Entry struct {
Key string
Value interface{}
}
func (m OrderedMap) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
buf.WriteByte('{')
for i, entry := range m {
if i != 0 {
buf.WriteByte(',')
}
key, err := json.Marshal(entry.Key)
if err != nil {
return nil, err
}
buf.Write(key)
buf.WriteByte(':')
value, err := json.Marshal(entry.Value)
if err != nil {
return nil, err
}
buf.Write(value)
}
buf.WriteByte('}')
return buf.Bytes(), nil
}
func main() {
response := Response{
Data: OrderedMap{
{"42", map[string][]int{"1517616000": []int{1200}}},
{"15", map[string][]int{"1517616000": []int{1200}}},
{"29", map[string][]int{"1517616000": []int{1200}}},
{"10", map[string][]int{"1517616000": []int{1000}}},
},
}
j, err := json.MarshalIndent(response, "", " ")
if err != nil {
panic(err)
}
fmt.Println(string(j))
}
The result is the same JSON representation you were seeing before but with the keys in the order specified by our OrderedMap.
{
"data": {
"42": {
"1517616000": [1200]
},
"15": {
"1517616000": [1200]
},
"29": {
"1517616000": [1200]
},
"10": {
"1517616000": [1000]
}
}
}
I'm attempting to do a resumable upload as described here.
When I perform the upload, I receive a response status of 400 Bad Request, and a response body of:
{
"error": {
"errors": [
{
"domain": "global",
"reason": "badContent",
"message": "Unsupported content with type: application/octet-stream"
}
],
"code": 400,
"message": "Unsupported content with type: application/octet-stream"
}
}
The script I'm using to perform the upload is in Go, here:
package main
import(
"bytes"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
"path/filepath"
)
func main(){
// config
accessToken := "a valid token"
acctId := "a valid account id"
webPropertyId := "a valid web property id"
customDataSourceId := "a valid custom data source id"
path := "/a/path/to/a/validly/formatted/file.csv"
params := map[string]string{
"title": "My Document",
"author": "Becca Petrin",
"description": "Riveting stuff",
}
paramName := "file"
url := fmt.Sprintf("https://www.googleapis.com/upload/analytics/v3/management/accounts/%s/webproperties/%s/customDataSources/%s/uploads?uploadType=resumable", acctId, webPropertyId, customDataSourceId)
// create the body
file, err := os.Open(path)
if err != nil {
fmt.Println("Err opening file:", err.Error())
return
}
defer file.Close()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile(paramName, filepath.Base(path))
if err != nil {
fmt.Println("Err creating form file:", err.Error())
return
}
_, err = io.Copy(part, file)
if err != nil {
fmt.Println("Err copying:", err.Error())
return
}
for k, v := range params {
_ = writer.WriteField(k, v)
}
if err := writer.Close(); err != nil {
fmt.Println("Err closing writer:", err.Error())
return
}
req, err := http.NewRequest("POST", url, body)
if err != nil {
fmt.Printf("Err creating request:", err.Error())
return
}
// add authorization
req.Header.Set("Authorization", "Bearer "+accessToken)
// add headers
// no multipart headers work, and "application/octet-stream"" doesn't work
// uncommenting and using "text/plain" results in a 200 without the expected response body
//req.Header.Add("Content-Type", "text/plain")
// execute request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Err doing request:", err.Error())
return
}
fmt.Println("Response status:", resp.Status)
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("Err reading resp body:", err.Error())
return
}
fmt.Printf("Response body: %s", b)
}
As noted in the comments, I receive the unsupported content type response if I don't include a Content-Type header. I also receive it if I use any multipart content types, or application/octet-stream. If I use text/plain, I receive a 200, but I don't receive the expected body.
What am I doing wrong? Thanks in advance!
I decided to take an alternate approach to this by trying through the Google Go client library, and I got it working with this snippet:
package main
import (
"errors"
"fmt"
"os"
"time"
google "google.golang.org/api/analytics/v3"
"golang.org/x/oauth2"
)
var (
accessToken = "a valid token"
acctId = "a valid account ID"
webPropertyId = "a valid web property ID"
customDataSourceId = "a valid custom data source ID"
filePath = "/path/to/file.csv"
)
func main(){
ctx1 := NewCallContext()
tokenSource := TokenSource{}
httpClient := oauth2.NewClient(ctx1, &tokenSource)
service, err := google.New(httpClient)
if err != nil {
fmt.Println("Err making client:", err.Error())
return
}
r, err := NewFileReader(filePath)
if err != nil {
fmt.Println("Err making reader:", err.Error())
return
}
ctx2 := NewCallContext()
call := service.Management.Uploads.UploadData(acctId, webPropertyId, customDataSourceId)
call = call.ResumableMedia(ctx2, r, 10, "application/octet-stream")
upload, err := call.Do()
if err != nil {
fmt.Println("Err doing call: %v", err)
return
}
fmt.Printf("%s", upload)
}
// http://golang.org/pkg/io/#ReaderAt
func NewFileReader(pathToFile string) (*FileReader, error) {
f, err := os.Open(pathToFile)
if err != nil {
return nil, err
}
return &FileReader{f}, nil
}
type FileReader struct {
f *os.File
}
func (s *FileReader) ReadAt(p []byte, off int64) (int, error) {
return s.f.ReadAt(p, off)
}
// https://godoc.org/golang.org/x/net/context#Context
func NewCallContext() *CallContext {
c := make(<-chan struct{})
return &CallContext{c}
}
type CallContext struct {
doneChan <-chan struct{}
}
func (s *CallContext) Deadline() (time.Time, bool) {
return time.Now().Add(time.Duration(10) * time.Second), true
}
func (s *CallContext) Done() <-chan struct{} {
return s.doneChan
}
func (s *CallContext) Err() error {
select {
case <- s.doneChan:
return errors.New("Done")
default:
}
return nil
}
func (s *CallContext) Value(key interface{}) interface{} {
return nil
}
// satisfies the oauth2 tokensource interface
type TokenSource struct {}
func (t *TokenSource) Token() (*oauth2.Token, error) {
return &oauth2.Token{AccessToken:accessToken}, nil
}