Let's Go Further پیوست‌ها › نکات دقیق رمزگشایی JSON
قبلی · فهرست مطالب · بعدی
فصل ۲۱.۵.

نکات دقیق رمزگشایی JSON

رمزگشایی در آرایه‌های Go

هنگامی که یک آرایه JSON را در یک آرایه Go (نه slice) رمزگشایی می‌کنید، چند رفتار مهم وجود دارد که باید از آنها آگاه باشید:

به عنوان مثال:

js := `[1, 2, 3]`

var tooShortArray [2]int
err := json.NewDecoder(strings.NewReader(js)).Decode(&tooShortArray)
if err != nil {
    log.Fatal(err)
}

var tooLongArray [4]int
err = json.NewDecoder(strings.NewReader(js)).Decode(&tooLongArray)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("tooShortArray: %v\n", tooShortArray)
fmt.Printf("tooLongArray: %v\n", tooLongArray)

چاپ خواهد شد:

tooShortArray: [1 2]
tooLongArray: [1 2 3 0]

رمزگشایی جزئی JSON

اگر مقدار زیادی ورودی JSON برای پردازش دارید و فقط به بخش کوچکی از آن نیاز دارید، اغلب می‌توانید از نوع json.RawMessage برای کمک به حل این مشکل استفاده کنید. به عنوان مثال:

// Let's say that the only thing we're interested in is processing the "genres" array in
// the following JSON object
js := `{"title": "Top Gun", "genres": ["action", "romance"], "year": 1986}`

// Decode the JSON object to a map[string]json.RawMessage type. The json.RawMessage 
// values in the map will retain their original, undecoded, JSON values.
var m map[string]json.RawMessage

err := json.NewDecoder(strings.NewReader(js)).Decode(&m)
if err != nil {
    log.Fatal(err)
}

// We can then access the JSON "genres" value from the map and decode it as normal using
// the json.Unmarshal() function.
var genres []string

err = json.Unmarshal(m["genres"], &genres)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("genres: %v\n", genres)

این کد چاپ خواهد شد:

genres: [action romance]

در این مثال ساده، استفاده از json.RawMessage کار زیادی برای ما ذخیره نمی‌کند. اما اگر نیاز به پردازش یک شیء JSON با ده‌ها یا صدها جفت کلید-مقدار دارید و فقط به چند مورد از آنها نیاز دارید، استفاده از این رویکرد می‌تواند تایپ زیادی را برای شما ذخیره کند.

رمزگشایی در نوع any

امکان رمزگشایی مقادیر JSON در نوع any وجود دارد. وقتی این کار را انجام می‌دهید، مقدار زیرینی که نوع any نگه می‌دارد به نوع مقدار JSON که رمزگشایی می‌شود بستگی دارد.

نوع JSON نوع زیرین Go از any
JSON boolean bool
JSON string string
JSON number float64
JSON array []any
JSON object map[string]any
JSON null nil

رمزگشایی در نوع any می‌تواند در شرایط زیر مفید باشد:

به عنوان مثال، کد زیر را در نظر بگیرید:

// This JSON array contains both JSON string and JSON boolean types.
js := `["foo", true]`

// Decode the JSON into a []any slice.
var s []any

err := json.NewDecoder(strings.NewReader(js)).Decode(&s)
if err != nil {
    log.Fatal(err)
}

// The first value in the slice will have the underlying Go type string, the second will
// have the underlying Go type bool. We can then type assert them and print them out  
// the values along with their underlying type.
fmt.Printf("item: 0; type: %T; value: %v\n", s[0], s[0].(string))
fmt.Printf("item: 1; type: %T; value: %v\n", s[1], s[1].(bool))

این کد چاپ خواهد شد:

item: 0; type: string; value: foo
item: 1; type: bool; value: true

رمزگشایی یک عدد JSON در نوع any

همانطور که در جدول بالا نشان داده شده است، وقتی یک عدد JSON را در نوع any رمزگشایی می‌کنید، مقدار نوع زیرین float64 خواهد داشت — حتی اگر در JSON اصلی یک عدد صحیح باشد. به عنوان مثال:

js := `10` // This JSON number is an integer.

var n any

err := json.NewDecoder(strings.NewReader(js)).Decode(&n)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("type: %T; value: %v\n", n, n) 

چاپ خواهد شد:

type: float64; value: 10

اگر می‌خواهید مقدار را به صورت عدد صحیح (به جای float64) دریافت کنید، باید قبل از رمزگشایی متد UseNumber() را روی نمونه json.Decoder خود فراخوانی کنید. این کار باعث می‌شود تمام اعداد JSON به نوع زیرین json.Number به جای float64 رمزگشایی شوند.

سپس نوع json.Number متد Int64() را ارائه می‌دهد که می‌توانید آن را برای دریافت عدد به صورت int64 فراخوانی کنید، یا متد String() برای دریافت عدد به صورت string. به عنوان مثال:

js := `10`

var n any

dec := json.NewDecoder(strings.NewReader(js))
dec.UseNumber() // Before using the decoder, call the UseNumber() method on it.
err := dec.Decode(&n)
if err != nil {
    log.Fatal(err)
}

// Type assert the any value to a json.Number, and then call the Int64() method
// to get the number as a Go int64.
nInt64, err := n.(json.Number).Int64()
if err != nil {
    log.Fatal(err)
}

// Likewise, you can use the String() method to get the number as a Go string.
nString := n.(json.Number).String()

fmt.Printf("type: %T; value: %v\n", n, n)
fmt.Printf("type: %T; value: %v\n", nInt64, nInt64)
fmt.Printf("type: %T; value: %v\n", nString, nString)

این کد چاپ خواهد شد:

type: json.Number; value: 10
type: int64; value: 10
type: string; value: 10

دستورالعمل‌های برچسب struct

استفاده از برچسب struct json:"-" روی یک فیلد struct باعث می‌شود هنگام رمزگشایی JSON نادیده گرفته شود، حتی اگر ورودی JSON حاوی جفت کلید-مقدار مربوطه باشد. به عنوان مثال:

js := `{"name": "alice", "age": 21}`

var person struct {
    Name string `json:"name"`
    Age  int32 `json:"-"`
}

err := json.NewDecoder(strings.NewReader(js)).Decode(&person)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("%+v", person)

چاپ خواهد شد:

{Name:alice Age:0}

دستورالعمل‌های برچسب struct omitzero و omitempty هیچ تأثیری بر رفتار رمزگشایی JSON ندارند.