3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Goのjsonエンコーダ/デコーダライブラリの比較

Last updated at Posted at 2025-12-16

この記事は、サイバーエージェント26卒内定者 Advent Calendarの17日目の記事です。

はじめに

Go 1.25にて、標準ライブラリの encoding/json の最新版である encoding/json/v2 が実験的に導入されました。
私自身、これまでは既存の標準ライブラリを使うことが多かったのですが、内定者アルバイト先で go-json が採用されていたことをきっかけに、他のライブラリについても関心を持つようになりました。
そこで本記事では、標準ライブラリ(v1/v2)をはじめとする主要なJSONエンコーダ/デコーダについて、サンプルコードを交えて紹介するとともに、ベンチマークによる性能比較を行います。

TL; DR

  • jsonのMarshal, Unmarshalに関するライブラリを紹介し、ベンチマークを行いました
  • go-jsonがjsonのデコードが最も高速で、エンコードでも高性能を発揮し、パフォーマンス的に最も優れており、encoding/jsonからの置き換えが可能であり、より高速な処理が必要な場合は最適だと思います
  • 標準ライブラリのencoding/jsonは、Unmarshalにおいて最もパフォーマンスが悪く、encoding/json/v2ではパフォーマンスが向上していました
  • easyjsonは自動生成されたコードでjsonのエンコード・デコードを行うため、リフレクションを使用せずメモリ効率が最も良かったです
  • Marshalでは、segmentio/encoding/jsonが最も高速という結果になり、エンコード処理で高いパフォーマンスが必要な場合、最適かと思います。こちらもencoding/jsonと置き換え可能です
  • 大規模なjsonデータの特定の値の取得、更新を最速で行いたい場合はgjsonsjsonです

主なjsonエンコーダ/デコーダ

1. encoding/json

  • Goの標準ライブラリ
  • 標準ライブラリであり公式がサポート
  • 依存関係がない
  • リフレクションを多用しており、他のライブラリに比べパフォーマンス面が課題

package main

import (
	"encoding/json"
	"fmt"
)

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func main() {
	u := User{ID: 1, Name: "Gopher"}

	// Marshal
	b, _ := json.Marshal(u)
	fmt.Println("Marshal:", string(b))

	// Unmarshal
	var u2 User
	json.Unmarshal(b, &u2)
	fmt.Printf("Unmarshal: %+v\n", u2)
}

2. encoding/json/v2

  • Go 1.25で実験的に追加されている標準ライブラリ
  • GOEXPERIMENT=jsonv2 を有効にすることで使用可能
  • アロケーションのパフォーマンスの大幅向上

package main

import (
	"fmt"

	jsonv2 "encoding/json/v2"
)

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func main() {
	u := User{ID: 1, Name: "Gopher"}

	// Marshal
	b, _ := jsonv2.Marshal(u)
	fmt.Println("Marshal:", string(b))

	// Unmarshal
	var u2 User
	jsonv2.Unmarshal(b, &u2)
	fmt.Printf("Unmarshal: %+v\n", u2)
}

3. go-json

  • Go の encoding/json と互換性のある高速 JSON エンコーダ/デコーダ
  • encoding/jsonのメソッドをそのまま置き換えることができる
  • 構造体のフィールドを型安全に動的にフィルタリングできる
  • リフレクションの利用の制限、go-reflect でリフレクションをゼロアロケーションで利用可能などパフォーマンスで最適化
  • スター数:3.6k

package main

