create array of struct from by decoding a JSON file in go - 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.

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

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.

How can I make my unmarshall function in golang handle multiple types?

I'm using the json.unmarshalling function in golang to decode some JSON responses we got from the API. How do I make it handle multiple types?
The response we receive are always status code and a message, but the json field have different names. Sometimes these two fields are called code and message and sometimes they are called statuscode and description, depending on what we query.
say that we queries Apple and this is simply solved by creating an Apple type struct like this:
type Apple struct {
Code int `json:"code"`
Description string `json:"message"`
}
But when we query Peach, the json we got back is no longer code and message anymore, the field names became statuscode and description. So we will need the following:
type Peach struct {
Code int `json:"statuscode"`
Description string `json:"description"`
}
Potentially, we need to set up 50 more types and write duplicate for 50 times?? There MUST be a better way to do this. Unfortunately I'm new to Golang and don't know how polymorphism works in this language. Please help.
As far as I know , You should always decode into structs to benefit from go static types , the methods attached to that struct and perhaps be able to validate your responses with a package like validator , but you could always parse the JSON body into a map like this :
// JsonParse parses the json body of http request
func JsonParse(r *http.Request) (map[string]interface{}, error) {
// Read the r.body into a byte array
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return nil, err
}
// Make a map of String keys and Interface Values
b := make(map[string]interface{})
// Unmarshal the body array into the map
err = json.Unmarshal(body, &b)
if err != nil {
return nil, err
}
return b, nil
}

Chaincode GetState returns an empty response

func (t *ballot) initBallot(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 2 {
return shim.Error("Incorrect number of arguments. Expecting 2")
}
// ==== Input sanitation ====
fmt.Println("- start init ballot")
if len(args[0]) == 0 {
return shim.Error("1st argument must be a non-empty string")
}
if len(args[1]) == 0 {
return shim.Error("2nd argument must be a non-empty string")
}
personFirstName := args[0]
personLastName := args[1]
hash := sha256.New()
hash.Write([]byte(personFirstName + personLastName)) // ballotID is created based on the person's name
ballotID := hex.EncodeToString(hash.Sum(nil))
voteInit := "VOTE INIT"
// ==== Create ballot object and marshal to JSON ====
Ballot := ballot{personFirstName, personLastName, ballotID, voteInit}
ballotJSONByte, err := json.Marshal(Ballot)
if err != nil {
return shim.Error(err.Error())
}
err = stub.PutState(string(ballotID), ballotJSONByte)
//FIXME:0-------------------------------------------------
ballotAsByte, err := stub.GetState(string(ballotID))
if err != nil {
return shim.Error(err.Error())
}
BBBallot := ballot{}
//umarshal the data to a new ballot struct
json.Unmarshal(ballotAsByte, &BBBallot)
//
fmt.Println(BBBallot)
fmt.Println(BBBallot.personFirstName)
return shim.Success([]byte(ballotID))
}
Above is the code and this is the test script i am running it against
func Test_Invoke_initBallot(t *testing.T) {
scc := new(ballot)
stub := shim.NewMockStub("voting", scc)
res := stub.MockInvoke("1", [][]byte{[]byte("initBallot"), []byte("John"), []byte("C")})
if res.Status != shim.OK {
t.Log("bad status received, expected: 200; received:" + strconv.FormatInt(int64(res.Status), 10))
t.Log("response: " + string(res.Message))
t.FailNow()
}
if res.Payload == nil {
t.Log("initBallot failed to create a ballot")
t.FailNow()
}
}
I am trying to read from the ledger after putting the transaction in. However, I have been getting empty responses from both of the Println statements.
// PutState puts the specified `key` and `value` into the transaction's
// writeset as a data-write proposal. PutState doesn't effect the ledger
// until the transaction is validated and successfully committed.
// Simple keys must not be an empty string and must not start with null
// character (0x00), in order to avoid range query collisions with
// composite keys, which internally get prefixed with 0x00 as composite
// key namespace.
PutState(key string, value []byte) error
It does say on the documentation that putState does not commit transactions to the ledger until its validated, but I am just trying to test my chaincode using the MockStub without setting up the fabric network. What is the fix to this problem?
P.S the problem has been solved, here is the right way to set up a struct
type ballot struct {
PersonFirstName string
PersonLastName string
BallotID string
VoteInit string
}
You haven't provided the code for the ballot struct yet. But from what you provided, I have a guess what might be going on. I think you probably haven't exported the fields and your struct looks like this:
type ballot struct {
personFirstName string
personLastName string
ballotID string
voteInit string
}
But when you tried to convert this object to JSON using json.Marshal(Ballot), none of the fields are added to the JSON object because they were not exported. All that you have to do in this case is exporting the necessary fields (using Uppercase letter at the beginning of field names). Your updated struct should look something like the following:
type ballot struct {
PersonFirstName string
PersonLastName string
BallotID string
VoteInit string
}
This is a very common mistake many newcomers make. Wish you all the best in your journey forward!!!
P.S. Please edit your question and add the code of you ballot struct here even if this solution solves your problem as that might help others in the future. Also, please add proper indentation to the code and add the last } symbol in the code block.

