Golang Mutation with a list as a param variable (GRAPHQL) - go

Basically what im trying to do is send a list of string ex: ["aa","bb","vv"] into a graphql Mutation field, currently this is my Mutation Schema
"listTest": &graphql.Field{
Type: QueryMessageType,
Args: graphql.FieldConfigArgument{
"listNew": &graphql.ArgumentConfig{
Description: "Example List of Json String",
Type: graphql.NewList(graphql.NewNonNull(graphql.String)),
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
list := p.Args["listTest"].([]string)
return listTest(list)
},
},
and the Method listTest
func listTest(testing[]string) (*QueryMessage, error) {
fmt.Println(testing)
return &QueryMessage{
QueryBody: "nothing to do here",
}, nil
}
However when i do the request in INSOMNIA the response is:
{
"data": {
"listTest": null
},
"errors": [
{
"message": "interface conversion: interface {} is []interface {}, not []string",
"locations": []
}
]
}
and the request is this:
mutation{
listTest(listNew: ["aa","bb","vv"]){
querybody
}
}
can anyone tell me how to receive a List of String in my Go Server. Thanks! :)
UPDATE
When i call a fmt.Println(p.Args["listTest"])
the result is: [aa bb vv]
SOLVED
Following the instructions of the voted answer, the script now do his job. This is the final result:
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
var groupIDs []string
for _, gid := range p.Args["list"].([]interface{}) {
groupIDs = append(groupIDs, gid.(string))
}
for _, final := range groupIDs {
fmt.Println(final)
}
return listTest(groupIDs)
},
and in the console i got this:
aa
bb
vv

Your problem, according to the error message, is with this line:
list := p.Args["listTest"].([]string)
p.Args["listTest"] is returning []interface{}.
interface{} can store any other type. If you are familiar with java it's a little bit like Object.
The problem here is that you have your field from p.Args["listTest"] and you are trying to type assert it to []string. This would work if the value stored in args were interface{} (any). But it's not, p.Args (according to the error) holds []interface{}. This is a slice of interface values, where each of those can be anything (rather than a single interface value holding a slice of strings.)
Instead try ranging over that list of interfaces, and type asserting each value.
var strs []string
for _, v := range p.Args["list"].([]interface{}) {
strs = append(strs, v.(string))
}
Or investigate another way to set up the graphQL types so that you get the values back in a more useable way.

Related

Using validator to check if value is boolean

I'm new to Go, so this might be very easy, but I can't find it. I have an entity Page with these two properties:
type Page struct {
Title string `form:"title" binding:"required"`
Active bool
}
Now, if you don't send a title we get a (not very pretty, but acceptable*):
Key: 'Page.Title' Error:Field validation for 'Title' failed on the 'required' tag.
Now, if I send this to the endpoint:
{
"title": "I'm a valid title",
"active": "I'm not a boolean at all!"
}
We get this:
json: cannot unmarshal string into Go struct field Page.Active of type bool
Which, IMO, is giving way too much info. What is the standard practice in Go to validate user input?
I was first making a page-validor.go with some checks, then I found this, but I'm not sure what is good practice in Go.
How do I validate this properly? Should I find check what is provided and then try to move it into the struct and validate the actual contents?
I am using GinGonic
* I've found a way to unwrap the errors and make it nicer
Write custom JSON Unmarshaller method for the type Page and inside UnmarshalJSON(bytes []byte) method, you can unmarshal the JSON bytes to map[string]interface{} and then validate the types you need with the JSON field keys.
An example of the JSON Unmarshaller looks like below.
type Page struct {
Title string `form:"title" binding:"required"`
Active bool
}
func (p *Page) UnmarshalJSON(bytes []byte) error {
var data map[string]interface{}
err := json.Unmarshal(bytes, &data)
if err != nil {
return err
}
actv, _ := data["active"]
if reflect.TypeOf(actv).Kind() != reflect.Bool {
return errors.New("active field should be a boolean")
}
p.Active = actv.(bool)
return nil
}
See the full example here in Playground.
After some more research, I've implemented Go-map-schema.
var page Page
src := make(map[string]interface{})
json.Unmarshal(jsonData, &src)
results, _ := schema.CompareMapToStruct(page, src, nil)
fmt.Println(results.MissingFields)
fmt.Println(results.MismatchedFields)
This works simple with the standard notations for an struct:
type Page struct {
Title string `json:"title" validator:"required"`
Active bool `json:"metaRobotsFollow" validate:"required,bool"`
}
You should use validator v10 available with Go
validator documentation go
For your use case you can use boolean validator
https://pkg.go.dev/github.com/go-playground/validator/v10#hdr-Boolean
type Page struct {
Title string `json:"title" binding:"required"`
Active bool `json:"active" binding:"required,boolean"`
}
Below is sample Test case with one positive and negative
func TestPage(t *testing.T) {
tests := []struct {
testName string
input string
wantErr bool
}{
{
testName: "positive test",
input: `{
"title": "first book title",
"active": false
}`,
wantErr: false,
}, {
testName: "wrong boolean",
input: `{
"title": "second book title",
"active": falsee
}`,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
var p Page
b := []byte(tt.input)
err := json.Unmarshal(b, &p)
assert.Nil(t, err, "got error %v", err)
})
}

