Why I love gjson
Why I love gjson
Easy parsing. That’s it. That’s the reason.
I have these structs
type ApiResponse struct {
Message string `json:"message"`
Status string `json:"status"`
Code int `json:"code"`
Return int `json:"return"`
}
type SystemDNSResponse struct {
Data *pfsense.DNS `json:"data"`
ApiResponse
}
When I make a request to the client I’ll get a response that looks something like this:
{
"status": "ok",
"code": 200,
"return": 0,
"message": "Success",
"data": {
"dnsserver": [
"1.1.1.1",
"8.8.8.8"
],
"dnsallowoverride": false,
"dnslocalhost": true
}
}
I can marshall this into that struct easily using the built in encoding/json
package.
But, I want a generic methods which can be passed in a struct and do the unmarshalling further down the line.
This is where gjson
really shines; it can inspect []byte
and output the string which I can
then do something with. I don’t need any struct just the field name I want.
For example, I care about the code
field. And have functions which will return errors based on its
value:
// checkRawJsonStatusCode ensures that any non-200 status codes return an error
// from the firewall. The response from the firewall is not a http/net object
// so, we must manually inspect the code int and derive their meaning here.
func (s *pfsensesrvc) checkRawJsonStatusCode(code int) error {
switch {
case code >= 200 && code <= 299:
break
default:
return fmt.Errorf("failed client validation of pfsense api: %d", code)
}
return nil
}
This function takes a string which would require unmarshalling to extract.
With gjson
I can pass in the []byte
and grab it out easily, like this:
// apiResponseCode returns the pfSense-api 'code' value from a successful API
// call to the client API.
func apiResponseCode(jsn []byte) int {
const key = "code"
c := gjson.GetBytes(jsn, key)
return int(c.Num)
}
// MarshallAPIResponse parses weakly typed responses from the client device and marshall's
// it into a struct.
func (s *pfsensesrvc) MarshallAPIResponse(b []byte, result any) error {
// truncated the rest of this method
err = s.checkRawJsonStatusCode(apiResponseCode(b))
if err != nil {
return err
}
return nil
}
You can see that I grab the code
field as an int
which is passed in as an argument to
checkRawJsonStatusCode
.
I do it this way because in MarshallAPIResponse
I only have to pass in []byte
whereas
if I used encoding/json
I would need to be more explicit about the type.
Personally, I’ve found it to be invaluable, especially when creating an API which has many tens of endpoints, each returning their own types.
Tags:
#json #go #gjson