Cách xử lí JSON trong Golang
I. Lời nói đầu
Việc parsing JSON trong ngôn ngữ static như Golang sẽ gặp phải chút vấn đề. Giả sử ta có dữ liệu JSON, trong JSON đó có 1 key và 1 value, nhưng value đó có thể lồng 1 cặp key và 1 value khác và cứ như vậy. Vậy thì compiler của 1 ngôn ngữ static như Golang làm thế nào có thể làm việc được với JSON đó?
II. Unmarshaling và Marshaling
Nếu trường hợp ta biết trước được cấu trúc của JSON đó như thế nào thì giải pháp chính là ta sẽ tạo 1 struct
có cấu trúc tương ứng với cấu trúc của JSON đó, nếu JSON đó có thêm key nào khác thì chỉ việc bỏ qua key đó.
1. Parsing từ JSON sang struct (Unmarshaling)
type App struct {
Id string `json:"id"`
Title string `json:"title"`
}
data := []byte(`
{
"id": "k34rAT4",
"title": "My Awesome App"
}
`)
var app App
err := json.Unmarshal(data, &app)
2. Parsing từ struct sang JSON (Marshal)
data, err := json.Marshal(app)
III. Struct Tags
Có thể bạn sẽ nhận ra rằng ở ví dụ trên struct của chúng ta có thêm các tags json ở bên phải, đó chính là các manh mối để giúp có việc thực hiện parsing diễn ra theo đúng như mong đợi.
1. Field name:
Có thể bạn đã biết rằng việc viết hoa chữ cái đầu tiên của 1 field name của 1 struct mang ý nghĩa field đó là public
, tương tự viết thường là private
. Nhưng dữ liệu từ JSON thì rất hiếm khi các key được viết hoa. Do do có sự khác việt đó nên ta cần cho thêm các manh mối cho việc parsing và giải pháp chính là các tags
.
Dưới đây là ví dụ đơn giản về việc viết thêm tag
cho struct
để phục vụ cho việc parsing JSON
type MyStruct struct {
SomeField string `json:"some_field"`
}
2. Parsing từ struct sang JSON, làm gì khi 1 field có giá trị empty (zero value)?
Zero value
cho kiểu dữ liệu string
sẽ là empty string
, cho số là 0
, cho map
, slice, pointer
là nil
.
type MyStruct struct {
SomeField string `json:"some_field"`
}
Nếu some_field == ""
thì với tag
như bên trên JSON kết quả sẽ là {"some_field": ""}
Tuy nhiên đôi khi đó không thực là những gì ta mong muốn, nếu ta muốn bỏ qua field
bị empty
để nó không xuất hiện trong JSON thì ta sẽ dùng đến omitempty
type MyStruct struct {
SomeField string `json:"some_field,omitempty"`
}
- Skipping fields
type App struct {
Id string `json:"id"`
Password string `json:"-"`
}
Để parser/writer
bỏ qua 1 field
thì chỉ cần dùng -
trong tag
4. Nested Fields
Trong Golang cho phép 1 struct
lồng 1 struct
khác. Với struct
lồng như vậy thì vẫn có thể thêm các tag
vào 1 cách bình thường để xử lí JSON
type App struct {
Id string `json:"id"`
}
type Org struct {
Name string `json:"name"`
}
type AppWithOrg struct {
App
Org
}
5. Pointers
Con trỏ được dereference
trước khi JSON bị encode
. Hay nói cách khác, cái ta có được là value
chứ không phải pointer
.
6. Xử lí lỗi
Marshal
và Unmarshal
đều trả về err
. Ta có thể dùng nó để xử lí khi có lỗi xảy ra
func MustMarshal(data interface{}) []byte {
out, err := json.Marshal(data)
if err != nil {
panic(err)
}
return out
}
IV. Parsing JSON mà không biết trước cấu trúc
Vậy trường hợp ta hoàn toàn không biết trước cấu trúc cụ thể của JSON thì ta phải xử lí như thế nào? Việc định nghĩa struct
là bất khả thì. Do đó ta sẽ thử dùng interface
thay vì struct
vì trong Golang
1 empty interface
có thể chứa tất cả mọi thứ. Đợi đến thời điểm runtime
thì compiler
sẽ cung cấp memory
phù hợp có những thứ đó. Khi đó code của chúng ta sẽ trông như thế này:
var parsed interface{}
err := json.Unmarshal(data, &parsed)
Tuy nhiên khi đã có được biến parsed
, việc dùng biến này cần 1 chút xử lí để biết được kiểu dữ liệu cụ thể cụ nó là gì và việc này hơi tốn công. Ta cần thêm code check type như sau:
switch parsed.(type) {
case int:
someGreatIntFunction(parsed.(int))
case map:
someMapThing(parsed.(map))
default:
panic("JSON type not understood")
}
hoặc là
intVal, ok := parsed.(int)
if !ok {
panic("JSON value must be an int")
}
Hiếm khi ta hoàn toàn không biết gì về JSON cần xử lí. Ví dụ như ta biết 1 thông tin nhỏ là kiểu dữ liệu của nó là object
. Lúc này ta sẽ dùng map[string]interface{}
, việc dùng map
giúp ta có thêm khả năng refer
đến value
bằng key
var parsed map[string]interface{}
data := []byte(`
{
"id": "k34rAT4",
"age": 24
}
`)
err := json.Unmarshal(data, &parsed)
Và có thể lấy value
từ key
parsed["id"]
Vì ta dùng map[string]interface{}
nên khi lấy value
từ map
đó bằng 1 key
thì kiểu dữ liệu của value
đó vẫn là interface
, điều này có nghĩa là ta vẫn sẽ cần check type cho interface
đó.
Golang dùng 6 loại dữ liệu sau cho tất cả các values
được parse
thành interface
bool, cho JSON booleans
float64, cho JSON numbers
string, cho JSON strings
[]interface{}, cho JSON arrays
map[string]interface{}, cho JSON objects
nil cho JSON null
Điều này có nghĩa là nếu ở JSON, value
là số, thì khi parse value
đó luôn luôn có kiểu là float64
, nếu ta muốn thành int
thì phải xử lí thêm.