JSON処理

レッスン21:JSON処理

生活での例え

あなたが翻訳者であると想像してください。JSONは世界で最も広く使われている「共通言語」です。Goプログラムが他のシステム(フロントエンド、API、データベース)と通信する必要がある場合、以下を行う必要があります:

翻訳者が両方の言語のルールと慣用句を理解する必要があるように、Goのencoding/jsonパッケージはJSONを処理するための強力なツールです。


コアコンセプト

コンセプト 説明
シリアライゼーション(Marshal) Goのデータ構造をJSONバイトスライスに変換
デシリアライゼーション(Unmarshal) JSONバイトスライスをGoのデータ構造に解析
構造体タグ JSONフィールド名と動作を制御するメタデータ
ストリーミング Decoder/Encoderを使用して大量データやネットワークストリームを処理
カスタムシリアライゼーション Marshaler/Unmarshalerインターフェースを実装してカスタム変換ロジックを実現

基本構文と使い方

1. パッケージのインポート

GO
import "encoding/json"

2. シリアライゼーション:構造体 → JSON

GO
// 構造体を定義
type User struct {
    Name  string
    Age   int
    Email string
}

user := User{Name: "Alice", Age: 28, Email: "alice@example.com"}

// シリアライズ
data, err := json.Marshal(user)
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(data))
// 出力: {"Name":"Alice","Age":28,"Email":"alice@example.com"}
💡 ヒント: json.Marshal[]byteを返すため、読みやすいJSONを表示するにはstring()変換が必要です。

3. デシリアライゼーション:JSON → 構造体

GO
jsonStr := `{"Name":"Bob","Age":32,"Email":"bob@example.com"}`

var user User
err := json.Unmarshal([]byte(jsonStr), &user)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("名前: %s, 年齢: %d\n", user.Name, user.Age)
// 出力: 名前: Bob, 年齢: 32
💡 ヒント: Unmarshalの第2パラメータはポインタでなければなりません。さもないと変更が反映されません。

4. 構造体タグ

GO
type Product struct {
    ID    int     `json:"id"`           // JSONフィールド名を指定
    Name  string  `json:"name"`         // 小文字命名はJSONにより親和性が高い
    Price float64 `json:"price"`
    Desc  string  `json:"description,omitempty"` // 空の場合は省略
    internal string `json:"-"`          // このフィールドを完全に無視
}
💡 ヒント:

  • omitempty:フィールドがゼロ値の場合、JSON出力から省略されます
  • -:このフィールドはJSONに出現しません
  • タグ名はフィールド名より優先されます

5. 一般的な型マッピング

Goの型 JSONの型
string string
int, float64 number
bool boolean
nil null
[]T array
map[string]T object
struct object

例題

例:基本的なJSONシリアライゼーションとデシリアライゼーション(難易度⭐)

GO
package main

import (
    "encoding/json"
    "fmt"
    "log"
)

// Book構造体
type Book struct {
    Title    string   `json:"title"`
    Author   string   `json:"author"`
    Pages    int      `json:"pages"`
    Tags     []string `json:"tags"`
    InStock  bool     `json:"in_stock"`
}

func main() {
    // === シリアライゼーション ===
    book := Book{
        Title:   "Go in Action",
        Author:  "John Smith",
        Pages:   350,
        Tags:    []string{"Programming", "Go", "Backend"},
        InStock: true,
    }

    // プリティプリント(インデント付き)
    jsonData, err := json.MarshalIndent(book, "", "  ")
    if err != nil {
        log.Fatal("シリアライゼーション失敗:", err)
    }
    fmt.Println("=== シリアライゼーション結果 ===")
    fmt.Println(string(jsonData))

    // === デシリアライゼーション ===
    jsonStr := `{
        "title": "Mastering Go",
        "author": "Jane Doe",
        "pages": 480,
        "tags": ["Go", "Advanced", "Concurrency"],
        "in_stock": false
    }`

    var newBook Book
    err = json.Unmarshal([]byte(jsonStr), &newBook)
    if err != nil {
        log.Fatal("デシリアライゼーション失敗:", err)
    }
    fmt.Println("\n=== デシリアライゼーション結果 ===")
    fmt.Printf("タイトル: %s\n", newBook.Title)
    fmt.Printf("著者: %s\n", newBook.Author)
    fmt.Printf("タグ: %v\n", newBook.Tags)
    fmt.Printf("在庫あり: %v\n", newBook.InStock)
}
▶ 試してみよう

