在 Go 中处理 JSON 需要定义与 JSON 结构匹配的结构体类型。与动态类型语言不同,Go 要求显式的结构体定义和正确的 json 标签。本指南详细介绍 JSON 到 Go 结构体的转换,包括类型映射、嵌套结构、指针类型处理可选字段、omitempty 最佳实践和自定义序列化。
使用我们的免费在线工具即时将 JSON 转换为 Go 结构体。
JSON 到 Go 类型映射基础
每种 JSON 类型都有对应的 Go 类型。理解这些映射是编写正确结构体定义的基础:
JSON Type Go Type Example
─────────────────────────────────────────────────────────
string string "hello" → "hello"
number (integer) int / int64 42 → 42
number (float) float64 3.14 → 3.14
boolean bool true → true
null *T (pointer) null → nil
object struct {...} → MyStruct{}
array of strings []string ["a","b"] → []string{"a","b"}
array of objects []MyStruct [{...}] → []MyStruct{...}
array of mixed []interface{} [1,"a",true] → []interface{}{...}
dynamic object map[string]any {...} → map[string]any{...}最重要的区别是 Go 如何处理 null 值。普通的 string 或 int 字段不能为 null。要表示可空字段,必须使用指针类型(*string、*int)。
JSON 结构体标签:基础指南
结构体标签告诉 Go 的 encoding/json 包如何在 JSON 键和结构体字段之间映射:
package main
import (
"encoding/json"
"fmt"
)
// Basic struct with json tags
type User struct {
// Field name → json key mapping
ID int `json:"id"` // "id" in JSON
FirstName string `json:"first_name"` // "first_name" in JSON
LastName string `json:"last_name"` // "last_name" in JSON
Email string `json:"email"` // "email" in JSON
Age int `json:"age,omitempty"` // skip if zero value
Password string `json:"-"` // always skip (never serialize)
// Without tag - uses field name as-is (case-insensitive unmarshal)
Username string // matches "Username", "username", etc.
}
func main() {
jsonData := `{
"id": 1,
"first_name": "Alice",
"last_name": "Smith",
"email": "alice@example.com",
"age": 0,
"Username": "alice123"
}`
var user User
if err := json.Unmarshal([]byte(jsonData), &user); err != nil {
panic(err)
}
fmt.Printf("Name: %s %s\n", user.FirstName, user.LastName)
// Output: Name: Alice Smith
// Marshal back - age is omitted because it's 0 (omitempty)
output, _ := json.MarshalIndent(user, "", " ")
fmt.Println(string(output))
}关键规则:字段必须导出(首字母大写)才能被 JSON 包识别。标签将导出的 Go 字段名映射到 JSON 键名。
处理嵌套 JSON 对象
真实的 JSON 负载很少是扁平的。嵌套对象需要为每一层定义单独的结构体:
// JSON input:
// {
// "id": 1,
// "name": "Acme Corp",
// "address": {
// "street": "123 Main St",
// "city": "Springfield",
// "state": "IL",
// "zip": "62701",
// "coordinates": {
// "lat": 39.7817,
// "lng": -89.6501
// }
// },
// "contacts": [
// { "name": "Alice", "role": "CEO", "email": "alice@acme.com" },
// { "name": "Bob", "role": "CTO", "email": "bob@acme.com" }
// ]
// }
// Define separate structs for each nested level
type Coordinates struct {
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
}
type Address struct {
Street string `json:"street"`
City string `json:"city"`
State string `json:"state"`
Zip string `json:"zip"`
Coordinates Coordinates `json:"coordinates"`
}
type Contact struct {
Name string `json:"name"`
Role string `json:"role"`
Email string `json:"email"`
}
type Company struct {
ID int `json:"id"`
Name string `json:"name"`
Address Address `json:"address"`
Contacts []Contact `json:"contacts"`
}深层嵌套的 JSON 应分别定义每个结构体类型,而不是使用内联匿名结构体,这样可以提高可读性和类型复用。
数组和切片
JSON 数组映射到 Go 切片。元素类型取决于数组内容:
// Different array types in JSON → Go
type Product struct {
Name string `json:"name"`
Tags []string `json:"tags"` // ["electronics", "sale"]
Prices []float64 `json:"prices"` // [29.99, 39.99, 49.99]
Ratings []int `json:"ratings"` // [5, 4, 5, 3]
IsActive []bool `json:"flags"` // [true, false, true]
// Array of objects
Reviews []Review `json:"reviews"`
// Nested array of arrays (matrix)
Matrix [][]int `json:"matrix"` // [[1,2],[3,4]]
// Mixed-type array (rare but possible)
Metadata []interface{} `json:"metadata"` // [1, "hello", true]
}
type Review struct {
Author string `json:"author"`
Rating int `json:"rating"`
Comment string `json:"comment"`
}
// Empty arrays vs null arrays
type Response struct {
// null in JSON → nil slice (len=0, cap=0, == nil)
// [] in JSON → empty slice (len=0, cap=0, != nil)
// omitempty skips nil slices but NOT empty slices
Items []Item `json:"items,omitempty"`
}处理可选和可空字段
JSON 转 Go 最棘手的部分之一是正确处理可选字段。Go 有三种模式:
// Pattern 1: omitempty - skip zero values when marshaling
type UpdateRequest struct {
Name string `json:"name,omitempty"` // "" is skipped
Age int `json:"age,omitempty"` // 0 is skipped
Admin bool `json:"admin,omitempty"` // false is skipped
}
// Problem: Can't distinguish "age not provided" from "age is 0"
// Pattern 2: Pointer types - distinguish null/missing from zero
type UpdateRequestV2 struct {
Name *string `json:"name,omitempty"` // nil = not provided, "" = empty
Age *int `json:"age,omitempty"` // nil = not provided, 0 = zero
Admin *bool `json:"admin,omitempty"` // nil = not provided, false = false
}
// Helper function to create pointers (Go doesn't allow &literal)
func ptr[T any](v T) *T { return &v }
// Usage:
// req := UpdateRequestV2{
// Name: ptr("Alice"),
// Age: ptr(0), // explicitly set to 0, not nil
// }
// Pattern 3: json.RawMessage - defer parsing
type Event struct {
Type string `json:"type"`
Payload json.RawMessage `json:"payload"` // raw JSON bytes
}
// Parse payload based on type
func (e *Event) ParsePayload() (interface{}, error) {
switch e.Type {
case "user_created":
var u User
return &u, json.Unmarshal(e.Payload, &u)
case "order_placed":
var o Order
return &o, json.Unmarshal(e.Payload, &o)
default:
var m map[string]interface{}
return &m, json.Unmarshal(e.Payload, &m)
}
}根据用例选择正确的模式:零值可接受时用 omitempty,需要区分"未提供"和"零值"时用指针类型,多态字段用 json.RawMessage。
自定义 JSON 序列化和反序列化
有时默认的 JSON 映射不够用。Go 允许实现 json.Marshaler 和 json.Unmarshaler 接口:
import (
"encoding/json"
"time"
"fmt"
"strings"
)
// Custom date format (JSON uses "2006-01-02", not RFC3339)
type Date struct {
time.Time
}
const dateFormat = "2006-01-02"
func (d *Date) UnmarshalJSON(data []byte) error {
// Remove quotes from JSON string
s := strings.Trim(string(data), `"`)
if s == "null" || s == "" {
return nil
}
t, err := time.Parse(dateFormat, s)
if err != nil {
return fmt.Errorf("invalid date format: %s", s)
}
d.Time = t
return nil
}
func (d Date) MarshalJSON() ([]byte, error) {
if d.Time.IsZero() {
return []byte("null"), nil
}
return json.Marshal(d.Time.Format(dateFormat))
}
// Usage in a struct
type Employee struct {
Name string `json:"name"`
StartDate Date `json:"start_date"`
EndDate *Date `json:"end_date,omitempty"`
}
// Custom enum type
type Status string
const (
StatusActive Status = "active"
StatusInactive Status = "inactive"
StatusPending Status = "pending"
)
func (s *Status) UnmarshalJSON(data []byte) error {
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
}
switch Status(str) {
case StatusActive, StatusInactive, StatusPending:
*s = Status(str)
return nil
default:
return fmt.Errorf("invalid status: %s", str)
}
}自定义序列化的常见用例包括:处理多种日期格式、字符串和数值之间的转换、实现枚举类型、序列化时扁平化嵌套结构。
真实 API 模式
以下是使用 REST API 时常见的 JSON 模式及其在 Go 中的建模方式:
// Pattern: Paginated API Response
type PaginatedResponse[T any] struct {
Data []T `json:"data"`
Total int `json:"total"`
Page int `json:"page"`
PerPage int `json:"per_page"`
TotalPages int `json:"total_pages"`
NextURL string `json:"next_url,omitempty"`
PrevURL string `json:"prev_url,omitempty"`
}
// Pattern: API Error Response
type APIError struct {
Code int `json:"code"`
Message string `json:"message"`
Details map[string]string `json:"details,omitempty"`
}
func (e *APIError) Error() string {
return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}
// Pattern: Wrapper for success/error responses
type APIResponse[T any] struct {
Success bool `json:"success"`
Data *T `json:"data,omitempty"`
Error *APIError `json:"error,omitempty"`
}
// Pattern: Webhook payload with dynamic event data
type WebhookPayload struct {
ID string `json:"id"`
Event string `json:"event"`
Timestamp time.Time `json:"timestamp"`
Data json.RawMessage `json:"data"`
}
// Pattern: Configuration file with defaults
type Config struct {
Host string `json:"host"`
Port int `json:"port"`
Debug bool `json:"debug"`
LogLevel string `json:"log_level"`
Database struct {
URL string `json:"url"`
MaxConnections int `json:"max_connections"`
ConnectTimeout int `json:"connect_timeout_ms"`
} `json:"database"`
}
// Set defaults before unmarshaling
func NewConfig() *Config {
return &Config{
Host: "localhost",
Port: 8080,
LogLevel: "info",
}
}
func LoadConfig(data []byte) (*Config, error) {
cfg := NewConfig()
if err := json.Unmarshal(data, cfg); err != nil {
return nil, err
}
return cfg, nil
}自动化 JSON 到 Go 结构体生成
我们的 JSON to Go 转换器工具从任何 JSON 输入自动生成正确标记和类型的结构体定义。
使用我们的免费在线工具即时将 JSON 转换为 Go 结构体。
对于 CI/CD 集成,也可以使用 json-to-go 命令行工具或从 JSON Schema 生成 Go 类型。
# Generate Go structs from a JSON file
cat api-response.json | json-to-go
# Generate from a JSON Schema definition
go install github.com/atombender/go-jsonschema/cmd/gojsonschema@latest
gojsonschema -p models schema.json
# Validate your structs handle the JSON correctly
go test -run TestUnmarshal ./models/...常见问题
如何将 JSON 转换为 Go 结构体?
定义带有导出字段和 json 标签的 Go 结构体。将 JSON 类型映射到 Go 类型。使用我们的免费在线工具自动生成。
Go JSON 标签中的 omitempty 是什么?
omitempty 告诉 JSON 编码器在序列化时跳过零值字段。适用于 PATCH API 请求。
如何在 Go JSON 中处理 null 值?
对可能为 null 的字段使用指针类型(*string、*int、*bool)。JSON null 值时指针为 nil。
Go 字段名可以和 JSON 字段名不同吗?
可以,使用结构体标签。例如 `json:"user_name"` 将 JSON 键映射到 Go 字段名。
如何处理动态或未知的 JSON 字段?
完全动态的 JSON 用 map[string]interface{},延迟解析用 json.RawMessage,部分已知结构可以组合结构体和 map。
正确地将 JSON 转换为 Go 结构体是编写健壮 Go 应用的基础。理解类型映射、指针类型、omitempty 和自定义序列化将使 JSON 处理可靠且易维护。快速转换使用在线工具,生产代码建议建立一致的标签约定。
Related Developer Tools and Guides
- JSON to Go Converter - Generate Go struct definitions from JSON data instantly
- JSON Formatter - Format and validate JSON before conversion
- JSON to TypeScript - Generate TypeScript interfaces from JSON
- JSON Validator - Validate your JSON structure before generating structs
- JSON to Go Struct Best Practices - Struct tag patterns and mapping strategies
- JSON vs YAML vs TOML - Compare data serialization formats
- REST API Best Practices - Design robust APIs with proper JSON structures
- Protobuf vs JSON - When to use Protocol Buffers over JSON