All Benchmarks

JSON Struct vs Map — Go Benchmark

Comparing JSON serialization performance between typed structs and generic maps (map[string]any).

jsonserializationstructmapencoding

When working with JSON in Go, you can either marshal and unmarshal into strongly typed structs or loosely typed maps (map[string]any). This benchmark compares the performance of both methods, covering both serialization (marshal) and deserialization (unmarshal). Strongly typed structs offer known type bounds for the reflections package, lowering runtime overhead and allocations.

linux/amd64AMD Ryzen 9 9950X3D 16-Core Processorbenchmarks/json-struct-vs-map
Compare at
CPUs

1 CPU

MarshalUnmarshal

32 CPUs

MarshalUnmarshal
Performance Comparison (lower is better)
CPU:

Struct Payload

Fastest (Marshal)Fastest (Unmarshal)

Serializes and deserializes JSON using a strictly typed struct. This is the idiomatic way to handle JSON in Go. Because the fields and types are known in advance, it significantly reduces allocations and execution time while providing compile-time type safety.

Performance — Struct Payload (lower is better)
CPU:
const JsonData = `{"id":12345,"name":"John Doe","email":"john.doe@example.com","is_active":true}`

func getStructData() StructPayload {
	return StructPayload{
		ID:       12345,
		Name:     "John Doe",
		Email:    "john.doe@example.com",
		IsActive: true,
	}
}

func BenchmarkStructPayload_marshal(b *testing.B) {
	data := getStructData()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		structMarshalSink, _ = json.Marshal(data)
	}
}

func BenchmarkStructPayload_unmarshal(b *testing.B) {
	data := []byte(JsonData)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = json.Unmarshal(data, &structUnmarshalSink)
	}
}
1 CPU

Marshal

4×faster(304%)thanMap Payload

Unmarshal

1.4×faster(39%)thanMap Payload
32 CPUs

Marshal

3.9×faster(285%)thanMap Payload

Unmarshal

1.5×faster(45%)thanMap Payload

Map Payload

Slowest (Marshal)Slowest (Unmarshal)

Serializes and deserializes JSON using a dynamic map[string]any. While this approach is flexible and can handle arbitrary JSON shapes, it comes with a high overhead in execution time and heap allocations, as the standard library has to allocate map entries and parse types dynamically.

Performance — Map Payload (lower is better)
CPU:
const JsonData = `{"id":12345,"name":"John Doe","email":"john.doe@example.com","is_active":true}`

func getMapData() map[string]any {
	return map[string]any{
		"id":        12345,
		"name":      "John Doe",
		"email":     "john.doe@example.com",
		"is_active": true,
	}
}

func BenchmarkMapPayload_marshal(b *testing.B) {
	data := getMapData()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		mapMarshalSink, _ = json.Marshal(data)
	}
}

func BenchmarkMapPayload_unmarshal(b *testing.B) {
	data := []byte(JsonData)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = json.Unmarshal(data, &mapUnmarshalSink)
	}
}
1 CPU

Marshal

4×slower(304%)thanStruct Payload

Unmarshal

1.4×slower(39%)thanStruct Payload
32 CPUs

Marshal

3.9×slower(285%)thanStruct Payload

Unmarshal

1.5×slower(45%)thanStruct Payload

Contributors