出力:

TEXT
=== シリアライゼーション結果 ===
{
  "title": "Go in Action",
  "author": "John Smith",
  "pages": 350,
  "tags": [
    "Programming",
    "Go",
    "Backend"
  ],
  "in_stock": true
}

=== デシリアライゼーション結果 ===
タイトル: Mastering Go
著者: Jane Doe
タグ: [Go Advanced Concurrency]
在庫あり: false

例:ネストされたJSONとMapの処理(難易度⭐⭐)

GO
package main

import (
    "encoding/json"
    "fmt"
    "log"
)

// Address構造体
type Address struct {
    City    string `json:"city"`
    Street  string `json:"street"`
    ZipCode string `json:"zip_code"`
}

// 連絡先情報
type Contact struct {
    Phone string `json:"phone"`
    Email string `json:"email"`
}

// Employee構造体(ネスト付き)
type Employee struct {
    Name      string            `json:"name"`
    Age       int               `json:"age"`
    Address   Address           `json:"address"`     // ネストされた構造体
    Contact   Contact           `json:"contact"`     // ネストされた構造体
    Skills    []string          `json:"skills"`      // スライス
    Metadata  map[string]string `json:"metadata"`    // 動的フィールド
}

func main() {
    // ネストされたデータを構築
    emp := Employee{
        Name: "Alice",
        Age:  35,
        Address: Address{
            City:    "Beijing",
            Street:  "88 Jianguo Road, Chaoyang District",
            ZipCode: "100022",
        },
        Contact: Contact{
            Phone: "13800138000",
            Email: "alice@example.com",
        },
        Skills: []string{"Go", "Python", "Docker"},
        Metadata: map[string]string{
            "department": "Engineering",
            "level":      "P7",
            "joined":     "2020-03-15",
        },
    }

    // シリアライズ
    data, err := json.MarshalIndent(emp, "", "  ")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("=== ネストされたJSONシリアライゼーション ===")
    fmt.Println(string(data))

    // 動的JSONの処理(mapを使用)
    dynamicJSON := `{
        "event": "user_login",
        "timestamp": 1700000000,
        "data": {
            "user_id": 12345,
            "ip": "192.168.1.100",
            "browser": "Chrome"
        },
        "tags": ["web", "auth"]
    }`

    var result map[string]interface{}
    err = json.Unmarshal([]byte(dynamicJSON), &result)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("\n=== 動的JSON解析 ===")
    fmt.Printf("イベント: %s\n", result["event"])
    fmt.Printf("タイムスタンプ: %.0f\n", result["timestamp"])

    // ネストされたmapにアクセス
    if data, ok := result["data"].(map[string]interface{}); ok {
        fmt.Printf("ユーザーID: %.0f\n", data["user_id"])
        fmt.Printf("IPアドレス: %s\n", data["ip"])
    }

    // 配列にアクセス
    if tags, ok := result["tags"].([]interface{}); ok {
        fmt.Print("タグ: ")
        for _, tag := range tags {
            fmt.Printf("%s ", tag)
        }
        fmt.Println()
    }
}
▶ 試してみよう

出力:

TEXT
=== ネストされたJSONシリアライゼーション ===
{
  "name": "Alice",
  "age": 35,
  "address": {
    "city": "Beijing",
    "street": "88 Jianguo Road, Chaoyang District",
    "zip_code": "100022"
  },
  "contact": {
    "phone": "13800138000",
    "email": "alice@example.com"
  },
  "skills": [
    "Go",
    "Python",
    "Docker"
  ],
  "metadata": {
    "department": "Engineering",
    "joined": "2020-03-15",
    "level": "P7"
  }
}

=== 動的JSON解析 ===
イベント: user_login
タイムスタンプ: 1700000000
ユーザーID: 12345
IPアドレス: 192.168.1.100
タグ: web auth

例:カスタムシリアライゼーションとストリーミング(難易度⭐⭐⭐)

GO
package main

