Go语言JSON序列化实战:从基础到进阶的优雅实现

· 4min · Paxon Qiao

Go语言JSON序列化实战:从基础到进阶的优雅实现

在Go语言开发中,JSON序列化是一个绕不开的核心技能。无论是API开发、数据传输,还是日志处理,我们都需要将Go结构体高效、准确地转换为JSON格式。今天,我们将深入剖析一组实用的JSON序列化函数,从基础字节切片转换到格式化输出,带你掌握Go中JSON处理的精髓。无论你是初学者还是进阶开发者,这篇文章都将为你提供灵感和实践参考!

本文介绍了一组Go语言中实现JSON序列化的实用函数,包括ToJSON、ToJSONString和ToPrettyJSON,并通过详细的测试用例展示了它们在不同场景下的表现。代码不仅覆盖了基本的数据转换,还处理了nil值、不可序列化输入及循环引用等边缘情况。无论你是需要快速生成JSON字节流,还是追求格式化输出的优雅呈现,这些实现都能满足需求。文章还附带测试代码和参考资源,助你快速上手并优化项目。

实操

package experiments

import (
	"encoding/json"
	"fmt"
)

// ToJSON 将一个 Go 语言值转换为 JSON 格式的字节切片。
// 该函数接受一个 interface{} 类型的参数 v,这意味着它可以接受任何类型的值。
// 使用 json.Marshal 函数将 v 转换为 JSON 格式。如果转换过程中发生错误,这个错误将被忽略。
// 返回值是转换后的 JSON 格式的字节切片。如果 v 无法被转换为 JSON 格式,则返回空的字节切片。
func ToJSON(v interface{}) []byte {
	b, _ := json.Marshal(v)
	return b
}

// ToJSONString 将给定的值转换为JSON格式的字符串。
// 如果输入值为nil,则根据需求决定返回"null"或空字符串。
// 参数:
//
//	v - 要转换为JSON字符串的值。
//
// 返回值:
//
//	JSON格式的字符串表示,如果发生错误则返回空字符串。
func ToJSONString(v interface{}) string {
	// 检查输入值是否为nil,如果是,则根据需求返回"null"或空字符串。
	if v == nil {
		// 根据需求决定是否返回 "null" 或空字符串
		return "null"
	}

	// 尝试将输入值转换为JSON格式的字节切片。
	b, err := json.Marshal(v)
	// 如果转换过程中发生错误,则记录错误信息并返回空字符串。
	if err != nil {
		// 记录错误日志(如果需要)
		fmt.Println("Error during JSON marshaling:", err)
		// 返回空字符串或自定义错误信息
		return ""
	}
	// 如果转换成功,则将字节切片转换为字符串并返回。
	return string(b)
}

// ToPrettyJSON 将给定的接口类型数据转换为格式化的 JSON 字符串。
// 如果输入数据为 nil,返回 "null" 的 JSON 表示。
// 如果输入数据无法序列化为 JSON,返回包含错误信息的 JSON 字符串。
func ToPrettyJSON(v interface{}) string {
	// 如果 v 为 nil,直接返回 "null" 的 JSON 表示
	if v == nil {
		return "null"
	}

	// 使用 json.MarshalIndent 将数据格式化为带缩进的 JSON 字符串
	b, err := json.MarshalIndent(v, "", "  ")
	if err != nil {
		// 如果序列化失败,返回一个明确的错误提示
		return fmt.Sprintf(`{"error": "%v"}`, err)
	}

	// 返回格式化后的 JSON 字符串
	return string(b)
}

这段代码为我们提供了三种 JSON 序列化方式:ToJSON 快速生成字节切片,适合高效传输;ToJSONString 输出字符串,兼顾 nil 和错误处理,便于日志或调试;ToPrettyJSON 则带来格式化输出,调试时一目了然。无论是性能还是可读性,这组函数都为 Go 开发者提供了实用工具,接下来我们通过测试看看它们的实际表现吧!

测试

package tests

import (
	"github.com/qiaopengjun5162/GopherNest/experiments"
	"strings"
	"testing"
)

