Decoding JSON object and mapping the values to a struct in Go - go

I have created a webserver using the library net http. My problem is that I want to create a Struct like below from a JSON object, whose keys names don't match with the struct attributes.
type JsonData struct {
Operation string
IsStaff int
}
The JSON object sent from the server is:
{
"function": "search",
"is_staff": 1
"description": "Test"
}
Most of the solutions I have found are creating another struct, where the JSON keys and struct attribute names match each other. Is there a way to map the JSON decoded to my JsonData struct? Below it is my current function right now.
func handler(w http.ResponseWriter, r *http.Request){
switch r.Method {
case http.MethodPost:
var data JsonData
err := json.NewDecoder(r.Body).Decode(&data)
}
}

Search for "tag" in the json.Marshal documentation.
Basically, you can tag struct fields with special annotations that tell the default un/marshaler to use the given name instead of the (case-insensitive) name of the struct field.
For your struct you can likely just do the following, depending on your actual intent:
type JsonData struct {
Operation string `json:"function"`
IsStaff int `json:"is_staff"`
}
func main() {
jsonstr := `{"function":"search","is_staff":1,"description":"Test"}`
var jd JsonData
err := json.Unmarshal([]byte(jsonstr), &jd)
fmt.Printf("OK: jd=%#v, err=%v\n", jd, err)
// OK: jd=main.JsonData{Operation:"search", IsStaff:1}, err=<nil>
}

Related

Json unmarshal struct to map is ignoring zero values of struct

I am trying to convert a struct to map using following method
func ConvertStructToMap(in interface{}) map[string]interface{} {
fmt.Println(in)
var inInterface map[string]interface{}
inrec, _ := json.Marshal(in)
json.Unmarshal(inrec, &inInterface)
return inInterface
}
The problem is when I am trying to convert a struct
type Data struct{
Thing string `json:"thing,omitempty"`
Age uint8 `json:"age,omitempty"`
}
With this data
c:=Data{
Thing :"i",
Age:0,
}
it just gives me the following output map[things:i] instead it should give the output
map[things:i,age:0]
And when I don't supply age
like below
c:=Data{
Thing :"i",
}
Then it should give this output map[things:i] .I am running an update query and the user may or may not supply the fields ,any way to solve it .I have looked over the internet but couldn't get my head on place
Edit -
I am sending json from frontend which gets converted to struct with go fiber Body parser
if err := c.BodyParser(&payload); err != nil {
}
Now If I send following payload from frontend
{
thing:"ii"
}
the bodyParser converts it into
Data{
Thing :"ii",
Age :0
}
that's why I have used omitempty such that bodyParser can ignore the field which are not present
omitempty ignore when convert from struct to json
type Response struct {
Age int `json:"age,omitempty"`
Name string `json:"name,omitempty"`
}
resp:=Response {
Name: "john",
}
data,_:=json.Marshal(&resp)
fmt.Println(string(data)) => {"name": "john"}
In you case, you convert from json to struct, if attribute not present, it will be fill with default value of data type, in this case is 0 with uint8

Cannot convert struct to different type with same underlying fields