import (
    "encoding/json"
    "fmt"
    "log"
    "strings"
    "time"
)

// CustomTime カスタム時間型
type CustomTime struct {
    time.Time
}

// json.Marshalerインターフェースを実装
func (ct CustomTime) MarshalJSON() ([]byte, error) {
    // 出力フォーマット: 2006-01-02 15:04:05
    formatted := ct.Format("2006-01-02 15:04:05")
    return json.Marshal(formatted)
}

// json.Unmarshalerインターフェースを実装
func (ct *CustomTime) UnmarshalJSON(data []byte) error {
    var s string
    if err := json.Unmarshal(data, &s); err != nil {
        return err
    }
    // 複数のフォーマット解析をサポート
    formats := []string{
        "2006-01-02 15:04:05",
        "2006-01-02T15:04:05",
        "2006/01/02",
    }
    for _, format := range formats {
        t, err := time.Parse(format, s)
        if err == nil {
            ct.Time = t
            return nil
        }
    }
    return fmt.Errorf("時間を解析できませんでした: %s", s)
}

// Status カスタム列挙型
type Status int

const (
    StatusActive   Status = iota // 0
    StatusInactive               // 1
    StatusBanned                 // 2
)

// Statusから文字列へのマッピング
var statusNames = map[Status]string{
    StatusActive:   "active",
    StatusInactive: "inactive",
    StatusBanned:   "banned",
}

// 文字列からStatusへのマッピング
var statusValues = map[string]Status{
    "active":   StatusActive,
    "inactive": StatusInactive,
    "banned":   StatusBanned,
}

// MarshalJSON カスタムシリアライゼーション
func (s Status) MarshalJSON() ([]byte, error) {
    name, ok := statusNames[s]
    if !ok {
        return json.Marshal("unknown")
    }
    return json.Marshal(name)
}

// UnmarshalJSON カスタムデシリアライゼーション
func (s *Status) UnmarshalJSON(data []byte) error {
    var name string
    if err := json.Unmarshal(data, &name); err != nil {
        return err
    }
    val, ok := statusValues[name]
    if !ok {
        return fmt.Errorf("不明なステータス: %s", name)
    }
    *s = val
    return nil
}

// EventLog イベントログエントリー
type EventLog struct {
    Event     string     `json:"event"`
    Timestamp CustomTime `json:"timestamp"`
    Status    Status     `json:"status"`
    Details   string     `json:"details,omitempty"`
}

func main() {
    // === カスタムシリアライゼーションデモ ===
    logEntry := EventLog{
        Event:     "user_register",
        Timestamp: CustomTime{time.Date(2024, 1, 15, 14, 30, 0, 0, time.Local)},
        Status:    StatusActive,
        Details:   "新規ユーザーが正常に登録しました",
    }

    data, _ := json.MarshalIndent(logEntry, "", "  ")
    fmt.Println("=== カスタムシリアライゼーション ===")
    fmt.Println(string(data))

    // === カスタムデシリアライゼーションデモ ===
    jsonStr := `{
        "event": "user_login",
        "timestamp": "2024/01/15",
        "status": "inactive"
    }`

    var entry EventLog
    err := json.Unmarshal([]byte(jsonStr), &entry)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("\n解析結果: イベント=%s, 時刻=%s, ステータス=%d\n",
        entry.Event,
        entry.Timestamp.Format("2006-01-02 15:04:05"),
        entry.Status,
    )

    // === ストリーミングデモ ===
    fmt.Println("\n=== ストリーミングDecoder ===")
    // ネットワークから受信したJSONストリームをシミュレーション
    jsonStream := `[
        {"name": "Alice", "score": 95},
        {"name": "Bob", "score": 87},
        {"name": "Charlie", "score": 92}
    ]`

    decoder := json.NewDecoder(strings.NewReader(jsonStream))

    // 開始トークンを読み取る
    token, err := decoder.Token()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("開始トークン: %v\n", token)

    // 配列の要素を1つずつ読み取る
    type Student struct {
        Name  string `json:"name"`
        Score int    `json:"score"`
    }

    var students []Student
    for decoder.More() {
        var s Student
        if err := decoder.Decode(&s); err != nil {
            log.Fatal(err)
        }
        students = append(students, s)
    }

    for _, s := range students {
        fmt.Printf("学生: %s, スコア: %d\n", s.Name, s.Score)
    }

    // === ストリーミングEncoderデモ ===
    fmt.Println("\n=== ストリーミングEncoder ===")
    var buf strings.Builder
    encoder := json.NewEncoder(&buf)
    encoder.SetIndent("", "  ")

    // 個々のオブジェクトをエンコード
    for _, s := range students {
        if err := encoder.Encode(s); err != nil {
            log.Fatal(err)
        }
    }
    fmt.Println(buf.String())
}
▶ 試してみよう

