How to omit empty json fields using json.decoder - go

I try to understand why both functions return the same output.
As far as I understood, the point of omit empty is to not add that key to the result struct.
I wrote this example, I was expecting the first output not to have the "Empty" key, but for some reason its value still shows as 0.
package main
import (
"encoding/json"
"fmt"
"strings"
)
type agentOmitEmpty struct {
Alias string `json:"Alias,omitempty"`
Skilled bool `json:"Skilled,omitempty"`
FinID int32 `json:"FinId,omitempty"`
Empty int `json:"Empty,omitempty"`
}
type agent struct {
Alias string `json:"Alias"`
Skilled bool `json:"Skilled"`
FinID int32 `json:"FinId"`
Empty int `json:"Empty"`
}
func main() {
jsonString := `{
"Alias":"Robert",
"Skilled":true,
"FinId":12345
}`
fmt.Printf("output with omit emtpy: %v\n", withEmpty(strings.NewReader(jsonString)))
// output with omit emtpy: {Robert true 12345 0}
fmt.Printf("output regular: %v\n", withoutEmpty(strings.NewReader(jsonString)))
// output without omit: {Robert true 12345 0}
}
func withEmpty(r *strings.Reader) agentOmitEmpty {
dec := json.NewDecoder(r)
body := agentOmitEmpty{}
err := dec.Decode(&body)
if err != nil {
panic(err)
}
return body
}
func withoutEmpty(r *strings.Reader) agent {
dec := json.NewDecoder(r)
body := agent{}
err := dec.Decode(&body)
if err != nil {
panic(err)
}
return body
}

You need to define Empty as *int so it will be replaced with nil when there is no value. Then it will not be saved in the database.

Related

How to unmarshal JSOn with an array of different types

I have JSON like this that I need to parse into a golang type:
{
name: "something"
rules: [
{
"itemTypeBasedConditions": [["containsAny", ["first_match", "second_match"]]],
"validity": "INVALID"
}]
}
The problem is that each array of the array in itemTypeBasedConditions contains a mix of strings (always first element) and another array (second element), and I am not sure how to parse all of that into an object that I could then manipulate.
I got to:
type RulesFile struct {
Name string
Rules []RulesItem
}
type RulesItem struct {
itemTypeBasedConditions [][]interface{}
validity bool
}
And then I guess I have to convert elements one by one from interface{} to either string (containsAny) or an array of strings ("first_match", "second_match")
Is there a better way of approaching this JSON parsing?
I would do something like this, you can probably alter this to your needs.
package main
import (
"encoding/json"
"fmt"
"os"
"reflect"
)
type RulesFile struct {
Name string `json:"name"`
Rules []RulesItem `json:"rules"`
}
type RulesItem struct {
ItemTypeBasedConditions [][]Condition `json:"itemTypeBasedConditions"`
Validity bool `json:"validity"`
}
type Condition struct {
Value *string
Array *[]string
}
func (c Condition) String() string {
if c.Value != nil {
return *c.Value
}
return fmt.Sprintf("%v", *c.Array)
}
func (c *Condition) UnmarshalJSON(data []byte) error {
var y interface{}
err := json.Unmarshal(data, &y)
if err != nil {
return err
}
switch reflect.TypeOf(y).String() {
case "string":
val := fmt.Sprintf("%v", y)
c.Value = &val
return nil
case "[]interface {}":
temp := y.([]interface{})
a := make([]string, len(temp))
for i, v := range temp {
a[i] = fmt.Sprint(v)
}
c.Array = &a
return nil
}
return fmt.Errorf("cannot unmarshall into string or []string: %v", y)
}
var input string = `
{
"name": "something",
"rules": [
{
"itemTypeBasedConditions": [["containsAny",["first_match", "second_match"]]],
"validity": false
}
]
}`
func main() {
var ruleFile RulesFile
err := json.Unmarshal([]byte(input), &ruleFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("%+v\n", ruleFile)
}
You can implement the json.Unmarshaler interface. Have that implementation first unmarshal the json into a slice of json.RawMessage, then, once you've done that, you can unmarshal the individual elements to their corresponding types.
type Cond struct {
Name string
Args []string
}
func (c *Cond) UnmarshalJSON(data []byte) error {
// unmarshal into a slice of raw json
var raw []json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return err
} else if len(raw) != 2 {
return errors.New("unsupported number of elements in condition")
}
// unmarshal the first raw json element into a string
if err := json.Unmarshal(raw[0], &c.Name); err != nil {
return err
}
// unmarshal the second raw json element into a slice of string
return json.Unmarshal(raw[1], &c.Args)
}
https://go.dev/play/p/-tbr73TvX0d