import (
    "fmt"
    json "github.com/goccy/go-json" 
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func main() {
    u := User{ID: 1, Name: "Gopher"}
    
    // Marshal
    b, _ := json.Marshal(u)
    fmt.Println(string(b))

    // Unmarshal
    var u2 User
    json.Unmarshal(b, &u2)
}

4. json-iterator

  • リフレクションのキャッシュ化などで高速化
  • 型ごとのエンコーダ/デコーダのキャッシング
  • スター数:13.9k

package main

import (
	"fmt"

	jsoniter "github.com/json-iterator/go"
)

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func main() {
	var json = jsoniter.ConfigCompatibleWithStandardLibrary
	u := User{ID: 1, Name: "Gopher"}

	// Marshal
	b, _ := json.Marshal(u)
	fmt.Println("Marshal:", string(b))

	// Unmarshal
	var u2 User
	json.Unmarshal(b, &u2)
	fmt.Printf("Unmarshal: %+v\n", u2)
}

5. gojay

  • リフレクションを使用しないため、高速
  • ゼロアロケーション
  • 構造体に対し、メソッドを定義する必要がある
  • コードの記述量が多くメンテナンスコストがかかる
  • スター数:2.1k

package main

import (
	"fmt"

	"github.com/francoispqt/gojay"
)

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func (u *User) MarshalJSONObject(enc *gojay.Encoder) {
	enc.IntKey("id", u.ID)
	enc.StringKey("name", u.Name)
}

func (u *User) IsNil() bool {
	return u == nil
}

func (u *User) UnmarshalJSONObject(dec *gojay.Decoder, key string) error {
	switch key {
	case "id":
		return dec.Int(&u.ID)
	case "name":
		return dec.String(&u.Name)
	}
	return nil
}

func (u *User) NKeys() int {
	return 2
}

func main() {
	u := &User{ID: 1, Name: "Gopher"}

	// Marshal
	b, _ := gojay.Marshal(u)
	fmt.Println("Marshal:", string(b))

	// Unmarshal
	u2 := &User{}
	gojay.Unmarshal(b, u2)
	fmt.Printf("Unmarshal: %+v\n", u2)
}

6. sonic

  • JIT (just-in-time compiling) と SIMD (single-instruction-multiple-data) によって高速化
  • JSON全体を構造体に変換せず、特定のキーだけを取得・変更するsonic.Getを提供
  • Go1.24.0 はサポートしていない→ issue
  • スター数:9k
package main

import (
	"fmt"

	"github.com/bytedance/sonic"
)

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func main() {
	u := User{ID: 1, Name: "Gopher"}

	// Marshal
	b, _ := sonic.Marshal(u)
	fmt.Println("Marshal:", string(b))

	// Unmarshal
	var u2 User
	sonic.Unmarshal(b, &u2)
	fmt.Printf("Unmarshal: %+v\n", u2)
}

7. sonnet

  • Go標準ライブラリと完全に互換性
  • 標準ライブラリの"encoding/json" に依存していない
  • Pure Goで実現されている
  • スター数:301

package main

import (
	"fmt"

	"github.com/sugawarayuuta/sonnet"
)

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func main() {
	u := User{ID: 1, Name: "Gopher"}

	// Marshal
	b, _ := sonnet.Marshal(u)
	fmt.Println("Marshal:", string(b))

	// Unmarshal
	var u2 User
	sonnet.Unmarshal(b, &u2)
	fmt.Printf("Unmarshal: %+v\n", u2)
}

8. segmentio

  • 標準ライブラリと完全な互換性
  • 不要な動的メモリ割り当てをなくし、reflect packageへの呼び出しがほとんど発生せずメモリ効率が良い
  • 内部でunsafeパッケージを使用
  • スター数:1k

package main

import (
	"fmt"

	json "github.com/segmentio/encoding/json"
)

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func main() {
	u := User{ID: 1, Name: "Gopher"}

	// Marshal
	b, _ := json.Marshal(u)
	fmt.Println("Marshal:", string(b))

	// Unmarshal
	var u2 User
	json.Unmarshal(b, &u2)
	fmt.Printf("Unmarshal: %+v\n", u2)
}

9. easyjson

  • 構造体を定義し、構造体に適切なMarshaler・Unmarshaler関数を自動生成
  • スネークケース形式の名前の生成やデフォルトの動作の有効化など
  • オブジェクトキーは大文字と小文字が区別される
  • スター数:4.8k

  1. typesを定義
  2. easyjson -all models/types.go で自動生成
  3. 生成されたメソッドをimportして使用する
easyjson_demo/
├── models/
   ├── types.go              # 構造体定義
   └── types_easyjson.go     # 自動生成されたコード
├── main.go                   # メインプログラム
├── go.mod
└── go.sum
models/types.go
package models

//easyjson:json
type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}
models/types_easyjson.go
// 自動生成のコードのため省略しています
// MarshalJSON supports json.Marshaler interface
func (v User) MarshalJSON() ([]byte, error) {

// UnmarshalJSON supports json.Unmarshaler interface
func (v *User) UnmarshalJSON(data []byte) error {
main.go
package main

import (
	"easyjson_demo/models"
	"fmt"
)

func main() {
	user := models.User{
		ID:   1,
		Name: "Gopher",
	}

	// MarshalJSON
	b, err := user.MarshalJSON()
	if err != nil {
		panic(err)
	}
	fmt.Println("Marshal:", string(b))

	// UnmarshalJSON
	var user2 models.User
	err = user2.UnmarshalJSON([]byte(string(b)))
	if err != nil {
		panic(err)
	}
	fmt.Printf("Unmarshal: %+v\n", user2)
}

10 gjson

  • 指定されたパスからJSONの中の特定の値を高速に取り出すことができる
  • JSONの特定の値の抽出に優れており、Unmarshalはできない
  • 関数修飾子とパスチェーンのサポート
  • String(), Int(), Bool() で取り出した値のキャストが容易
  • エンコードはサポートしていない
  • スター数:15.4k

package main

import (
	"encoding/json"
	"fmt"

	"github.com/tidwall/gjson"
)

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func main() {
	u := User{ID: 1, Name: "Gopher"}

	// Marshal (encoding/jsonを使用)
	b, _ := json.Marshal(u)
	fmt.Println("Marshal:", string(b))

	// gjson.Get で値を取得
	result := gjson.Parse(string(b))
	id := result.Get("id").Int()
	name := result.Get("name").String()

	// Unmarshal相当(手動で構造体に設定)
	u2 := User{
		ID:   int(id),
		Name: name,
	}
	fmt.Printf("Unmarshal: %+v\n", u2)
}

11. sjson

  • gjsonと同じ開発者
  • gjsonに対し、エンコードに特化
  • 構造体の定義が不要で、JSONの一部に高速で書き込みが可能
  • スター数:2.7k

package main

import "github.com/tidwall/sjson"

const jsonString = `{"id":1,"name":"Anderson"}`

func main() {
	// デコード・構造体の定義不要で書き込める
	value, _ := sjson.Set(jsonString, "name", "Gopher")
	println(value)
}

12. fastjson

  • パーサー
  • input JSON の解析は1回のみで、複数のフィールドにアクセスするケースではgjsonより優れている
  • リフレクションを使わず、アロケーションを最小限に抑える設計
  • JSON内の 複数の異なるフィールド にアクセスする必要がある場合に優れている
  • スター数:2.4k

package main

import (
	"encoding/json"
	"fmt"

	"github.com/valyala/fastjson"
)

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func main() {
	u := User{ID: 1, Name: "Gopher"}

	// Marshal (encoding/jsonを使用)
	b, _ := json.Marshal(u)
	fmt.Println("Marshal:", string(b))

	// Parse
	var p fastjson.Parser
	v, _ := p.Parse(string(b))

	// Get values
	id := v.GetInt("id")
	name := string(v.GetStringBytes("name"))

	// Unmarshal相当(手動で構造体に設定)
	u2 := User{
		ID:   id,
		Name: name,
	}
	fmt.Printf("Unmarshal: %+v\n", u2)
}

比較

  • go-json/benchmarksに今回紹介したライブラリを追加し、合計12のツールに対してベンチマークを行いました
  • パーサーのgjson, fastjsonはUnmarshalのみ、書き込みのsjsonはEncodeのみ実行しました
    ※gjsonは、Unmarshalの機能を持っていないため、json→構造体になるように全ての値に対して取得する処理を行っています

テストデータ

  • Small Struct(小規模データ)
    • サイズ:143 bytes
  • Medium Struct(中規模データ)
    • サイズ:2,071 bytes
  • Large Struce(大規模データ)
    • サイズ:28,123 bytes

テスト項目

  • Unmarshal
    • json→構造体
  • Encode
    • 構造体 → JSON(キャッシュなし)

ベンチマーク環境

  • OS: darwin (macOS)
  • アーキテクチャ: arm64
  • CPU: Apple M4 Max
  • Go バージョン: 1.25.5

結果

Small Struct

Unmarshal

Rank ライブラリ 処理時間 (ns/op) Memory (B/op) Allocation (allocs/op)
1 go-json 178.8 256 2
2 easyjson 190.4 0 0
3 gojay 217.6 256 2
4 sonnet 337.5 320 7
5 json-iterator 375.3 208 13
6 segmentio 399.3 192 5
7 sonic 514.2 420 4
8 encoding/json/v2 594.5 112 1
9 gjson 689.5 96 6
10 fastjson 791.1 3560 11
11 encoding/json 1041.0 416 9

Encode

Rank ライブラリ 処理時間 (ns/op) Memory (B/op) Allocation (allocs/op)
1 segmentio 126.8 256 2
2 go-json 199.9 432 2
3 encoding/json 281.2 256 2
4 encoding/json/v2 285.3 256 2
5 sonic 289.5 278 3
6 easyjson 304.9 720 4
7 sonnet 321.7 600 6
8 gojay 326.1 624 2
9 json-iterator 419.2 264 3
10 sjson 1602.0 2922 48

Medium Struct

Unmarshal

Rank ライブラリ 処理時間 (ns/op) Memory (B/op) Allocation (allocs/op)
1 go-json 1404.0 2426 8
2 gojay 1937.0 2425 8
3 sonic 2004.0 3044 13
4 easyjson 2439.0 64 4
5 segmentio 2535.0 272 9
6 json-iterator 2936.0 768 82
7 sonnet 3046.0 336 11
8 encoding/json/v2 3376.0 72 4
9 fastjson 4365.0 18648 54
10 gjson 5248.0 488 22
11 encoding/json 8744.0 624 18

Encode

Rank ライブラリ 処理時間 (ns/op) Memory (B/op) Allocation (allocs/op)
1 segmentio 397.5 609 15
2 easyjson 459.0 896 4
3 go-json 525.5 864 15
4 sonnet 600.0 952 19
5 gojay 764.3 800 15
6 encoding/json/v2 839.3 608 15
7 encoding/json 864.8 608 15
8 sonic 913.3 646 16
9 json-iterator 1129.0 616 16
10 sjson 1149.0 2290 24

Large Struct

Unmarshal

Rank ライブラリ 処理時間 (ns/op) Memory (B/op) Allocation (allocs/op)
1 go-json 19086.0 30530 67
2 gojay 25176.0 31005 77
3 sonic 25410.0 32851 71
4 sonnet 28210.0 3670 131
5 encoding/json/v2 37170.0 32 1
6 easyjson 39105.0 0 0
7 segmentio 49599.0 4128 132
8 json-iterator 55895.0 16359 1268
9 fastjson 57889.0 275241 540
10 gjson 63889.0 29550 72
11 encoding/json 122987.0 4432 147

Encode

Rank ライブラリ 処理時間 (ns/op) Memory (B/op) Allocation (allocs/op)
1 segmentio 8610.0 14106 319
2 sonnet 9655.0 14404 323
3 easyjson 9943.0 14793 326
4 go-json 11386.0 20380 319
5 gojay 15101.0 27505 323
6 encoding/json/v2 15877.0 14124 319
7 encoding/json 16290.0 14110 319
8 sjson 17633.0 36110 333
9 sonic 18345.0 14717 320
10 json-iterator 19771.0 14117 320

Unmarshal(json→構造体)の性能はgo-jsonがSmall, Medium, Largeの全てにおいて高速でした。標準ライブラリのencoding/jsonは、Unmarshalにおいて最も遅かったです。一方encoding/json/v2は、Unmarshalでかなり高速化していると言えます。
easyjsonはUnmarshalにおいて、メモリ効率が最も良い結果となりました。これは自動生成されたコードで行い、リフレクションを使用しないことによるものだと思います。
Encode(構造体→json)ではsegmentioが最も高速という結果になりました。

まとめ

jsonのエンコーダ/デコーダのライブラリでも非常に多くあり、それぞれ特徴がありとても興味深かったです。また、標準のパッケージと互換性を大事にしているパッケージが多く使いやすいなと感じました。Webサーバー開発においてパフォーマンスは非常に重要な要件となってくるので、ライブラリの選定もよく考えるべきだと改めて感じました。メソッドの自動生成やパス指定、リフレクションを使わないなどパフォーマンスの最適化で様々なアプローチが取られており調べていて、とても勉強になりました。
今回は各ライブラリの概要だけをまとめましたが、内部実装も追って
また記事に適宜情報などを追加していきます。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?