type Req struct {
apiVersion string
path string
resourceEndpoint string
accessKey string
log *logrus.Entry
incomingReq interface{}
httpClient lib.HTTPClient
redisClient redis.Cmdable
ctx context.Context
}
type TestReq struct {
Req
}
According to this this question and its answers, I feel like I should be able to do the following:
req := &Req{}
req = TestReq(req)
But I get this error in VsCode:
cannot convert req (variable of type *Req) to TestReq compiler(InvalidConversion)
Don't these two structs have the same underlying fields? If so, why can't the first be converted into the second?
Don't these two structs have the same underlying fields?
No, they don't. Req has several fields, TestReq has a single field of type Req so they are not convertible into each other. Embedding does not "copy" the fields of the embedded type to the embedder type. Embedding adds a single field which can be referred to by the unqualified type name of its type.
The use of embedding is not to automatically "copy" the fiels, but rather to have them "promoted", also promoting the methods of the embedded type.
If you have a value of type TestReq, you may use the unqualified type name Req to refer to the embedded field, so you may do something like this:
var tr TestReq
var r Req
r = tr.Req // Valid
tr.Req = r // This is also valid
The above operations (statements) are assignments and as such, they copy the whole Req struct value. If you want to avoid that, you may embed a pointer, for example:
type TestReq struct {
*Req
}
And then the following assignments will only copy a pointer value:
var tr = &TestReq{Req: &Req{}}
var r *Req
r = tr.Req // Valid
tr.Req = r // This is also valid
(Note: tr itself may or may not be a pointer here, it doesn't matter.)
Based on the suggestion icza, using the type name req to assign value to embedded field.Here is a simple code for the same , for simplicity I converted redis,logrus,context and http types as interface{}
package main
import (
"fmt"
)
type Req struct {
apiVersion string
path string
resourceEndpoint string
accessKey string
log interface{}
incomingReq interface{}
httpClient interface{}
redisClient interface{}
ctx interface{}
}
type TestReq struct {
Req
}
func main() {
req1 := Req{"api01", "c:/doc/folder", "endkey", "ackey", "logs", [2]float64{2.0, 7.88}, "http", "redis", "ctx"}
fmt.Println("ReqObject",req1)
var testReq TestReq
testReq.Req = req1
fmt.Println("TestReqObject",testReq)
}
Output:
ReqObject {api01 c:/doc/folder endkey ackey logs [2 7.88] http redis ctx}
TestReqObject {{api01 c:/doc/folder endkey ackey logs [2 7.88] http redis ctx}}

Parsing JSON using struct

I'm trying to parse JSON using Go. Can anyone tell me why my code is not working as expected?
package main
import (
"encoding/json"
"fmt"
)
type Message struct {
Name string
Body string
Time int64
}
type Person struct {
M Message
}
func get_content() {
body := []byte(`{"person":{"Name":"Alice","Body":"Hello","Time":1294706395881547000}}`)
var data Person
err := json.Unmarshal(body, &data)
if err != nil {
panic(err.Error())
}
fmt.Printf("%v",data.M.Name)
}
func main() {
get_content()
}
I'm expecting it to print the Name.
Go playground Code
There are two problems in the code.
The first one is what #umar-hayat mentioned above -> you are unmarshalling into the data object and you should be aiming at the data.M field.
The second problem is that your JSON's structure doesn't match your struct's structure. Your Person has a single field called M. If we want to represent this as JSON it would look like this:
{
"M": {
"Name": "Joe",
"Body": "Hi",
"time": 2600
}
}
Instead, you have a field called person in your JSON which cannot be matched to any field in your struct. The fact that it's similar to the name of the struct's type doesn't help in any way, I'm afraid.
So, you can either change your JSON and your target:
body := []byte(`{"Name":"Alice","Body":"Hello","Time":1294706395881547000}`)
var data Person
err := json.Unmarshal(body, &data.M)
Or just your JSON:
body := []byte(`{"M":{"Name":"Alice","Body":"Hello","Time":1294706395881547000}}`)
var data Person
err := json.Unmarshal(body, &data)
But it's essential that the names of the fields in your JSON match the names of the fields in your struct. Or, as mentioned by Konstantinos, you can use tags in order to specify particular names with which your struct's fields will be represented in the JSON.
You might find this helpful: https://gobyexample.com/json
Here is how to Unmarshel JSON to the struct. you can check it on Go Playground here:
package main
import (
"encoding/json"
"fmt"
)
type Message struct {
Name string
Body string
Time int64
}
type Person struct {
M Message
}
func get_content() {
body := []byte(`{"Name":"Alice","Body":"Hello","Time":1294706395881547000}`)
var data Person
err := json.Unmarshal(body, &data.M)
if err != nil {
panic(err.Error())
}
fmt.Printf(data.M.Name)
}
func main() {
get_content()
}
Replace data with data.M in below line.
err := json.Unmarshal(body, &data)
As long as you intent to map Json keys on structs whose fields have different names you should add tags:
type Message struct {
Name string `json:"Name"`
Body string `json:"Body"`
Time int64 `json:"Time"`
}
type Person struct {
M Message `json:"person"`
}
You can find more information here
In addition this answer explains in an nutshell the purpose of tags in go.

create array of struct from by decoding a JSON file in go

All I looking to do is to create an array of struct Response from a json encoded file.
the file that contains json data looks like this.
cat init.txt
{"events": [{"action":"cpr","id":69,"sha1":"abc","cpr":"cpr_data0"},{"action":"cpr","id":85,"sha1":"def","cpr":"cpr_data1"}]}
The way I have gone about approaching this is
I created a response of type map[string][]Response
.. decoded the JSON from the file
.. created a responseStruct of type []Response
But somehow when I inspect the Value they all look 0 or empty
map[events:[{ 0 } { 0 }]
What is wrong with the approach mentioned above.
type Response struct {
action string `json:"action"`
id int64 `json:"id"`
sha1 string `json:"sha1"`
cpr string `json:"cpr"`
}
func main() {
file, err := os.Open("init.txt")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
var response map[string][]Response
err = json.NewDecoder(file).Decode(&response)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
var responseArray []Response
responseArray = response["events"]
for _, responseStruct := range responseArray {
fmt.Println("id =", responseStruct.id)
fmt.Println("action =", responseStruct.action)
fmt.Println("sha1 = ", responseStruct.sha1)
fmt.Println("cpr =", responseStruct.cpr)
fmt.Println("==========")
}
fmt.Println(response)
}
Well If I modify the struct to look like this it works
type Response struct {
Action string `json:"action"`
ID int64 `json:"id"`
Sha1 string `json:"sha1"`
Cpr string `json:"cpr"`
}
So my question is this how the stuff would work, can't I get the above code to work in the way it is?
Go has the notion of public and private fields in objects, and the only distinction is whether they start with an initial capital letter or not. This applies to not just code modules but also the reflect package and things that use it. So in your initial version
type Response struct {
action string `json:"action"`
...
}
nothing outside your source package, not even the "encoding/json" module, can see the private fields, so it can't fill them in. Changing these to public fields by capitalizing Action and the other field names makes them visible to the JSON decoder.
Lowercase struct elements in golang are private, Hence json decoder (which is an external package) can't access them.
Its able to create the struct object but not able to set values. They appear zero because they 0 is default value.

How to create a reusable object that embeds different other objects?

I have many different models:
type objectModel struct {
Title string `json:"title"`
Body string `json:"body"`
}
// Many more models...
These models are used to create response objects that are returned to clients. All responses must contain a boolean OK value; other fields depend on the context.
type objectResponse struct {
OK bool `json:"ok"`
Object *objectModel `json:"object"`
}
type objectListResponse struct {
OK bool `json:"ok"`
Objects []*objectModel `json:"objects"`
}
// Many more response types that are similar to these two examples.
Problem
What I want to have is a reusable response object that embeds all these different custom response objects objectResponse, objectListResponse and so on. In this case I wouldn't need to define OK bool on every response object I have. I would use it like this: response.write(responseWriter), but that's out of the scope of this question.
type response struct {
OK bool `json:"ok"`
CustomResponse
}
It should be possible with an interface, but I don't know what common method all of the responses should implement.
What I want to have is a reusable response object that embeds all these different custom response objects objectResponse, objectListResponse and so on. In this case I wouldn't need to define OK bool on every response object I have.
What about doing the opposite: have a reusable Response object that is embedded in your custom response objects? That would allow you to not define OK bool on every response object (your stated goal), you would just embed the Response struct in each one. If there are other repeated fields you could use the same embedded struct for those as well, or another struct if they only belong in some custom responses.
Here's a simplified working example:
package main
import(
"fmt"
"encoding/json"
)
type Response struct {
OK bool `json:"ok"`
}
type lookResponse struct {
Response
Color string `json:"color"`
Shape string `json:"shape"`
}
func main() {
b := []byte(`{"ok":true,"color":"blue","shape":"circle"}`)
var r lookResponse
err := json.Unmarshal(b, &r)
if err != nil {
fmt.Printf("Error: %s\n", err)
}
fmt.Printf("ok: %v, color: %v, shape: %v\n", r.OK, r.Color, r.Shape)
}
When run that prints:
ok: true, color: blue, shape: circle
You can read more about embedded fields in the Go Language Specification, e.g.:
A field declared with a type but no explicit field name is called an
embedded field... A field or method f of an embedded field in a struct
x is called promoted if x.f is a legal selector that denotes that
field or method f. Promoted fields act like ordinary fields of a
struct except that they cannot be used as field names in composite
literals of the struct.
If that doesn't solve your issue, please clarify your goals/question.
I would use an interface{} there. For example:
type response struct {
OK bool `json:"ok"`
Object interface{} `json:"object"`
}
Now when I want to use I can put any other types there:
res := response{
OK: true,
Object: varOfCustomObjectModel,
}
Here is a response object that I used for one of my project:
https://github.com/leninhasda/gitpull-me/blob/master/api/response.go
Use case:
res := &response{
StatusCode: http.StatusOK,
Data: map[string]interface{}{
"product_title": "Awesome Product",
"price": 188.99,
"quantity": 1,
"meta": map[string]interface{}{
"height": 10,
"width": 15,
},
},
// or as simple as just
// Data: "hello world!"
}
res.json(w) // w is http.ResponseWriter

Resources