Reading and Unmarshalling API results in Golang

In the below program I'm extracting some data from an API.
It outputs a rather complex data.
When I ioutil.ReadAll(resp.Body), the result is of type []uint8.
If I try to read the results, its just a random array of integers.
However, I'm able to read it if I convert it to string using string(diskinfo)
But I want to use this in a Struct and having trouble unmarshalling.
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"reflect"
)
type ApiResults struct {
results []struct {
statement_id int `json.statement_id`
series []struct {
name string `json.name`
tags struct {
host string `json.host`
}
columns []string `json.columns`
values []interface{} `json.values`
}
}
}
func main() {
my_url := "my_url"
my_qry := fmt.Sprintf("my_query")
resp, err := http.Get(my_url + url.QueryEscape(my_qry))
if err != nil {
fmt.Printf("ERROR: %v\n", err)
} else {
fmt.Println(reflect.TypeOf(resp))
diskinfo, _ := ioutil.ReadAll(resp.Body)
fmt.Println(reflect.TypeOf((diskinfo)))
fmt.Println(diskinfo)
fmt.Println(string(diskinfo))
diskinfo_string := string(diskinfo)
data := ApiResults{}
json.Unmarshal([]byte(diskinfo_string), &data)
//fmt.Printf("Values = %v\n", data.results.series.values)
//fmt.Printf("Server = %v\n", data.results.series.tags.host)
}
}
If I view the data as a string, I get this (formatted):
{"results":[
{"statement_id":0,
"series":[
{"name":"disk",
"tags":{"host":"myServer1"},
"columns":["time","disk_size"],
"values":[["2021-07-07T07:53:32.291490387Z",1044]]},
{"name":"disk",
"tags":{"host":"myServer2"},
"columns":["time","disk_size"],
"values":[["2021-07-07T07:53:32.291490387Z",1046]]}
]}
]}
I think my Apireturn struct is also structured incorrectly because the API results have info for multiple hosts.
But first, I doubt if the data has to be sent in a different format to the struct. Once I do this, I can probably try to figure out how to read from the Struct next.
The ioutil.ReadAll already provides you the data in the type byte[]. Therefore you can just call json.Unmarshal passing it as a parameter.
import (
"encoding/json"
"io/ioutil"
"net/http"
)
func toStruct(res *http.Response) (*ApiResults, error) {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
defer res.Body.Close()
data := ApiResults{}
if err := json.Unmarshal(body, &data); err != nil {
return nil, err
}
return data, nil
}
There also seems to be an issue with your struct. The correct way to use struct tags is as follows. Plus, fields need to be exported for the json tag (used by json.Umarshal) to work – starting with uppercase will do it.
type ApiResults struct {
Results []struct {
StatementId int `json:"statement_id"`
Series []struct {
Name string `json:"name"`
Tags struct {
Host string `json:"host"`
} `json:"tags"`
Columns []string `json:"columns"`
Values []interface{} `json:"values"`
} `json:"series"`
} `json:"results"`
}

Problem with Marshal/unMarshal when key of map is a struct