出力:

TEXT
=== カスタムシリアライゼーション ===
{
  "event": "user_register",
  "timestamp": "2024-01-15 14:30:00",
  "status": "active",
  "details": "新規ユーザーが正常に登録しました"
}

解析結果: イベント=user_login, 時刻=2024-01-15 00:00:00, ステータス=1

=== ストリーミングDecoder ===
開始トークン: [
学生: Alice, スコア: 95
学生: Bob, スコア: 87
学生: Charlie, スコア: 92

=== ストリーミングEncoder ===
{
  "name": "Alice",
  "score": 95
}
{
  "name": "Bob",
  "score": 87
}
{
  "name": "Charlie",
  "score": 92
}

応用シナリオ

シナリオ1:APIレスポンスラッパー

GO
package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
)

// APIResponse 統一APIレスポンス構造
type APIResponse struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data,omitempty"`
    Error   string      `json:"error,omitempty"`
}

// SuccessResponse 成功レスポンス
func SuccessResponse(w http.ResponseWriter, data interface{}) {
    resp := APIResponse{
        Code:    200,
        Message: "success",
        Data:    data,
    }
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(resp)
}

// ErrorResponse エラーレスポンス
func ErrorResponse(w http.ResponseWriter, statusCode int, errMsg string) {
    resp := APIResponse{
        Code:    statusCode,
        Message: "error",
        Error:   errMsg,
    }
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(statusCode)
    json.NewEncoder(w).Encode(resp)
}

// UserHandler ユーザーリクエストを処理
func UserHandler(w http.ResponseWriter, r *http.Request) {
    // シミュレーション用ユーザーデータ
    users := []map[string]interface{}{
        {"id": 1, "name": "Alice", "role": "admin"},
        {"id": 2, "name": "Bob", "role": "user"},
        {"id": 3, "name": "Charlie", "role": "user"},
    }

    SuccessResponse(w, users)
}

func main() {
    // シミュレーション用APIレスポンス
    fmt.Println("=== シミュレーション用APIレスポンス ===")

    // 成功レスポンス
    successResp := APIResponse{
        Code:    200,
        Message: "success",
        Data: map[string]interface{}{
            "id":   1,
            "name": "Alice",
        },
    }
    data, _ := json.MarshalIndent(successResp, "", "  ")
    fmt.Println("成功レスポンス:")
    fmt.Println(string(data))

    // エラーレスポンス
    errorResp := APIResponse{
        Code:    404,
        Message: "error",
        Error:   "ユーザーが見つかりません",
    }
    data, _ = json.MarshalIndent(errorResp, "", "  ")
    fmt.Println("\nエラーレスポンス:")
    fmt.Println(string(data))

    _ = log.Fatal // 未使用警告を回避
}

出力:

TEXT
=== シミュレーション用APIレスポンス ===
成功レスポンス:
{
  "code": 200,
  "message": "success",
  "data": {
    "id": 1,
    "name": "Alice"
  }
}

エラーレスポンス:
{
  "code": 404,
  "message": "error",
  "error": "ユーザーが見つかりません"
}

シナリオ2:設定ファイルの読み取りとバリデーション

GO
package main

import (
    "encoding/json"
    "fmt"
    "log"
    "os"
)

// DatabaseConfig データベース設定
type DatabaseConfig struct {
    Host     string `json:"host"`
    Port     int    `json:"port"`
    Username string `json:"username"`
    Password string `json:"password"`
    DBName   string `json:"dbname"`
}