// TestToJSON_ValidInput_ReturnsJSON tests that ToJSON correctly converts a valid input to JSON.
func TestToJSON_ValidInput_ReturnsJSON(t *testing.T) {
	type Person struct {
		Name string
		Age  int
	}

	person := Person{Name: "John Doe", Age: 30}
	expectedJSON := `{"Name":"John Doe","Age":30}`

	actualJSON := experiments.ToJSON(person)

	if string(actualJSON) != expectedJSON {
		t.Errorf("ToJSON(%v) = %s; want %s", person, actualJSON, expectedJSON)
	}
}

// TestToJSON_InvalidInput_ReturnsEmpty tests that ToJSON returns an empty byte slice for invalid input.
func TestToJSON_InvalidInput_ReturnsEmpty(t *testing.T) {
	type Invalid struct {
		Foo func() // Functions are not JSON serializable
	}

	invalid := Invalid{Foo: func() {}}

	actualJSON := experiments.ToJSON(invalid)

	if len(actualJSON) != 0 {
		t.Errorf("ToJSON(%v) = %s; want empty byte slice", invalid, actualJSON)
	}
}

// TestToJSONString_NilInput_ReturnsNull tests the behavior of ToJSONString when the input is nil.
func TestToJSONString_NilInput_ReturnsNull(t *testing.T) {
	result := experiments.ToJSONString(nil)
	if result != "null" {
		t.Errorf("Expected 'null' for nil input, got '%s'", result)
	}
}

// TestToJSONString_ValidInput_ReturnsJSONString tests the behavior of ToJSONString with a valid input.
func TestToJSONString_ValidInput_ReturnsJSONString(t *testing.T) {
	input := map[string]string{"key": "value"}
	expected := `{"key":"value"}`
	result := experiments.ToJSONString(input)
	if result != expected {
		t.Errorf("Expected '%s', got '%s'", expected, result)
	}
}

// TestToJSONString_UnserializableInput_ReturnsEmptyString tests the behavior of ToJSONString with an unserializable input.
func TestToJSONString_UnserializableInput_ReturnsEmptyString(t *testing.T) {
	type Unserializable struct {
		Chan chan int
	}
	input := Unserializable{Chan: make(chan int)}
	result := experiments.ToJSONString(input)
	if result != "" {
		t.Errorf("Expected empty string for unserializable input, got '%s'", result)
	}
}

func TestToPrettyJSON_NilInput_ReturnsNull(t *testing.T) {
	result := experiments.ToPrettyJSON(nil)
	if result != "null" {
		t.Errorf("Expected 'null' for nil input, got %s", result)
	}
}

func TestToPrettyJSON_ValidStruct_ReturnsFormattedJSON(t *testing.T) {
	type Person struct {
		Name string `json:"name"`
		Age  int    `json:"age"`
	}
	person := Person{Name: "John Doe", Age: 30}
	expected := `{
  "name": "John Doe",
  "age": 30
}`
	result := experiments.ToPrettyJSON(person)
	if result != expected {
		t.Errorf("Expected %s, got %s", expected, result)
	}
}

func TestToPrettyJSON_CircularReference_ReturnsErrorJSON(t *testing.T) {
	type Node struct {
		Next *Node `json:"next"`
	}
	node := &Node{}
	node.Next = node // 创建循环引用

	result := experiments.ToPrettyJSON(node)
	if !strings.Contains(result, "error") {
		t.Errorf("Expected error JSON for circular reference, got %s", result)
	}
}

总结

通过本文,我们探索了Go语言中JSON序列化的三种实用实现:ToJSON提供了高效的字节切片输出,ToJSONString兼顾了字符串转换的灵活性,而ToPrettyJSON则以格式化输出提升了可读性。这些函数在实际项目中各有千秋,能够应对从简单数据处理到复杂调试场景的多种需求。结合精心设计的测试用例,我们不仅验证了功能的正确性,还展示了如何处理异常情况。希望这些代码和思路能为你的Go开发之旅增添一份助力!想深入学习?文末参考资源不容错过!

参考

  • https://go.dev/dl/
  • https://talkgo.org/
  • https://github.com/go-playground/validator
  • https://go.dev/
  • https://books.studygolang.com/advanced-go-programming-book/
  • https://github.com/golang-china/gopl-zh

Categories Go
Tags Go