I defined a struct named Student and a map named score.
Data structure is shown below:
type Student struct {
CountryID int
RegionID int
Name string
}
stu := Student{111, 222, "Tom"}
score := make(map[Student]int64)
score[stu] = 100
i am using json.Marshal to marshal score into json, but i cannot use json.Unmarshal to unmarshal this json. Below is my code. i am using function GetMarshableObject to translate struct Student into string which is marshable.
Could anyone tell me how to deal with this json to unmarshal it back to map score.
package main
import (
"encoding/json"
"fmt"
"os"
"reflect"
)
type Student struct {
CountryID int
RegionID int
Name string
}
func GetMarshableObject(src interface{}) interface{} {
t := reflect.TypeOf(src)
v := reflect.ValueOf(src)
kind := t.Kind()
var result reflect.Value
switch kind {
case reflect.Map:
//Find the map layer count
layer := 0
cur := t.Elem()
for reflect.Map == cur.Kind() {
layer++
cur = cur.Elem()
}
result = reflect.MakeMap(reflect.MapOf(reflect.TypeOf("a"), cur))
for layer > 0 {
result = reflect.MakeMap(reflect.MapOf(reflect.TypeOf("a"), result.Type()))
layer--
}
keys := v.MapKeys()
for _, k := range keys {
value := reflect.ValueOf(GetMarshableObject(v.MapIndex(k).Interface()))
if value.Type() != result.Type().Elem() {
result = reflect.MakeMap(reflect.MapOf(reflect.TypeOf("a"), value.Type()))
}
result.SetMapIndex(reflect.ValueOf(fmt.Sprintf("%v", k)), reflect.ValueOf(GetMarshableObject(v.MapIndex(k).Interface())))
}
default:
result = v
}
return result.Interface()
}
func main() {
stu := Student{111, 222, "Tom"}
score := make(map[Student]int64)
score[stu] = 100
b, err := json.Marshal(GetMarshableObject(score))
if err != nil {
fmt.Println("error:", err)
}
os.Stdout.Write(b) //{"{111 222 Tom}":100}
scoreBak := make(map[Student]int64)
if err = json.Unmarshal(b, &scoreBak); nil != err {
fmt.Println("error: %v", err) // get error here: cannot unmarshal object into Go value of type map[main.Student]int64
}
}
From the docs:
The map's key type must either be a string, an integer type, or
implement encoding.TextMarshaler.
func (s Student) MarshalText() (text []byte, err error) {
type noMethod Student
return json.Marshal(noMethod(s))
}
func (s *Student) UnmarshalText(text []byte) error {
type noMethod Student
return json.Unmarshal(text, (*noMethod)(s))
}
As an example I'm using encoding/json to turn a Student value into a json object key, however that is not required and you can choose your own format.
https://play.golang.org/p/4BgZn4Y37Ww

How to handle Response JSON have custom field with out key?

