Skip to content

Commit e1e20af

Browse files
committed
A minimal implementation with a lot of comments.
1 parent 673d41c commit e1e20af

File tree

1 file changed

+99
-0
lines changed

1 file changed

+99
-0
lines changed

jsondemo2/minimal.go

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package main
2+
3+
/*
4+
5+
The Go JSON module can't not access unexported fields in a struct. So
6+
how do you work with them?
7+
8+
This demonstrates the solution in http://choly.ca/post/go-json-marshalling/
9+
where we have a 2nd struct that embeds the primary struct but adds
10+
fields that will be used to expose the unexported fields. We then write
11+
MarshalJSON() and UnmarshalJSON() functions that do the right thing.
12+
13+
This also helps in situations where we have fields that require a custom
14+
format only in JSON.
15+
*/
16+
17+
import (
18+
"encoding/json"
19+
"fmt"
20+
"time"
21+
)
22+
23+
// Cranberry stores data.
24+
// Visible: This field is exported and JSON displays it as usual.
25+
// invisible: This field is unexported but we want it to be included in JSON.
26+
// Custom: This field has a custom output format. We store it as time.Time
27+
// but when it appears in JSON, it should be in Unix Epoch format.
28+
type Cranberry struct {
29+
Visible int `json:"visible"`
30+
invisible int // No tag here
31+
Custom time.Time `json:"-"` // Don't output this field (we'll handle it in CranberryJSON).
32+
}
33+
34+
// CranberryAlias is an alias of Cranberry. We use an alias because aliases
35+
// are stripped of any functions and we need a struct without
36+
// MarshalJSON/UnmarshalJSON defined, otherwise we'd get a recursive defintion.
37+
type CranberryAlias Cranberry
38+
39+
// CranberryJSON represents out we represent Cranberry to the JSON package.
40+
type CranberryJSON struct {
41+
*CranberryAlias // All the exported fields.
42+
Invisible int `json:"invisible"`
43+
CustomUnixEpoch int64 `json:"epoch"`
44+
// FYI: The json tags "invisble" and "epoch" can be any valid JSON tag.
45+
// It is all a matter of how we want the JSON to be presented externally.
46+
}
47+
48+
// MarshalJSON marshals a Cranberry. (struct to JSON)
49+
func (u *Cranberry) MarshalJSON() ([]byte, error) {
50+
return json.Marshal(&CranberryJSON{
51+
CranberryAlias: (*CranberryAlias)(u),
52+
// Unexported or custom-formatted fields are listed here:
53+
Invisible: u.invisible,
54+
CustomUnixEpoch: u.Custom.Unix(),
55+
})
56+
}
57+
58+
// UnmarshalJSON unmarshals a Cranberry. (JSON to struct)
59+
func (u *Cranberry) UnmarshalJSON(data []byte) error {
60+
temp := &CranberryJSON{
61+
CranberryAlias: (*CranberryAlias)(u),
62+
}
63+
if err := json.Unmarshal(data, &temp); err != nil {
64+
return err
65+
}
66+
67+
// Copy the exported fields:
68+
*u = (Cranberry)(*(temp).CranberryAlias)
69+
// Each unexported field must be copied and/or converted individually:
70+
u.invisible = temp.Invisible
71+
u.Custom = time.Unix(temp.CustomUnixEpoch, 0) // Convert while copying.
72+
73+
return nil
74+
}
75+
76+
func main() {
77+
var out []byte
78+
var err error
79+
80+
// Demonstration of marshalling: Marshal s (struct) to out ([]byte)
81+
fmt.Printf("Struct to JSON:\n")
82+
s := &Cranberry{Visible: 1, invisible: 2, Custom: time.Unix(1521492409, 0)}
83+
out, err = json.Marshal(s)
84+
if err != nil {
85+
panic(err)
86+
}
87+
fmt.Printf(" got=%v\n", string(out))
88+
fmt.Println(` expected={"visible":1,"invisible":2,"epoch":1521492409}`)
89+
90+
// Demonstration of how to unmarshal: Unmarshal "out" ([]byte) to n (struct)
91+
fmt.Printf("JSON to struct:\n")
92+
var n = &Cranberry{}
93+
err = json.Unmarshal(out, n)
94+
if err != nil {
95+
panic(err)
96+
}
97+
fmt.Printf(" got=%+v\n", n)
98+
fmt.Println(` expected=&{Visible:1 invisible:2 Custom:2018-03-19 xx:46:49 xxxxx xxx}`)
99+
}

0 commit comments

Comments
 (0)