// ServerConfig サーバー設定
type ServerConfig struct {
    Host         string   `json:"host"`
    Port         int      `json:"port"`
    ReadTimeout  int      `json:"read_timeout"`
    WriteTimeout int      `json:"write_timeout"`
    AllowOrigins []string `json:"allow_origins"`
}

// AppConfig アプリケーション設定
type AppConfig struct {
    AppName  string         `json:"app_name"`
    Debug    bool           `json:"debug"`
    Server   ServerConfig   `json:"server"`
    Database DatabaseConfig `json:"database"`
}

// Validate 設定をバリデーション
func (c *AppConfig) Validate() error {
    if c.AppName == "" {
        return fmt.Errorf("app_nameは空にできません")
    }
    if c.Server.Port <= 0 || c.Server.Port > 65535 {
        return fmt.Errorf("server.portは1〜65535の間でなければなりません")
    }
    if c.Database.Host == "" {
        return fmt.Errorf("database.hostは空にできません")
    }
    return nil
}

func main() {
    // シミュレーション用設定ファイル内容
    configJSON := `{
        "app_name": "GoWebApp",
        "debug": true,
        "server": {
            "host": "0.0.0.0",
            "port": 8080,
            "read_timeout": 30,
            "write_timeout": 30,
            "allow_origins": ["http://localhost:3000", "https://example.com"]
        },
        "database": {
            "host": "localhost",
            "port": 3306,
            "username": "root",
            "password": "secret123",
            "dbname": "myapp"
        }
    }`

    // 設定を解析
    var config AppConfig
    err := json.Unmarshal([]byte(configJSON), &config)
    if err != nil {
        log.Fatalf("設定の解析に失敗しました: %v", err)
    }

    // 設定をバリデーション
    if err := config.Validate(); err != nil {
        log.Fatalf("設定バリデーション失敗: %v", err)
    }

    // 設定情報を出力
    fmt.Printf("アプリ名: %s\n", config.AppName)
    fmt.Printf("デバッグモード: %v\n", config.Debug)
    fmt.Printf("サーバーアドレス: %s:%d\n", config.Server.Host, config.Server.Port)
    fmt.Printf("データベース接続: %s:%d/%s\n",
        config.Database.Host,
        config.Database.Port,
        config.Database.DBName,
    )
    fmt.Printf("許可オリジン: %v\n", config.Server.AllowOrigins)

    // 書き込み例(変更された設定を保存)
    config.Debug = false
    config.Server.Port = 9090

    output, err := json.MarshalIndent(config, "", "  ")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("\n=== 変更された設定 ===")
    fmt.Println(string(output))

    // 実際のプロジェクトではファイルに書き込みます:
    // os.WriteFile("config.json", output, 0644)
    _ = os.WriteFile // 未使用警告を回避
}

出力:

TEXT
アプリ名: GoWebApp
デバッグモード: true
サーバーアドレス: 0.0.0.0:8080
データベース接続: localhost:3306/myapp
許可オリジン: [http://localhost:3000 https://example.com]

=== 変更された設定 ===
{
  "app_name": "GoWebApp",
  "debug": false,
  "server": {
    "host": "0.0.0.0",
    "port": 9090,
    "read_timeout": 30,
    "write_timeout": 30,
    "allow_origins": [
      "http://localhost:3000",
      "https://example.com"
    ]
  },
  "database": {
    "host": "localhost",
    "port": 3306,
    "username": "root",
    "password": "secret123",
    "dbname": "myapp"
  }
}

❓ よくある質問

質問1:なぜJSONフィールド名が大文字になるのですか?

理由: Goは先頭が大文字のフィールドのみをエクスポートし、json.Marshalはデフォルトでフィールド名をJSONキーとして使用します。

解決策: 構造体タグを使用して小文字の名前を指定します:

GO
type User struct {
    Name  string `json:"name"`   // JSONでは "name"
    Age   int    `json:"age"`    // JSONでは "age"
    Email string `json:"email"`  // JSONでは "email"
}

質問2:空値のフィールドを無視するにはどうすればいいですか?

omitemptyタグを使用します:

GO
type Request struct {
    Name  string `json:"name"`
    Email string `json:"email,omitempty"` // 空文字列の場合は省略
    Age   int    `json:"age,omitempty"`   // 0の場合は省略
    Items []string `json:"items,omitempty"` // nilまたは空スライスの場合は省略
}

// テスト
req := Request{Name: "Alice"}
data, _ := json.Marshal(req)
fmt.Println(string(data))
// 出力: {"name":"Alice"} — email、age、itemsはすべて省略

質問3:JSONの数値精度問題をどう処理すればいいですか?

Goのjson.UnmarshalはデフォルトでJSONの数値をfloat64として解析するため、大きな整数では精度が失われます:

GO
// 問題の例
var result map[string]interface{}
json.Unmarshal([]byte(`{"id": 12345678901234567}`), &result)
fmt.Printf("%.0f\n", result["id"]) // 出力: 12345678901234568(精度が失われる!)

// 解決策:json.Numberを使用
decoder := json.NewDecoder(strings.NewReader(`{"id": 12345678901234567}`))
decoder.UseNumber()
decoder.Decode(&result)

id, _ := result["id"].(json.Number).Int64()
fmt.Println(id) // 出力: 12345678901234567(正しい)

質問4:構造が不明なJSONをどう処理すればいいですか?

map[string]interface{}またはjson.RawMessageを使用します:

GO
// 方法1:mapを使用
var data map[string]interface{}
json.Unmarshal(jsonBytes, &data)

// 方法2:遅延解析のためにjson.RawMessageを使用
type Message struct {
    Type    string          `json:"type"`
    Payload json.RawMessage `json:"payload"` // 遅延解析
}

// Typeフィールドに基づいてPayloadの解析方法を決定
switch msg.Type {
case "user":
    var user User
    json.Unmarshal(msg.Payload, &user)
case "order":
    var order Order
    json.Unmarshal(msg.Payload, &order)
}

📖 まとめ

このレッスンではGoのJSON処理のコア内容をカバーしました:

  1. 基本操作json.Marshalでシリアライズ、json.Unmarshalでデシリアライズ
  2. 構造体タグjson:"name"でフィールド名を制御、omitemptyで空値を省略、-でフィールドを無視
  3. ネスト処理:構造体のネスト、map[string]interface{}で動的JSONを処理
  4. カスタムシリアライゼーションMarshaler/Unmarshalerインターフェースを実装
  5. ストリーミングjson.Decoderjson.Encoderでストリームデータを処理
  6. 実践応用:APIレスポンスラッパー、設定ファイル管理
💡 重要なポイント:

  • 常にエラー処理をチェック
  • デシリアライゼーションにはポインタを渡す
  • 構造体タグを使用してJSONの命名規則を維持
  • 大量データにはストリーミングを使用

📝 演習

演習1:基礎練習

Student構造体(名前、年齢、成績リストを持つ)を定義し、以下を実装するプログラムを書いてください:

  1. 3つの学生オブジェクトを作成
  2. JSON配列にシリアライズしてプリティプリント
  3. 構造体にデシリアライズして情報を出力

演習2:中級練習

シンプルなJSON設定マネージャーを実装してください:

  1. アプリケーション設定構造体を定義(サーバー、データベース、ログなどを含む)
  2. LoadConfig(filename)関数でファイルから設定を読み込む
  3. SaveConfig(filename, config)関数で設定をファイルに保存
  4. 設定バリデーションを実装

演習3:上級練習

JSON-RPCメッセージハンドラーを実装してください:

  1. リクエストとレスポンスの構造を定義
  2. json.RawMessageで遅延解析を使用
  3. リクエストメソッド名に基づいて異なるハンドラー関数にディスパッチ
  4. バッチリクエスト処理をサポート
GO
// ヒント:JSON-RPCリクエスト形式
type RPCRequest struct {
    JSONRPC string          `json:"jsonrpc"`
    Method  string          `json:"method"`
    Params  json.RawMessage `json:"params"`
    ID      interface{}     `json:"id"`
}

type RPCResponse struct {
    JSONRPC string      `json:"jsonrpc"`
    Result  interface{} `json:"result,omitempty"`
    Error   *RPCError   `json:"error,omitempty"`
    ID      interface{} `json:"id"`
}

次のレッスン

このレッスンを完了したら、レッスン22:HTTPサービスに進んでください。Goを使ってHTTPサーバーとクライアントを構築する方法を学びます。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%