Query Api and response a custom JSON, how to Unmarshal it. the sample JSON:
{"14AcKEr19gHJvgwQhK7sfFm6YJGmoZZoqu": {
"final_balance": 61914248289,
"n_tx": 3472,
"total_received": 3479994002972
}}
The key is a hex string. So how to handle it with golang convention, anyone can help me?
Below is my try test code:
c.OnResponse(func(r *colly.Response) {
jsonData := r.Body
fmt.Println(string(jsonData))
fmt.Println("==================")
//parse bitcoin json
jsonMap := make(map[string]interface{})
err := json.Unmarshal([]byte(jsonData), &jsonMap)
if err != nil {
panic(err)
}
fmt.Println(jsonMap)
dumpMap("", jsonMap)
})
func dumpMap(space string, m map[string]interface{}) {
for k, v := range m {
if mv, ok := v.(map[string]interface{}); ok {
fmt.Printf("{ \"%v\": \n", k)
dumpMap(space+"\t", mv)
fmt.Printf("}\n")
} else {
fmt.Printf("%v %v : %v\n", space, k, v)
}
}
}
and go run cmd/main.go, the console is print here:
{"14AcKEr19gHJvgwQhK7sfFm6YJGmoZZoqu": {
"final_balance": 75494521080,
"n_tx": 3493,
"total_received": 3493574275763
}}
==================
map[14AcKEr19gHJvgwQhK7sfFm6YJGmoZZoqu:map[n_tx:3493 total_received:3.493574275763e+12 final_balance:7.549452108e+10]]
{ "14AcKEr19gHJvgwQhK7sfFm6YJGmoZZoqu":
final_balance : 7.549452108e+10
n_tx : 3493
total_received : 3.493574275763e+12
}
Do I need customised unmarshal func to get string key? If I use 14AcKEr19gHJvgwQhK7sfFm6YJGmoZZoqu as key I can't easily to access. I just want to know how handle it.
you can unmarshal it into map, so you can get generated key as a key of map
https://play.golang.org/p/IfEjjvKakpu
package main
import (
"encoding/json"
"fmt"
"log"
)
var input = `{"14AcKEr19gHJvgwQhK7sfFm6YJGmoZZoqu": {
"final_balance": 61914248289,
"n_tx": 3472,
"total_received": 3479994002972
}}`
type object struct {
FinalBalance uint64 `json:"final_balance"`
NTX uint64 `json:"n_tx"`
TotalReceived uint64 `json:"total_received"`
}
func main() {
var result map[string]object;
err := json.Unmarshal([]byte(input), &result);
if err != nil {
log.Fatal(err)
}
fmt.Printf("result: %+v", result)
// result: map[14AcKEr19gHJvgwQhK7sfFm6YJGmoZZoqu:{FinalBalance:61914248289 NTX:3472 TotalReceived:3479994002972}]
}

golang type conversion in unmarshaljson

Could someone help me please what's going wrong here? For some reason the output are not the same and I don't get why.
type rTime time.Time
func (rt *rTime) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
t, err := time.Parse("2006-01-02", s)
if err != nil {
return err
}
log.Println(t)
*rt = rTime(t)
log.Println(*rt)
return nil
}
Log looks like this:
2014/09/18 04:31:35 1999-10-15 00:00:00 +0000 UTC
2014/09/18 04:31:35 {63075542400 0 0x933ea0}
Why's the conversion not working? The input string is 1995-10-15 btw.
The conversion is working, but fmt.Println() looks for a String() method, and that exists on time.Time but not on your type. You should need nothing more than func (rt rTime) String() string { return time.Time(rt).String() } to direct String() calls back to time.Time's implementation.
Here's an example:
package main
import (
"log"
"time"
)
type rTime time.Time
func (rt rTime) String() string { return time.Time(rt).String() }
func main() {
s := "1999-10-15"
t, err := time.Parse("2006-01-02", s)
if err != nil {
panic(err)
}
log.Println(t)
rt := rTime(t)
log.Println(rt)
}
Note that I treated both time types as values because the standard library does, per the canonical advice to avoid pointers for tiny structs with value semantics.
Maybe more interesting, you can use type embedding to automagically pick up all of the methods of time.Time except any you override. The syntax changes slightly (see on Playground):
package main
import (
"log"
"time"
)
type rTime struct { time.Time }
func main() {
s := "1999-10-15"
t, err := time.Parse("2006-01-02", s)
if err != nil {
panic(err)
}
log.Println(t)
rt := rTime{t}
log.Println(rt)
}
If you've used embedding and want to write your own custom methods that "proxy through" to the embedded type's, you use a syntax like obj.EmbeddedTypeName.Method, which could be like, for instance, rt.Time.String():
// a custom String method that adds smiley faces
func (rt rTime) String() string { return "😁 " + rt.Time.String() + " 😁" }
obj.EmbeddedTypeName is also how you (for example) access operators on non-struct types that you've embedded.

Resources