DevToolBox免费
博客

JSON 转 Go 结构体:完整转换指南与示例

9 分钟阅读作者 DevToolBox

在 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 值。普通的 stringint 字段不能为 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.Marshalerjson.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 处理可靠且易维护。快速转换使用在线工具,生产代码建议建立一致的标签约定。

立即试用 JSON 转 Go 结构体转换器。

Related Developer Tools and Guides

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

GoJSON to Go Struct{ }JSON Formatter

相关文章

JSON 转 Go Struct:映射策略与最佳实践

掌握 JSON 到 Go struct 的转换。涵盖 struct tags、嵌套类型、omitempty、自定义序列化和实际应用模式。

JSON vs YAML vs TOML:你应该用哪种配置格式?

比较 JSON、YAML 和 TOML 配置格式,了解语法、特性和优缺点,选择适合你项目的格式。

REST API 最佳实践:2026 完整指南

学习 REST API 设计最佳实践,包括命名规范、错误处理、认证、分页、版本控制和安全头。