How do I json.Unmarshal based on a type

I am writing a go client for the Flowdock API. Their API for Message has a number of attributes two of which are Event and Content
When Event = message then Content is a string. When Event = comment Content is a JSON object.
I want to defer unmarhsalling Content until it is needed. To do this I map RawContent in my struct and define a Content() method on Message to return the correct object.
Here is the code to illustrate:
package main
import (
"fmt"
"encoding/json"
)
// Message represents a Flowdock chat message.
type Message struct {
Event *string `json:"event,omitempty"`
RawContent *json.RawMessage `json:"content,omitempty"`
}
func (m *Message) Content() (content interface{}) {
// when m.Event is a message the RawContent is a string
// real code handles unmarshaling other types (removed for this example)
return string(*m.RawContent)
}
func main() {
var message = &Message{}
rawJSON := `{"event": "message", "content": "Howdy-Doo #Jackie #awesome"}`
if err := json.Unmarshal([]byte(rawJSON), &message); err != nil {
panic(err.Error())
}
event := "message"
rawMessage := json.RawMessage("Howdy-Doo #Jackie #awesome")
want := &Message{
Event: &event,
RawContent: &rawMessage,
}
fmt.Println(message.Content(), want.Content())
}
The result of running this is: http://play.golang.org/p/eds_AA6Aay
"Howdy-Doo #Jackie #awesome" Howdy-Doo #Jackie #awesome
Note: message.Content() and want.Content() are different!
At first I did not expect the quotes to be included in message but it makes sense because of how the JSON is parsed. It is just a slice of the whole rawJSON string.
So my questions are:
Should I just strip off the quotes?
If so what is the simplest way in Go to strip? maybe this: http://play.golang.org/p/kn8XKOqF0z lines(6, 19)?
Is this even the right approach to handling JSON that can have different types for the same attribute?
Here is a more complete example showing how I handle a JSON RawContent: http://play.golang.org/p/vrBJ5RYcql
Question 1:
No, you should json.Unmarshal the content also when it only contains a string. Apart from the quotes, JSON strings may also contain backslash-escaped control characters.
Question 2:
Because of the answer in Question 1, do the following for the "message" case:
var s string
if err := json.Unmarshal([]byte(*m.RawContent), &s); err != nil {
panic(err)
}
return s
Question 3:
It is a good approach to Unmarshal the event type string and use RawMessage to store the rest of the JSON until you've evaluated the type and know what kind of structure the content has.
You might want to consider also having a specific type also for just simple string contents, eg.:
type MessageContent string
This way you are free to implement methods on the type, allowing the Content method to return some other interface than just the empty interface{}.
Note:
Beware that, if you also json.Unmarshal the message string as I suggested, your Playground example will panic when trying to Unmarshal your non-quoted want.RawContent string, because it is not valid JSON.

Resources