Get value of struct with interfaces - go

I'm trying to parse this petition (https://www.binance.com/api/v1/depth?symbol=MDABTC&limit=500)
I was having tons of problems to create an struct for it, so I used an automated tool, this is what my struct looks like:
type orderBook struct {
Bids [][]interface{} `json:"Bids"`
Asks [][]interface{} `json:"Asks"`
}
I recover and parse the petition by doing:
url := "https://www.binance.com/api/v1/depth?symbol=MDABTC&limit=500"
resp, err := http.Get(url)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}else{
book := orderBook{}
if err := json.Unmarshal(body, &book); err != nil {
panic(err)
}
But whenever I try to make an operation with the struct, like:
v := book.Asks[i][0] * book.Asks[i][1]
I get an error:
invalid operation: book.Asks[i][0] * book.Asks[i][1] (operator * not
defined on interface)
How do I define it? Do I need to create an struct for bids/asks, if so, how would that look like?
Sorry if this seems basic, I just started learning go.

In Golang Spec
For an expression x of interface type and a type T, the primary
expression
x.(T)
asserts that x is not nil and that the value stored in x is of type T.
The notation x.(T) is called a type assertion.
Fetching an underlying value of string type you need to type assert to string from interface.
books.Asks[0][0].(string)
For performing an arithmetic operation on same you needs to convert string into float64 to take into account decimal values
v := strconv.ParseFloat(books.Asks[0][0].(string), 64) * strconv.ParseFloat(books.Asks[0][1].(string), 64)
Checkout code on Go playground

Note that you could define proper structs that unmarshal from the JSON document by implementing the json.Unmarshaler interface.
For example (on the Go Playground):
type OrderBook struct {
Asks, Bids []Order
LastUpdateId int
}
type Order struct {
Price, Volume float64
}
func (o *Order) UnmarshalJSON(bs []byte) error {
values := make([]interface{}, 0, 3)
err := json.Unmarshal(bs, &values)
if err != nil {
return err
}
// TODO: more error checking re: len(values), and their types.
price, err := strconv.ParseFloat(values[0].(string), 10)
if err != nil {
return err
}
volume, err := strconv.ParseFloat(values[1].(string), 10)
if err != nil {
return err
}
*o = Order{price, volume}
return nil
}
As such, unmarshaling those documents looks idiomatic:
func main() {
book := OrderBook{}
err := json.Unmarshal([]byte(jsonstr), &book)
if err != nil {
panic(err)
}
fmt.Printf("Asks: %#v\n", book.Asks)
fmt.Printf("Bids: %#v\n", book.Bids)
fmt.Printf("Update: %#v\n", book.LastUpdateId)
// Asks: []main.Order{main.Order{Price:0.00013186, Volume:167}, main.Order{Price:0.00013187, Volume:128}, ...
// Bids: []main.Order{main.Order{Price:0.00013181, Volume:110}, main.Order{Price:0.00013127, Volume:502}, ...
// Update: 14069188
}

Related

Check if any variable conforms any interface using generics in Go

I am writing an API using go-fiber, and I want to check, if passed JSON conforms an interface that I want to see. So I decided to use 1.18's feature - generics. Here is what I did, but it does not work due to type problem.
func checkDataConformsInterface[I any](format I, c *fiber.Ctx) (I, error) {
if err := c.BodyParser(&format); err != nil {
return nil, err
}
return c.JSON(format), nil
}
The errors say
src/endpoints/v1/tasks.go:36:10: cannot use nil as I value in return statement
src/endpoints/v1/tasks.go:39:9: cannot use c.JSON(format) (value of type error) as type I in return statement
And I want to call the function like this:
type CreateTaskDF struct {
Target string `json:"target"`
Deepness int `json:"deepness"`
}
func CreateTask(c *fiber.Ctx) error {
data, err := checkDataConformsInterface[CreateTaskDF](&CreateTaskDF{}, c)
if err != nil {
log.Fatal(err)
}
// work with data here
...
How should I convert the return value in the function to make it work? Thanks!
It probably could work like this(if you do not consider any lib-based payload validators, which exist in almost every golang routing lib or web framework). So, to just validate your data you can use this:
func checkDataConformsInterface[I any](format I, c *fiber.Ctx) bool {
if err := c.BodyParser(&format); err != nil {
return false
}
return true
}
So I came up with the following solution
func checkDataConformsInterface[I any](format *I, c *fiber.Ctx) error {
if err := c.BodyParser(&format); err != nil {
return err
}
err := c.JSON(format)
if err != nil {
return err
}
return nil
}
which can be called like
func CreateTask(c *fiber.Ctx) error {
parsedData := CreateTaskDF{}
err := checkDataConformsInterface[CreateTaskDF](&parsedData, c)
if err != nil {
c.SendStatus(400)
return c.SendString("Wrong data")
}
Please, point me the problems if any

Is there something like sql.NullJson akin to sql.NullString in golang?

I am querying from postgres using golang, one of the field contains json that can sometimes be NULL
Like this
row := db.QueryRow(
"select my_string, my_json from my_table where my_string = $1",
my_id)
var my_string sql.NullString
var myjson MyJsonStruct
err := row.Scan(&my_string2, &myjson)
But I am getting
sql: Scan error on column index 2, name "my_json": unsupported Scan, storing driver.Value type <nil> into type *main.MyJsonStruct
I checked https://godoc.org/database/sql but didn't find sql.NullJson What is a go way of dealing with this situation?
No, there is no sql.json. I think the best way to dealing with json column in db is to implement valuer and scanner. so something like this :
// Scan implements database/sql.Scanner interface
func (m *MyJsonStruct) Scan(src interface{}) error {
if src == nil {
return nil
}
data, ok := src.([]byte)
if !ok {
return errors.New("type assertion to []byte failed")
}
var myJsonStruct MyJsonStruct
if err := json.Unmarshal(data, &myJsonStruct); err != nil {
return fmt.Errorf("unmarshal myJsonStruct: %w", err)
}
*m = myJsonStruct
return nil
}
// Value implements database/sql/driver.Valuer interface
func (m MyJsonStruct) Value() (driver.Value, error) {
data, err := json.Marshal(m)
if err != nil {
return nil, fmt.Errorf("marshal myJsonStruct: %w", err)
}
return data, nil
}

Calling function of a struct imported via plugin

I've a following plugin:
package main
type Test struct {
Id string
}
func (test *Test) GetId() string {
return test.Id
}
var V Test
I'm importing it within my app:
package main
import (
"fmt"
"plugin"
)
func main() {
p, err := plugin.Open("test.so")
if err != nil {
panic(err)
}
v, err := p.Lookup("V")
if err != nil {
panic(err)
}
fmt.Println(v)
}
Unfortunately I'm not able to call v.getId() on it - is there a way to expose all functions that are set on the given struct?
Lookup returns a Symbol, which is just an empty interface. In order to use this you need to assert the type you want. The documentation for Symbol example shows both symbols asserted to the expected types:
v, err := p.Lookup("V")
if err != nil {
panic(err)
}
f, err := p.Lookup("F")
if err != nil {
panic(err)
}
*v.(*int) = 7
f.(func())() // prints "Hello, number 7"
To do that in your program, create the type you want, which in this case is an interface because you're looking for a particular method set (see the "Tour of Go" section on interfaces, esp implicit implementation and type assertion)
Here we create the V interface in the main program to define the method we want, than use a type assertion on the symbol returned from Lookup:
type V interface {
GetId() string
}
func main() {
p, err := plugin.Open("plugin.so")
if err != nil {
panic(err)
}
s, err := p.Lookup("V")
if err != nil {
panic(err)
}
v := s.(V)
fmt.Println(v.GetId())
}

Map response to a struct using Golang

I am attempting to map a response from an API to a struct using Golang.
The JSON that comes back when I view the link in the browser is below:
{
"GBP": 657.54
}
And I just want to map it to a simple struct like so:
type Price struct {
Name string
Value float64
}
Here is my current code.
func FetchCoinPrice(fsym string, tsyms string) Price {
url := fmt.Sprintf("https://min-api.cryptocompare.com/data/price?fsym=" + fsym + "&tsyms=" + tsyms)
fmt.Println("Requesting data from " + url)
price := Price{}
// getting the data using http
request, err := http.Get(url)
if err != nil {
log.Fatal(err.Error())
}
// Read the response body using ioutil
body, err := ioutil.ReadAll(request.Body)
if err != nil {
log.Fatal(err.Error())
}
defer request.Body.Close()
if request.StatusCode == http.StatusOK {
json.Unmarshal(body, &price)
}
return price
}
At the moment all I receive is an empty struct, I know the link is bringing back the correct data and I've tested it in my browser.
The mapping doesn't work that way. Instead, you should use a map:
data := []byte(`{
"GBP": 657.54
}`)
priceMap := map[string]float64{}
err := json.Unmarshal(data, &priceMap)
// Check your errors!
if err != nil {
log.Fatal(err.Error())
}
fmt.Println(priceMap)
This will print:
map[GBP:657.54]
You can then iterate over the map and build the struct you mentioned above, or just access the entry directly if you know the currency. eg: priceMap["GBP"]
You should really check your errors, especially if you're not getting the output you expect from Unmarshal.
The problem is that the unmarshaler cannot guess that keys in a JSON object should correspond to some value in a struct. Golang JSON mapping simply doesn't work that way.
However, you can make your "Price" type implement json.Unmarshaler to deserialize a message into a map of floats (map[string]float64) then ensure the shape is right and populate the struct accordingly:
func (p *Price) UnmarshalJSON(bs []byte) error {
kvs := map[string]float64{}
err := json.Unmarshal(bs, &kvs)
if err != nil {
return err
}
if len(kvs) != 1 {
return fmt.Errorf("expected 1 key, got %d", len(kvs))
}
for name, value := range kvs {
p.Name, p.Value = name, value
}
return nil
}
func main() {
jsonstr := `[{"GBP":657.54},{"USD":123.45}]`
ps := []Price{}
err := json.Unmarshal([]byte(jsonstr), &ps)
if err != nil {
panic(err)
}
// ps=[]main.Price{
// main.Price{Name:"GBP", Value:657.54},
// main.Price{Name:"USD", Value:123.45}
// }
}

Custom json unmarshaler return empty fields

I've implemented a custom JSON unmarshaler, but for some reason it won't return the proper value -all fields come back nil.
For example:
type test struct {
t string
}
func New(data string) (*test, error) {
return &test{t: data}, nil
}
func (t *test) UnmarshalJSON(b []byte) error {
tt, err := New(string(b))
if err != nil {
return err
}
t = tt
return nil
}
func main() {
str := `"hello"`
b := []byte(str)
t := &test{}
err := json.Unmarshal(b, t)
if err != nil {
fmt.Printf("unmarshal error occurred: %#v", err)
}
fmt.Printf("%#v", t)
}
https://play.golang.org/p/LuXkZQZHWz
The above code shows the output: &main.test{t:""}
Why doesn't it unmarshal the fields? i.e &main.test{t:"hello"}
Only when I dereference the pointers above, do I get the desired result.
i.e -
func (t *test) UnmarshalJSON(b []byte) error {
tt, err := New(string(b))
if err != nil {
return err
}
*t = *tt
return nil
}
You're assigning the local variable t, a pointer to test, to the value of the local variable tt, also a pointer to test. This has no effect on the value the original pointer t pointed to. You have to dereference the pointers to change the value it points to, rather than changing the local pointer itself:
*t = *tt

Resources