Create slice of integers from JSON array

I'm trying to figure out how to create a slice I can more easily manipulate and use JUST the values from to later iterate over to make a number of API requests. The slice of integers are API IDs. I am successfully making a struct with custom types after making a GET to retrieve the JSON Array of IDs, but I now need to pull only the values from that JSON array and dump them into a slice without the key "id" (which will likely need to change over time in size).
This is my JSON:
{
"data": [
{
"id": 38926
},
{
"id": 38927
}
],
"meta": {
"pagination": {
"total": 163795,
"current_page": 3,
"total_pages": 81898
}
}
}
And I would like this from it:
{38926, 38927}
If you want custom Unmarshaling behavior, you need a custom type with its own json.Unmarshaler e.g.
type ID int
func (i *ID) UnmarshalJSON(data []byte) error {
id := struct {
ID int `json:"id"`
}{}
err := json.Unmarshal(data, &id)
if err != nil {
return err
}
*i = ID(id.ID)
return nil
}
To use this, reference this type in your struct e.g.
type data struct {
IDs []ID `json:"data"`
}
var d data
working example: https://go.dev/play/p/i3MAy85nr4X

how to unmarshal a map from DynamoDB to struct?

There is the following field on dynamo
{
"config": {
"BASE_AUTH_URL_KEY": "https://auth.blab.bob.com",
"BASE_URL": "https://api.dummy.data.com",
"CONN_TIME_OUT_SECONDS": "300000",
"READ_TIME_OUT_SECONDS": "300000"
},
"id": "myConfig"
}
and getting the element with dynamodbattribute
import(
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute")
result, err := svc.GetItem(&dynamodb.GetItemInput{
TableName: aws.String(tableName),
Key: map[string]*dynamodb.AttributeValue{
"id": {
S: aws.String(configId),
},
},
})
this code its working but when i try to retrieve the object its rendered like this
map[config:{
M: {
BASE_AUTH_URL_KEY: {
S: "https://auth.blab.bob.com"
},
CONN_TIME_OUT_SECONDS: {
S: "300000"
},
READ_TIME_OUT_SECONDS: {
S: "300000"
},
BASE_URL: {
S: "https://api.dummy.data.com"
}
}
} id:{
S: "myConfig"
}]
for that reason when i try to unmarshal my object the object unmarshalled returns as {}
type Config struct {
id string
baseAuthUrlKey string
baseUrl string
connectTimeOutSecs string
readTimeOutSecs string
}
item := Config{}
err = dynamodbattribute.UnmarshalMap(result.Item, &item)
how can i assign the value return from the GetItem that seems to be a map to my struct ?
The root of the issue is that your Config struct is incorrectly structured.
I recommend using json-to-go when converting JSON to Go structs; this tool will help you catch issues like this in the future.
Once you get your struct constructed correctly, you'll also notice that your struct fields are not capitalized, meaning they will not be exported (i.e. able to be used by other packages), which is another reason that your UnmarshalMap code will not return the result you are expecting.
Here is a good answer on struct field visibility and its importance, briefly summarized above.
Below is a corrected version of your struct that, combined with your UnmarshalMap code, will correctly allow you to print your item and not receive a {} which is no fun.
type Item struct {
Config struct {
BaseAuthUrlKey string `json:"BASE_AUTH_URL_KEY"`
BaseUrl string `json:"BASE_URL"`
ConnTimeoutSeconds string `json:"CONN_TIME_OUT_SECONDS"`
ReadTimeoutSeconds string `json:"READ_TIME_OUT_SECONDS"`
} `json:"config"`
ID string `json:"id"`
}

How to get/extract values of a JSON

I'm trying to get values/index/key of a JSON but no success. I found some answers and tutorials but I couldn't get them working for me.
I have the following JSON:
{
"id": "3479",
"product": "Camera",
"price": "",
"creation": 04032020,
"products": [
{
"camera": "Nikon",
"available": true,
"freeshipping": false,
"price": "1,813",
"color": "black"
},
{
"camera": "Sony",
"available": true,
"freeshipping": true,
"price": "931",
"color": "black"
}
],
"category": "eletronics",
"type": "camera"
}
I have tried several examples but none worked for this type of Json.
The error I'm getting:
panic: interface conversion: interface {} is nil, not
map[string]interface {}
I believe it's because of "products[]" I tried map[string]interface{} and []interface{} it compiles but gives me that error afterwards.
Could you give me an example on how I can extract these values?
The code I'm using:
//gets the json
product,_:=conn.GetProductData(shop.Info.Id)
// assing a variable
productInformation:=<-product
//prints the json
fmt.Printf(productInformation)
//try to get json values
type Product struct {
Id string
Product string
}
var product Product
json.Unmarshal([]byte(productInformation), &product)
fmt.Printf("Id: %s, Product: %s", product.Id, product.Product)
This code does not panic but it doesn't print all results either so I tried
this one below (which was supposed to give me all results) but it panics
var result map[string]interface{}
json.Unmarshal([]byte(productInformation), &result)
// The object stored in the "birds" key is also stored as
// a map[string]interface{} type, and its type is asserted from
// the interface{} type
products := result["products"].(map[string]interface{})
for key, value := range products {
// Each value is an interface{} type, that is type asserted as a string
fmt.Println(key, value.(string))
}
You need to add json tag to specify the field name in json as it is in lowercase
type Product struct {
Id string `json:"id"`
Product string `json:"product"`
}
And in the second case according to json it is a slice not a map so you need to cast it to []interface{}
var result map[string]interface{}
json.Unmarshal([]byte(productInformation), &result)
// The object stored in the "birds" key is also stored as
// a map[string]interface{} type, and its type is asserted from
// the interface{} type
products := result["products"].([]interface{})
for key, value := range products {
// Each value is an interface{} type, that is type asserted as a string
fmt.Println(key, value)
}

Parsing JSONSchema to struct type in golang

So, my use case consists of parsing varying JSON schemas into new struct types, which will be further used with an ORM to fetch data from a SQL database. Being compiled in nature, I believe there will not be an out-of-the-box solution in go, but is there any hack available to do this, without creating a separate go process. I tried with reflection, but could not find a satisfactory approach.
Currently, I am using a-h generate library which does generate the structs, but I am stuck at how to load these new struct types in go runtime.
EDIT
Example JSON Schema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Address",
"id": "Address",
"type": "object",
"description": "address",
"properties": {
"houseName": {
"type": "string",
"description": "House Name",
"maxLength": 30
},
"houseNumber": {
"type": "string",
"description": "House Number",
"maxLength": 4
},
"flatNumber": {
"type": "string",
"description": "Flat",
"maxLength": 15
},
"street": {
"type": "string",
"description": "Address 1",
"maxLength": 40
},
"district": {
"type": "string",
"description": "Address 2",
"maxLength": 30
},
"town": {
"type": "string",
"description": "City",
"maxLength": 20
},
"county": {
"type": "string",
"description": "County",
"maxLength": 20
},
"postcode": {
"type": "string",
"description": "Postcode",
"maxLength": 8
}
}
}
Now, in the above-mentioned library, there is a command line tool, which generates the text for struct type for above json as below:
// Code generated by schema-generate. DO NOT EDIT.
package main
// Address address
type Address struct {
County string `json:"county,omitempty"`
District string `json:"district,omitempty"`
FlatNumber string `json:"flatNumber,omitempty"`
HouseName string `json:"houseName,omitempty"`
HouseNumber string `json:"houseNumber,omitempty"`
Postcode string `json:"postcode,omitempty"`
Street string `json:"street,omitempty"`
Town string `json:"town,omitempty"`
}
Now, the issue is that how to use this struct type without re-compilation in the program. There is a hack, where I can start a new go process, but that doesn't seem a good way to do it. One other way is to write my own parser for unmarshalling JSON schema, something like:
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
var f interface{}
json.Unmarshal(b, &f)
m := f.(map[string]interface{})
for k, v := range m {
switch vv := v.(type) {
case string:
fmt.Println(k, "is string", vv)
case float64:
fmt.Println(k, "is float64", vv)
case int:
fmt.Println(k, "is int", vv)
case []interface{}:
fmt.Println(k, "is an array:")
for i, u := range vv {
fmt.Println(i, u)
}
default:
fmt.Println(k, "is of a type I don't know how to handle")
}
}
Can someone please suggest some pointers to look for. Thanks.
So it looks like you're trying to implement your own json marshalling. That's no biggie: the standard json package already supports that. Just have your type implement the MarshalJSON and UnmarshalJSON functions (cf first example on the docs). Assuming some fields will be shared (eg schema, id, type), you can create a unified type like this:
// poor naming, but we need this level of wrapping here
type Data struct {
Metadata
}
type Metadata struct {
Schema string `json:"$schema"`
Type string `json:"type"`
Description string `json:"description"`
Id string `json:"id"`
Properties json.RawMessage `json:"properties"`
Address *Address `json:"-"`
// other types go here, too
}
Now all properties will be unmarshalled into a json.RawMessage field (essentially this is a []byte field). What you can do in your custom unmarshall function now is something like this:
func (d *Data) UnmarshalJSON(b []byte) error {
meta := Metadata{}
// unmarshall common fields
if err := json.Unmarshal(b, &meta); err != nil {
return err
}
// Assuming the Type field contains the value that allows you to determine what data you're actually unmarshalling
switch meta.Type {
case "address":
meta.Address = &Address{} // initialise field
if err := json.Unmarshal([]byte(meta.Properties), meta.Address); err != nil {
return err
}
case "name":
meta.Name = &Name{}
if err := json.Unmarshal([]byte(meta.Properties), meta.Name); err != nil {
return err
}
default:
return errors.New("unknown message type")
}
// all done
d.Metadata = meta // assign to embedded
// optionally: clean up the Properties field, as it contains raw JSON, and is exported
d.Metadata.Properties = json.RawMessage{}
return nil
}
You can do pretty much the same thing for marshalling. First work out what type you're actually working with, then marshal that object into the properties field, and then marhsal the entire structure
func (d Data) MarshalJSON() ([]byte, error) {
var (
prop []byte
err error
)
switch {
case d.Metadata.Address != nil:
prop, err = json.Marshal(d.Address)
case d.Metadata.Name != nil:
prop, err = json.Marshal(d.Name) // will only work if field isn't masked, better to be explicit
default:
err = errors.New("No properties to marshal") // handle in whatever way is best
}
if err != nil {
return nil, err
}
d.Metadata.Properties = json.RawMessage(prop)
return json.Marshal(d.Metadata) // marshal the unified type here
}

Resources