I thought I have asserted (as far as I've learnt Go), but I keep getting this error
cannot use readBack["SomePIN"] (type interface {}) as type string in argument to c.String: need type assertion
Here is my code (this snippet is from a Request Handler function and I'm using Echo Web framework and Tiedot NoSQL database)
// To get query result document, simply
// read it [as stated in the Tiedot readme.md]
for id := range queryResult {
readBack, err := aCollection.Read(id)
if err != nil {
panic(err)
}
if readBack["OtherID"] == otherID {
if _, ok := readBack["SomePIN"].(string); ok {
return c.String(http.StatusOK, readBack["SomePIN"])
}
}
}
You are asserting readBack["SomePIN"] as a string - in the if statement. That doesn't make any change to readBack["SomePIN"], however - it's still an interface{}. In Go, nothing ever changes type. Here's what will work:
for id := range queryResult {
readBack, err := aCollection.Read(id)
if err != nil {
panic(err)
}
if readBack["OtherID"] == otherID {
if somePIN, ok := readBack["SomePIN"].(string); ok {
return c.String(http.StatusOK, somePIN)
}
}
}
You were tossing the string value from your type assertion, but you want it. So keep it, as somePIN, and then use it.
Final note - using the value, ok = interfaceVal.(type) syntax is a good practice. If interfaceVal turns out to be a non-string, you'll get value = "" and ok = false. If you eliminate the ok value from the type assertion and interfaceVal is a non-string, the program will panic.
It looks like your converting to a concrete type and throwing away the conversion, I think this should work:
if somePinString, ok := readBack["SomePIN"].(string); ok {
return c.String(http.StatusOK, somePinString)
}
Related
I'm relatively new to Go and am working on building out a request decoder. The request comes in JSON format and we decode that to a map[string]interface{}. We then pass that object data in to be decoded to our own ProcessRequest struct. As I said I'm new so I am reusing some logic in similar parts of the code wrote by previous developers. Essentially we are checking the map for the necessary pieces and then setting and returning those. Can someone explain to me why I am getting the titled error? Would I have to set the items all the way down to base structs that no longer have any nested? Is there a better way to accomplish what I want? Here is the code and the related structs. It is highlighting the error on the return of the model.ProcessRequest. TYIA
type ProcessRequest struct {
RequestID string
Message *message.Message
Rule *Rule
Options *ProcessOptions
//TODO: Context EvaluatorContext
//TODO: Links
}
type Message struct {
ID int
Key string
Source string
Headers *HeaderSet
Categories *CategorySet
Properties *PropertySet
Streams *StreamSet
}
type RuleAction struct {
Name string
Expression map[string]interface{}
}
type RuleLink struct {
LinkID int
Condition map[string]interface{}
TargetRuleID int
}
type Rule struct {
Name string
Code string
Actions []RuleAction
Links []RuleLink
}
type object = map[string]interface{}
func DecodeProcessRequest(dataObject map[string]interface{}) (*model.ProcessRequest, error) {
var (
requestID string
message *message.Message
rule *model.Rule
options *model.ProcessOptions
err error
)
if reqIDSrc, ok := dataObject["requestId"]; ok {
if requestID, err = converter.ToString(reqIDSrc); err != nil {
return nil, errors.Wrapf(err, "Error reading property 'requestID'")
}
if requestID == "" {
return nil, errors.Errorf("Property 'requestID' is an empty string")
}
} else {
return nil, errors.Errorf("Missing required property 'requestID'")
}
if messageSrc, ok := dataObject["message"]; ok {
messageData, ok := messageSrc.(object)
if !ok {
return nil, errors.Errorf("Error reading property 'message': Value is not an object")
}
if message, err = DecodeMessage(messageData); err != nil {
return nil, errors.Wrapf(err, "Error reading property 'message'")
}
} else {
return nil, errors.Errorf("Missing required property 'message'")
}
if ruleSrc, ok := dataObject["rule"]; ok {
ruleObj, ok := ruleSrc.(object)
if !ok {
return nil, errors.Errorf("Error reading property 'rule': Value is not an object")
}
if rule, err = DecodeRule(ruleObj); err != nil {
return nil, errors.Wrapf(err, "Error reading 'rule' during decoding")
}
} else {
return nil, errors.Errorf("Missing required property 'requestID'")
}
// Parse plain object to a Message struct
return &model.ProcessRequest{
requestID,
message,
rule,
options,
}, nil
}
super said in this comment:
In general, the warning says that you should prefer to use the syntax ProcessRequest{ RequestID: requestID, ... }. Naming the keys instead of unkeyed values.
That worked for me. Also the explanation by kostix in this comment really helped.
Basically the idea is that if you use "unkeyed" way of defining struct literals, the meaning of your definitions depends on the way the fields of the underlying type are layed out. Now consider that your type has three fields of type string in a certain order. Now a couple of iterations down the road some programmer moves the second field to the 1st position—your literal will still compile but will end up defining a completely different value at runtime.
I pasted a section of code that was supposed to catch an AllTopologyNodesDownError error which doesn't work and I have no idea why.
func (sc *ServerConfig) addNodesToCluster(store *ravendb.DocumentStore) error {
clusterTopology, err := sc.getClusterTopology(store)
if errors.Is(err, &ravendb.AllTopologyNodesDownError{}) {
for _, url := range sc.Url.List {
err = addNodeToCluster(store, url)
if err != nil {
return err
}
}
} else if err != nil {
return err
}
the structure of the ravendb.AllTopologyNodesDownError is
// AllTopologyNodesDownError represents "all topology nodes are down" error
type AllTopologyNodesDownError struct {
errorBase
}
type errorBase struct {
wrapped error
ErrorStr string
}
screen shot of the error when debugging the code
errors.Is() is used to tell if any error in the chain is the same instance as the provided error1, that can never be the case here because you provided a literal of your error type, no other code could hold that instance or a reference to it.
Your error looks like a type, to tell if any error in the chain is a given type you should use errors.As():
clusterTopology, err := sc.getClusterTopology(store)
var errAllDown *AllTopologyNodesDownError
if errors.As(err, &errAllDown) {
// err had an *AllTopologyNodesDownError in its
// chain and errAllDown now contains it.
}
Can be overridden by implementing the Unwrap() interface which your error type does not.
Background
I'm trying to analyze data from the Reddit api on users. I've declared a User struct like:
type User struct {
Kind string `json:"kind"`
Data struct {
...
Subreddit struct {
...
} `json:"subreddit"`
...
CreatedUtc float64 `json:"created_utc"` <---
...
} `json:"data"`
}
I request the data from the api and print it here.
func GetUser(url string) User {
var response User
resp, err := http.Get(url)
if err != nil {
...
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
...
}
err = json.Unmarshal(body, &response)
if err != nil {
...
}
fmt.Print(response.Data.CreatedUtc) <---
return response
}
Problem
When I request this endpoint it prints 0 while I can see in the browser that the created_utc timestamp is 1562538742. This seems to happen in the vast majority (but not all) cases.
Am I doing something wrong with my type conversions?
To understand why it is zero, you must first understand that in Go, types are not automatically references like in other languages. The variable var abc int will have a value of 0 by default.
When testing whether JSON is parsing correctly, you can change the values of the type to pointers. With this, any field that isn't filled is nil rather than the default value for that type.
Doing this, you can see if the value being returned is true, or if there is another failure, such as incorrect data model or failed network call.
Credit to #JimB for pointing out that I wasn't checking the status code of the response. I had expected that it would throw an error if it was > 400 but according to the docs that is not the case.
In my case, modifying the request to contain a user agent header resolved the issue.
I have this json that I convert to:
var leerCHAT []interface{}
but I am going through crazy hoops to get to any point on that map inside map and inside map crazyness, specially because some results are different content.
this is the Json
[
null,
null,
"hub:zWXroom",
"presence_diff",
{
"joins":{
"f718a187-6e96-4d62-9c2d-67aedea00000":{
"metas":[
{
"context":{},
"permissions":{},
"phx_ref":"zNDwmfsome=",
"phx_ref_prev":"zDMbRTmsome=",
"presence":"lobby",
"profile":{},
"roles":{}
}
]
}
},
"leaves":{}
}
]
I need to get to profile then inside there is a "DisplayName" field.
so I been doing crazy hacks.. and even like this I got stuck half way...
First is an array so I can just do something[elementnumber]
then is when the tricky mapping starts...
SORRY about all the prints etc is to debug and see the number of elements I am getting back.
if leerCHAT[3] == "presence_diff" {
var id string
presence := leerCHAT[4].(map[string]interface{})
log.Printf("algo: %v", len(presence))
log.Printf("algo: %s", presence["joins"])
vamos := presence["joins"].(map[string]interface{})
for i := range vamos {
log.Println(i)
id = i
}
log.Println(len(vamos))
vamonos := vamos[id].(map[string]interface{})
log.Println(vamonos)
log.Println(len(vamonos))
metas := vamonos["profile"].(map[string]interface{}) \\\ I get error here..
log.Println(len(metas))
}
so far I can see all the way to the meta:{...} but can't continue with my hacky code into what I need.
NOTICE: that since the id after Joins: and before metas: is dynamic I have to get it somehow since is always just one element I did the for range loop to grab it.
The array element at index 3 describes the type of the variant JSON at index 4.
Here's how to decode the JSON to Go values. First, declare Go types for each of the variant parts of the JSON:
type PrescenceDiff struct {
Joins map[string]*Presence // declaration of Presence type to be supplied
Leaves map[string]*Presence
}
type Message struct {
Body string
}
Declare a map associating the type string to the Go type:
var messageTypes = map[string]reflect.Type{
"presence_diff": reflect.TypeOf(&PresenceDiff{}),
"message": reflect.TypeOf(&Message{}),
// add more types here as needed
}
Decode the variant part to a raw message. Use use the name in the element at index 3 to create a value of the appropriate Go type and decode to that value:
func decode(data []byte) (interface{}, error) {
var messageType string
var raw json.RawMessage
v := []interface{}{nil, nil, nil, &messageType, &raw}
err := json.Unmarshal(data, &v)
if err != nil {
return nil, err
}
if len(raw) == 0 {
return nil, errors.New("no message")
}
t := messageTypes[messageType]
if t == nil {
return nil, fmt.Errorf("unknown message type: %q", messageType)
}
result := reflect.New(t.Elem()).Interface()
err = json.Unmarshal(raw, result)
return result, err
}
Use type switches to access the variant part of the message:
defer ws.Close()
for {
_, data, err := ws.ReadMessage()
if err != nil {
log.Printf("Read error: %v", err)
break
}
v, err := decode(data)
if err != nil {
log.Printf("Decode error: %v", err)
continue
}
switch v := v.(type) {
case *PresenceDiff:
fmt.Println(v.Joins, v.Leaves)
case *Message:
fmt.Println(v.Body)
default:
fmt.Printf("type %T not handled\n", v)
}
}
Run it on the playground.
What's the most idiomatic way to check error messages? My use case is that in err := os.Remove(path), I consider a success either:
A) if err == nil
or
B) if err != nil but the error is thrown because the file was not found.
any other error should cause the removal to retry. Currently I've wrapped this in a for { ... } loop and am checking:
if err == nil || strings.Contains(err.Error(), "no such file") {
// Success
} else {
// Fail
}
Since the docs say:
If there is an error, it will be of type *PathError.
I don't think there's a way to check by type assertion. Am I missing something fundamental? My error handling in Go has always felt somewhat slapdash.
I just dealt with this the other day. An error from os.Remove() will be syscall.ENOENT if the file did not exist.
So you can use logic like this:
if err != nil {
e, ok := err.(*os.PathError)
if ok && e.Err == syscall.ENOENT {
// The file didn't exist
w.WriteHeader(http.StatusNotFound)
return
} else {
// Error other than syscall.ENOENT
}
}
Of course, as shown in another answer, os.IsNotExist() is simple and idiomatic. Wish I'd seen that before today.
The "type" error is an interface. Interfaces don't have an concrete type. To get the type of the value you could use a type assertion or a type switch:
// Type assertion
_, ok := err.(*PathError)
// Type switch
switch err.(type) {
case *PathError:
// You know the type now
}
This is an idiomatic way to find out of which type an error is. As in the comments specified there is already a function inside the os package which does this for you (https://golang.org/pkg/os/#IsNotExist)