I'm testing golang with elasticsearch
I'm using the library:
https://github.com/mattbaird/elastigo
My problem is when i run:
go run elastigo_postal_code2.go
The compiler show something like this:
panic: runtime error: index out of range
goroutine 1 [running]:
panic(0x893ce0, 0xc82000a150)
/opt/go/src/runtime/panic.go:464 +0x3ff
main.main()
/home/hector/go/elastigo_postal_code2.go:80 +0xa30
exit status 2
I'm not sure what this means or how I can fix it!
Someone can help me and tell me I'm doing wrong
/*
curl -X PUT "http://localhost:9200/mx2/postal_code/1" -d "
{
\"cp\" : \"20000\",
\"colonia\" : \"Zona Centro\",
\"ciudad\" : \"Aguascalientes\",
\"delegacion\" : \"Aguascalientes\",
\"location\": {
\"lat\": \"22.0074\",
\"lon\": \"-102.2837\"
}
}"
curl -X PUT "http://localhost:9200/mx2/postal_code/2" -d "
{
\"cp\" : \"20008\",
\"colonia\" : \"Delegacion de La Secretaria de Comercio y Fomento Industrial\",
\"ciudad\" : \"Aguascalientes\",
\"delegacion\" : \"Aguascalientes\",
\"location\": {
\"lat\": \"22.0074\",
\"lon\": \"-102.2837\"
}
}"
*/
package main
import (
"encoding/json"
"flag"
"log"
elastigo "github.com/mattbaird/elastigo/lib"
)
var (
eshost *string = flag.String("host", "localhost", "Elasticsearch Server Host Address")
)
func main() {
flag.Parse()
log.SetFlags(log.Ltime | log.Lshortfile)
c := elastigo.NewConn()
c.Domain = *eshost
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
searchJson := `{
"size": 10,
"query": {
"match": {
"all": {
"query": "aguascalientes",
"operator": "and"
}
}
},
"sort": [{
"colonia": {
"order": "asc",
"mode": "avg"
}
}]
}`
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
searchresponse, err := c.Search("mx2", "postal_code", nil, searchJson)
if err != nil {
log.Println("error during search:" + err.Error())
log.Fatal(err)
}
// try marshalling to ElasticSearchResponse type
var t ElasticSearchResponse
bytes, err := searchresponse.Hits.Hits[0].Source.MarshalJSON()
if err != nil {
log.Fatalf("err calling marshalJson:%v", err)
}
json.Unmarshal(bytes, &t)
log.Printf("Search Found: %s", t)
c.Flush()
}
func (t *ElasticSearchResponse) String() string {
b, _ := json.Marshal(t)
return string(b)
}
// used in test suite, chosen to be similar to the documentation
type ElasticSearchResponse struct {
Cp string `json:"cp"`
Colonia string `json:"colonia"`
Ciudad string `json:"ciudad"`
Delegacion string `json:"delegacion"`
Location Location `json:"location"`
}
type Location struct {
Lat string `json:"lat"`
Lon string `json:"lon"`
}
The code of my scritp is here:
https://gist.github.com/hectorgool/c9e18d7d6324a9ed1a2df92ddcc95c08#file-elastigo_example-go-L80
That's not a compile error, you are getting panic: runtime error: index out of range
If the slice searchresponse.Hits.Hits has a length of 0, indexing the first element is going to be out of range and panic. You can check for the number of Hits first with searchresponse.Hits.Len().
Related
I want to make an array optional in struct and use it with an if else in a func.
type TestValues struct {
Test1 string `json:"test1"`
DefaultTests []string `json:"__tests"`
//DefaultTests *array `json:"__tests,omitempty" validate:"option"`
Test2 string `json:"__Test2"`
}
func (x *Controller) createTest(context *gin.Context, uniqueId string, testBody *TestValues) (*http.Response, error) {
if testBody.DefaultTags {
postBody, err := json.Marshal(map[string]string{
"Test2": testBody.Test2,
"Test1": testBody.Test1,
"defaultTests": testBody.DefaultTests,
"uniqueId": uniqueId,
})
} else {
postBody, err := json.Marshal(map[string]string{
"Test2": testBody.Test2,
"Test1": testBody.Test1,
"uniqueId": uniqueId,
})
}
...
}
When I run the code it tells me that DefaultTests is undefined array but I don't want this error to pop because DefaultTests can existe and sometimes it won't be present in the json that's why I want to make it optional. The if else part is not working too.
It's better to use len() when checking if an array is empty here.
if len(testBody.DefaultTests) > 0 {
...
}
Check the Zero value of the DefaultTests in struct below for more clarity on this behaviour
package main
import "fmt"
type TestValues struct {
Test1 string `json:"test1"`
DefaultTests []string `json:"__tests"`
//DefaultTests *array `json:"__tests,omitempty" validate:"option"`
Test2 string `json:"__Test2"`
}
func main() {
var tv = TestValues{Test1: "test"}
if len(tv.DefaultTests) > 0 {
fmt.Printf("Default Tests: %v\n", tv.DefaultTests)
} else {
fmt.Printf("Default Tests empty value: %v\n", tv.DefaultTests)
}
}
Here's my final code with Marc's answer
func (x *Controller) createTest(context *gin.Context, uniqueId string, testBody *TestValues) (*http.Response, error) {
if len(testBody.DefaultTags) > 0 {
postBody, err := json.Marshal(testBody.DefaultTags)
} else {
postBody, err := json.Marshal(map[string]string{
"Test2": testBody.Test2,
"Test1": testBody.Test1,
"uniqueId": uniqueId,
})
}
...
}
I have a json like following, where value can be int or string
{
"data": [
{
"Name": "a_name",
"value": 1
},
{
"Name": "b_name",
"value": "val"
},
{
"Name": "c_name",
"value": 2
}
]
}
Now I want to convert that json into following struct, like only extract a_name and b_name value.
type Data struct {
AName int `json: "a_name"`
BName string `json: "b_name"`
}
I can do it by following way
import (
"encoding/json"
"fmt"
)
type TmpData struct {
Data []struct {
Name string `json:"Name"`
Value interface{} `json:"value"`
} `json:"data"`
}
type ExpectedData struct {
AName int `json: "a_name"`
BName string `json: "b_name"`
}
func main() {
data := `{
"data": [
{
"Name": "a_name",
"value": 1
},
{
"Name": "b_name",
"value": "val"
},
{
"Name": "c_name",
"value": 2
}
]
}`
tmpData := &TmpData{}
json.Unmarshal([]byte(data), tmpData)
ans := &ExpectedData{}
for _, d := range tmpData.Data {
if d.Name == "a_name" {
ans.AName = int(d.Value.(float64))
} else if d.Name == "b_name" {
ans.BName = d.Value.(string)
}
}
fmt.Println(ans)
}
Is there any better solution for this?
Not possible with the standard JSON un-marshalling unless you write a custom un-marshaller for your Data type.
The key here is to define the type for value to be an interface{}, so that multiple types could be stored in your b_name record.
func (d *Data) UnmarshalJSON(data []byte) error {
var result Details
if err := json.Unmarshal(data, &result); err != nil {
return err
}
for _, value := range result.Data {
switch value.Name {
// The json package will assume float64 when Unmarshalling with an interface{}
case "a_name":
v, ok := (value.Value).(float64)
if !ok {
return fmt.Errorf("a_name got data of type %T but wanted float64", value.Value)
}
d.AName = int(v)
case "b_name":
v, ok := (value.Value).(string)
if !ok {
return fmt.Errorf("b_name got data of type %T but wanted string", value.Value)
}
d.BName = v
}
}
return nil
}
Playground - https://go.dev/play/p/GrXKAE87d1F
Response:
{
"meta": {
"query_time": 0.039130201,
"pagination": {
"offset": 1345,
"limit": 5000,
"total": 1345
},
Structs:
type InLicense struct {
Total int16 json:"total,omitempty"
}
type OutLicense struct {
Pagination []InLicense json:"pagination,omitempty"
}
type MetaLicense struct {
Meta []OutLicense json:"meta,omitempty"
}
Code snip inside function:
req, err := http.NewRequest("GET", , nil)
if err != nil {
//handle error
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Println("Error: ", err)
}
defer resp.Body.Close()
val := &MetaLicense{}
err = json.NewDecoder(resp.Body).Decode(&val)
if err != nil {
log.Fatal(err)
}
for _, s := range val.Meta {
for _, a := range s.Pagination {
fmt.Println(a.Total)
}
}
}
After I run this code I get the following error:
json: cannot unmarshal object into Go struct field MetaLicense.meta of type []OutLicense
Which type would []OutLicense need to be in order for this to be unmarshaled correctly? I cant print it another way, but it prints with the {} and Strings.Trim will not work.
You should use just a simple field declaration with actual type, not a [] of the type as it is done below:
type InLicense struct {
Total int16 json:"total,omitempty"
}
type OutLicense struct {
Pagination InLicense json:"pagination,omitempty"
}
type MetaLicense struct {
Meta OutLicense json:"meta,omitempty"
}
I simplified the parsing a bit and just used the json.Unmarshal() function instead.
raw := "{\n \"meta\": {\n \"query_time\": 0.039130201,\n \"pagination\": {\n \"offset\": 1345,\n \"limit\": 5000,\n \"total\": 1345\n }\n }\n}"
parsed := &MetaLicense{}
err := json.Unmarshal([]byte(raw), parsed)
if err != nil {
log.Fatal(err)
}
fmt.Println(parsed.Meta.Pagination.Total) // Prints: 1345
Here's the types I used
type InLicense struct {
Total int16 `json:"total,omitempty"`
}
type OutLicense struct {
Pagination InLicense `json:"pagination,omitempty"`
}
type MetaLicense struct {
Meta OutLicense `json:"meta,omitempty"`
}
As written your provided JSON has a extra , which makes your json unparsable (assuming you add the missing }'s too.
There are no lists in your JSON. Lists are denoted with the [] symbols. For your types to work it, your JSON would have to look like this:
{
"meta": [{
"query_time": 0.039130201,
"pagination": [{
"offset": 1345,
"limit": 5000,
"total": 1345
}]
}]
}
I'm struggling to get the keys and values of the following interface, which is the result of JSON marshaling the result returned by Execute as demonstrated in this example:
[
[
{
"id": 36,
"label": "TestThing",
"properties": {
"schema__testBoolean": [
{
"id": 40,
"value": true
}
],
"schema__testInt": [
{
"id": 39,
"value": 1
}
],
"schema__testNumber": [
{
"id": 38,
"value": 1.0879834
}
],
"schema__testString": [
{
"id": 37,
"value": "foobar"
}
],
"uuid": [
{
"id": 41,
"value": "7f14bf92-341f-408b-be00-5a0a430852ee"
}
]
},
"type": "vertex"
}
]
]
A reflect.TypeOf(result) results in: []interface{}.
I've used this to loop over the array:
s := reflect.ValueOf(result)
for i := 0; i < s.Len(); i++ {
singleVertex := s.Index(i).Elem() // What to do here?
}
But I'm getting stuck with errors like:
reflect.Value.Interface: cannot return value obtained from unexported
field or method
If you know that's your data structure, there's no reason to use reflection at all. Just use a type assertion:
for key, value := range result.([]interface{})[0].([]interface{})[0].(map[string]interface{}) {
// key == id, label, properties, etc
}
For getting the underlying value of an interface use type assertion. Read more about Type assertion and how it works.
package main
import (
"fmt"
)
func main() {
res, err := g.Execute( // Sends a query to Gremlin Server with bindings
"g.V(x)",
map[string]string{"x": "1234"},
map[string]string{},
)
if err != nil {
fmt.Println(err)
return
}
fetchValue(res)
}
func fetchValue(value interface{}) {
switch value.(type) {
case string:
fmt.Printf("%v is an interface \n ", value)
case bool:
fmt.Printf("%v is bool \n ", value)
case float64:
fmt.Printf("%v is float64 \n ", value)
case []interface{}:
fmt.Printf("%v is a slice of interface \n ", value)
for _, v := range value.([]interface{}) { // use type assertion to loop over []interface{}
fetchValue(v)
}
case map[string]interface{}:
fmt.Printf("%v is a map \n ", value)
for _, v := range value.(map[string]interface{}) { // use type assertion to loop over map[string]interface{}
fetchValue(v)
}
default:
fmt.Printf("%v is unknown \n ", value)
}
}
This question already has an answer here:
Return custom error message from struct tag validation
(1 answer)
Closed 1 year ago.
For example I've got the following struct
type Address struct {
City string `json:"city" binding:"required"`
AddressLine string `json:"address_line" binding:"required"`
}
and I've got the following function to handle request from users
func AddressCreate(c *gin.Context) {
var address Address
if err := c.BindJSON(&address); err == nil {
// if everything is good save to database
// and return success message
db.Create(&address)
c.JSON(http.StatusOK, gin.H {"status":"success"})
} else {
c.JSON(http.StatusBadRequest, err)
}
}
Expected behavior is to return JSON, formatted this way
[
{
"city":"required"
}
{
"address_line":"required"
}
]
But I'm getting an error formatted like this
"Address.City": {
"FieldNamespace": "Address.City",
"NameNamespace": "City",
"Field": "City",
"Name": "City",
"Tag": "required",
"ActualTag": "required",
"Kind": 24,
"Type": {},
"Param": "",
"Value": ""
},
"Address.AddressLine": {
"FieldNamespace": "AddressLine",
"NameNamespace": "AddressLine",
"Field": "AddressLine",
"Name": "AddressLine",
"Tag": "required",
"ActualTag": "required",
"Kind": 24,
"Type": {},
"Param": "",
"Value": ""
}
What I tried:
I created function which casts error to ValidationErrors and iterates through all FieldError's in it
func ListOfErrors(e error) []map[string]string {
ve := e.(validator.ValidationErrors)
InvalidFields := make([]map[string]string, 0)
for _, e := range ve {
errors := map[string]string{}
// field := reflect.TypeOf(e.NameNamespace)
errors[e.Name] = e.Tag
InvalidFields = append(InvalidFields, errors)
}
return InvalidFields
}
The output look's much better
[
{
"City":"required"
},
{
"AddressLine":"required"
}
]
But I cannot solve the problem with the name of the fields. I cannot swap Name into name which I noted in structs tag json:"city". So my question is did I choose correct way to solve the problem if the answer is yes how to get structs JSON tag for field?
If you want it to be same as defined in your json tag, then you should use reflection to pull that tag from your data type.
I don't have your libraries, so can't compile and check it. But I believe what you are after should go along those lines:
func ListOfErrors(address *Address, e error) []map[string]string {
ve := e.(validator.ValidationErrors)
InvalidFields := make([]map[string]string, 0)
for _, e := range ve {
errors := map[string]string{}
// field := reflect.TypeOf(e.NameNamespace)
field, _ := reflect.TypeOf(address).Elem().FieldByName(e.Name)
jsonTag := string(field.Tag.Get("json"))
errors[jsonTag] = e.Tag
InvalidFields = append(InvalidFields, errors)
}
return InvalidFields
}
Note that it is a bit contrived as type of address parameter is essentially known. So, not strictly required as a function parameter. But you can change address *Address to address interface{} and use it for other types too.
Disclaimer: I skipped error checking for brevity, but you certainly should check for errors in your code (e.g. no such field error or no json tag on that field).
You can use ToSnake to snake case the names:
import (
"unicode"
)
// ToSnake convert the given string to snake case following the Golang format:
// acronyms are converted to lower-case and preceded by an underscore.
func ToSnake(in string) string {
runes := []rune(in)
length := len(runes)
var out []rune
for i := 0; i < length; i++ {
if i > 0 && unicode.IsUpper(runes[i]) && ((i+1 < length && unicode.IsLower(runes[i+1])) || unicode.IsLower(runes[i-1])) {
out = append(out, '_')
}
out = append(out, unicode.ToLower(runes[i]))
}
return string(out)
}
func ListOfErrors(e error) []map[string]string {
ve := e.(validator.ValidationErrors)
invalidFields := make([]map[string]string, 0)
for _, e := range ve {
errors := map[string]string{}
errors[ToSnake(e.Name)] = e.Tag
invalidFields = append(InvalidFields, errors)
}
return invalidFields
}