Chapter 28: JSON Format Verification¶
Description¶
Test JSON serialization and deserialization with json.Marshal, json.Unmarshal, json.MarshalIndent, and assert.JSONEq. Verify field presence, omitempty behavior, indentation, round-trip correctness, and structural validation of nested JSON objects.
Code¶
type Order struct {
ID string `json:"id"`
Items []Item `json:"items"`
Total float64 `json:"total"`
Status string `json:"status"`
}
func FormatOrder(order Order) (string, error) {
data, err := json.MarshalIndent(order, "", " ")
return string(data), err
}
func ValidateOrderJSON(jsonStr string) error {
var raw map[string]interface{}
if err := json.Unmarshal([]byte(jsonStr), &raw); err != nil {
return fmt.Errorf("invalid JSON: %w", err)
}
required := []string{"id", "user_id", "items", "total", "status"}
for _, field := range required {
if _, ok := raw[field]; !ok {
return fmt.Errorf("missing required field: %s", field)
}
}
return nil
}
Test¶
func TestFormatOrder(t *testing.T) {
order := Order{ID: "ord_1", Items: []Item{{Name: "Widget", Quantity: 2, Price: 9.99}}, Total: 19.98}
jsonStr, err := FormatOrder(order)
require.NoError(t, err)
assert.True(t, strings.Contains(jsonStr, `"id": "ord_1"`))
assert.True(t, strings.HasPrefix(jsonStr, "{"))
}
func TestJSONEqAssert(t *testing.T) {
expected := `{"id":"1","user_id":"u1","items":[],"total":0,"status":"","created_at":"0001-01-01T00:00:00Z"}`
order, err := ParseOrder(`{"id":"1","user_id":"u1","items":[]}`)
require.NoError(t, err)
actual, _ := FormatOrder(*order)
assert.JSONEq(t, expected, actual)
}
func TestValidateOrderJSON(t *testing.T) {
t.Run("valid", func(t *testing.T) {
err := ValidateOrderJSON(`{"id":"1","user_id":"u1","items":[],"total":10,"status":"ok"}`)
assert.NoError(t, err)
})
t.Run("missing field", func(t *testing.T) {
err := ValidateOrderJSON(`{"id":"1"}`)
assert.Error(t, err)
assert.Contains(t, err.Error(), "missing required")
})
}
func TestFormatOrder_RoundTrip(t *testing.T) {
original := Order{ID: "ord_1", Items: []Item{{Name: "Gadget", Quantity: 1, Price: 49.99}}, Status: "shipped"}
jsonStr, _ := FormatOrder(original)
parsed, _ := ParseOrder(jsonStr)
assert.Equal(t, original.ID, parsed.ID)
assert.Len(t, parsed.Items, 1)
}
func TestContainsJSONKey(t *testing.T) {
assert.True(t, ContainsJSONKey(`{"found":true}`, "found"))
assert.False(t, ContainsJSONKey(`{"found":true}`, "missing"))
}
func TestNormalizeJSON(t *testing.T) {
n, _ := NormalizeJSON(`{ "b" : 2 , "a" : 1 }`)
assert.Equal(t, `{"a":1,"b":2}`, n)
}
func TestCompactJSON(t *testing.T) {
result := CompactJSON("{\n\t\"name\": \"test\"\n}")
assert.Equal(t, `{"name":"test"}`, result)
}
Testing Approach¶
JSON format verification:
assert.JSONEq— compares two JSON strings semantically, ignoring key order and whitespace. Use it instead ofassert.Equalwith raw strings to avoid brittle ordering dependencies.json.Validandjson.Unmarshalintomap[string]interface{}—IsJSON()usesjson.Validfor quick syntax checking.ValidateOrderJSONunmarshals into a generic map to check field presence without defining a struct.- Round-trip testing — create a struct → marshal → unmarshal → compare original and parsed. Verifies that all fields survive serialization without data loss.
MarshalIndentfor human readability —FormatOrderproduces indented JSON. Tests verify the prefix ({) and indentation.CompactJSONshows the reverse: minification.
View source code on GitHub