leptjson

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 16, 2025 License: MIT Imports: 2 Imported by: 0

README

我将为您创建第六单元的内容,主要关注JSON对象的解析。

从零开始的 JSON 库教程(六):解析对象 (Go语言版)

本文是《从零开始的 JSON 库教程》的第六个单元。本单元的源代码位于 json-tutorial/go-json-tutorial/tutorial06

本单元内容:

  1. JSON 对象语法
  2. 对象表示
  3. 解析对象
  4. 访问对象
  5. 内存管理
  6. 总结与练习
  7. 参考

1. JSON 对象语法

JSON 对象是一个无序的键值对集合,以花括号 {} 包围,键值对之间以逗号 , 分隔。每个键值对由键和值组成,键和值之间以冒号 : 分隔。键必须是字符串,值可以是任何 JSON 值类型。完整的语法如下:

object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D
member = string ws %x3A ws value

例如:

  • 空对象:{}
  • 包含单个键值对的对象:{"name": "value"}
  • 包含多个键值对的对象:{"name": "value", "age": 30, "isStudent": false}
  • 嵌套对象:{"person": {"name": "John", "age": 30}, "isActive": true}

2. 对象表示

在 Go 语言中,我们可以使用映射(map)来表示 JSON 对象。我们需要在 Value 结构体中添加一个 O 字段来存储对象值:

// Member 表示对象的成员(键值对)
type Member struct {
    K string // 键
    V *Value // 值
}

// Value 表示一个JSON值
type Value struct {
    Type   ValueType `json:"type"`   // 值类型
    N      float64   `json:"n"`      // 数字值(当Type为NUMBER时有效)
    S      string    `json:"s"`      // 字符串值(当Type为STRING时有效)
    A      []*Value  `json:"a"`      // 数组值(当Type为ARRAY时有效)
    O      []Member  `json:"o"`      // 对象值(当Type为OBJECT时有效)
}

我们使用 []Member 而不是 map[string]*Value 来存储对象的成员,这样可以保持成员的插入顺序,虽然 JSON 规范中对象是无序的,但保持顺序可以使输出更加可预测,也便于调试。

3. 解析对象

解析 JSON 对象的过程与解析数组类似,但需要处理键值对:

  1. 跳过开头的 {
  2. 如果遇到 },表示是空对象
  3. 否则,解析第一个键(必须是字符串)
  4. 解析冒号 :
  5. 解析值
  6. 然后循环处理:
    • 如果遇到 },表示对象结束
    • 如果遇到 ,,则解析下一个键值对
    • 否则,返回错误

以下是解析对象的核心代码:

// parseObject 解析对象值
func parseObject(c *context, v *Value) ParseError {
    c.index++ // 跳过开头的 {
    parseWhitespace(c)
    
    // 处理空对象的情况
    if c.index < len(c.json) && c.json[c.index] == '}' {
        c.index++
        v.Type = OBJECT
        v.O = make([]Member, 0)
        return PARSE_OK
    }
    
    var members []Member
    
    for {
        // 解析键(必须是字符串)
        if c.index >= len(c.json) || c.json[c.index] != '"' {
            return PARSE_MISS_KEY
        }
        
        var key string
        if err := parseStringRaw(c, &key, nil); err != PARSE_OK {
            return err
        }
        
        // 解析冒号
        parseWhitespace(c)
        if c.index >= len(c.json) || c.json[c.index] != ':' {
            return PARSE_MISS_COLON
        }
        c.index++
        parseWhitespace(c)
        
        // 解析值
        value := &Value{}
        if err := parseValue(c, value); err != PARSE_OK {
            return err
        }
        
        // 添加键值对
        members = append(members, Member{K: key, V: value})
        
        parseWhitespace(c)
        if c.index >= len(c.json) {
            return PARSE_MISS_COMMA_OR_CURLY_BRACKET
        }
        
        // 检查是否到达对象结束或需要继续解析
        if c.json[c.index] == '}' {
            c.index++
            v.Type = OBJECT
            v.O = members
            return PARSE_OK
        } else if c.json[c.index] == ',' {
            c.index++
            parseWhitespace(c)
        } else {
            return PARSE_MISS_COMMA_OR_CURLY_BRACKET
        }
    }
}

我们还需要在 parseValue 函数中添加对对象的处理:

// parseValue 解析JSON值
func parseValue(c *context, v *Value) ParseError {
    if c.index >= len(c.json) {
        return PARSE_EXPECT_VALUE
    }

    switch c.json[c.index] {
    case 'n':
        return parseNull(c, v)
    case 't':
        return parseTrue(c, v)
    case 'f':
        return parseFalse(c, v)
    case '"':
        return parseString(c, v)
    case '[':
        return parseArray(c, v)
    case '{':
        return parseObject(c, v)
    case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
        return parseNumber(c, v)
    default:
        return PARSE_INVALID_VALUE
    }
}

同时,我们需要添加新的错误类型:

// 解析错误常量
const (
    PARSE_OK                         ParseError = iota // 解析成功
    PARSE_EXPECT_VALUE                                 // 期望一个值
    PARSE_INVALID_VALUE                                // 无效的值
    PARSE_ROOT_NOT_SINGULAR                            // 根节点不唯一
    PARSE_NUMBER_TOO_BIG                               // 数字太大
    PARSE_MISS_QUOTATION_MARK                          // 缺少引号
    PARSE_INVALID_STRING_ESCAPE                        // 无效的转义序列
    PARSE_INVALID_STRING_CHAR                          // 无效的字符
    PARSE_INVALID_UNICODE_HEX                          // 无效的Unicode十六进制
    PARSE_INVALID_UNICODE_SURROGATE                    // 无效的Unicode代理对
    PARSE_MISS_COMMA_OR_SQUARE_BRACKET                 // 缺少逗号或方括号
    PARSE_MISS_KEY                                     // 缺少键
    PARSE_MISS_COLON                                   // 缺少冒号
    PARSE_MISS_COMMA_OR_CURLY_BRACKET                  // 缺少逗号或花括号
)

4. 访问对象

我们需要添加一些函数来访问对象的成员:

// GetObjectSize 获取JSON对象的大小
func GetObjectSize(v *Value) int {
    return len(v.O)
}

// GetObjectKey 获取JSON对象的键
func GetObjectKey(v *Value, index int) string {
    if index < 0 || index >= len(v.O) {
        return ""
    }
    return v.O[index].K
}

// GetObjectValue 获取JSON对象的值
func GetObjectValue(v *Value, index int) *Value {
    if index < 0 || index >= len(v.O) {
        return nil
    }
    return v.O[index].V
}

// FindObjectIndex 查找JSON对象中指定键的索引
func FindObjectIndex(v *Value, key string) int {
    for i, member := range v.O {
        if member.K == key {
            return i
        }
    }
    return -1
}

// GetObjectValueByKey 根据键获取JSON对象的值
func GetObjectValueByKey(v *Value, key string) *Value {
    index := FindObjectIndex(v, key)
    if index == -1 {
        return nil
    }
    return v.O[index].V
}

5. 内存管理

与数组类似,Go 语言的垃圾回收机制会自动处理对象的内存管理。但是,我们仍然需要注意一些内存使用的问题:

  1. 切片的容量:当我们使用 append 向切片添加成员时,如果超出当前容量,Go 会自动分配更大的底层数组。为了减少内存分配次数,我们可以预先分配足够的容量。

  2. 键的存储:我们直接使用 string 类型存储键,这样可以避免额外的内存分配和复制。

  3. 递归深度:解析嵌套对象时,我们使用递归调用 parseValue。在极端情况下,这可能导致栈溢出。在实际应用中,我们可能需要限制递归深度或使用非递归算法。

6. 总结与练习

本单元我们实现了 JSON 对象的解析,包括处理嵌套对象和各种错误情况。Go 语言的切片和映射机制使得实现相对简单,但我们仍然需要注意内存使用和性能优化。

练习:

  1. 实现一个 SetObject 函数,允许设置 Value 的对象值。
  2. 修改 parseObject 函数,预先分配一定容量的切片,比较性能差异。
  3. 实现对象的生成功能,将 Value 中的对象值转换为 JSON 字符串。
  4. 添加一个 SetObjectValue 函数,用于设置对象中指定键的值。
  5. 实现一个 RemoveObjectMember 函数,用于删除对象中的成员。

7. 参考

  1. RFC 7159 - The JavaScript Object Notation (JSON) Data Interchange Format
  2. Go 映射:用法和内部实现
  3. Go 切片:用法和内部实现
  4. Go 内存管理

8. 运行测试

单元测试

在Go语言中,测试是通过内置的go test命令来运行的。要运行我们的JSON库测试,可以使用以下命令:

# 在tutorial06目录下运行所有测试
go test

# 运行测试并显示详细输出
go test -v

# 运行特定的测试函数
go test -run TestParseObject
基准测试

基准测试用于测量代码的性能。要运行基准测试,可以使用以下命令:

# 运行所有基准测试
go test -bench=.

# 运行特定的基准测试
go test -bench=BenchmarkParseObject

# 运行基准测试并显示内存分配信息
go test -bench=. -benchmem

基准测试的输出示例:

BenchmarkParseNull-8           10000000               118 ns/op              0 B/op          0 allocs/op
BenchmarkParseTrue-8           10000000               119 ns/op              0 B/op          0 allocs/op
BenchmarkParseFalse-8          10000000               120 ns/op              0 B/op          0 allocs/op
BenchmarkParseNumber-8          3000000               435 ns/op              0 B/op          0 allocs/op
BenchmarkParseString-8          2000000               756 ns/op             32 B/op          1 allocs/op
BenchmarkParseArray-8            500000              3254 ns/op            224 B/op          9 allocs/op
BenchmarkParseObject-8           300000              4567 ns/op            336 B/op         12 allocs/op

从结果可以看出:

  1. 解析对象比解析数组要慢,这是因为对象解析涉及更复杂的处理逻辑和更多的内存分配。
  2. 对象解析会导致多次内存分配,这是因为我们需要为对象成员分配内存,并且可能需要多次扩展切片容量。

9. 常见问题

  1. 为什么使用[]Member而不是map[string]*Value来存储对象成员?

    使用切片可以保持成员的插入顺序,虽然 JSON 规范中对象是无序的,但保持顺序可以使输出更加可预测,也便于调试。此外,对于小型对象,线性查找可能比哈希查找更快。

  2. 如何处理对象中的重复键?

    JSON 规范没有明确规定如何处理重复键,但大多数实现会保留最后一个键值对。在我们的实现中,我们简单地将所有键值对添加到切片中,然后在查找时返回第一个匹配的键值对。如果需要处理重复键,可以在添加键值对之前检查键是否已存在。

  3. 解析嵌套对象有什么挑战?

    解析嵌套对象的主要挑战是处理递归结构。在我们的实现中,parseObject 函数会递归调用 parseValue 函数来解析对象值,而 parseValue 函数又可能调用 parseObject 函数来解析嵌套对象。这种递归结构需要小心处理,以避免栈溢出。

  4. 如何优化对象解析的性能?

    优化对象解析性能的几种方法:

    • 预分配切片容量,减少动态扩容的次数
    • 使用哈希表来加速键的查找
    • 使用迭代而不是递归来解析嵌套结构,避免栈溢出
    • 使用内存池来重用 Value 结构体,减少内存分配
    • 对于大型对象,考虑使用流式解析,而不是一次性加载整个对象

10. 下一步

在下一个单元中,我们将实现 JSON 的生成功能,将 Value 结构体转换为 JSON 字符串。我们将学习如何处理各种类型的值,包括数字、字符串、数组和对象,以及如何处理特殊字符和格式化输出。

Documentation

Overview

leptjson.go - Go语言版JSON库

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func FindObjectIndex

func FindObjectIndex(v *Value, key string) int

FindObjectIndex 查找JSON对象中指定键的索引

func GetArraySize

func GetArraySize(v *Value) int

GetArraySize 获取JSON数组的大小

func GetBoolean

func GetBoolean(v *Value) bool

GetBoolean 获取JSON布尔值

func GetNumber

func GetNumber(v *Value) float64

GetNumber 获取JSON数字值

func GetObjectKey

func GetObjectKey(v *Value, index int) string

GetObjectKey 获取JSON对象的键

func GetObjectSize

func GetObjectSize(v *Value) int

GetObjectSize 获取JSON对象的大小

func GetString

func GetString(v *Value) string

GetString 获取JSON字符串值

func SetBoolean

func SetBoolean(v *Value, b bool)

SetBoolean 设置JSON布尔值

func SetNumber

func SetNumber(v *Value, n float64)

SetNumber 设置JSON数字值

func SetString

func SetString(v *Value, s string)

SetString 设置JSON字符串值

Types

type Member

type Member struct {
	K string // 键
	V *Value // 值
}

Member 表示对象的成员(键值对)

type ParseError

type ParseError int

ParseError 表示解析错误

const (
	PARSE_OK                           ParseError = iota // 解析成功
	PARSE_EXPECT_VALUE                                   // 期望一个值
	PARSE_INVALID_VALUE                                  // 无效的值
	PARSE_ROOT_NOT_SINGULAR                              // 根节点不唯一
	PARSE_NUMBER_TOO_BIG                                 // 数字太大
	PARSE_MISS_QUOTATION_MARK                            // 缺少引号
	PARSE_INVALID_STRING_ESCAPE                          // 无效的转义序列
	PARSE_INVALID_STRING_CHAR                            // 无效的字符
	PARSE_INVALID_UNICODE_HEX                            // 无效的Unicode十六进制
	PARSE_INVALID_UNICODE_SURROGATE                      // 无效的Unicode代理对
	PARSE_MISS_COMMA_OR_SQUARE_BRACKET                   // 缺少逗号或方括号
	PARSE_MISS_KEY                                       // 缺少键
	PARSE_MISS_COLON                                     // 缺少冒号
	PARSE_MISS_COMMA_OR_CURLY_BRACKET                    // 缺少逗号或花括号
)

解析错误常量

func Parse

func Parse(v *Value, json string) ParseError

Parse 解析JSON文本

解析步骤: 1. 跳过前导空白字符 2. 解析JSON值 3. 跳过后续空白字符 4. 检查是否还有额外内容(这将导致PARSE_ROOT_NOT_SINGULAR错误)

Example (Array)

示例 - 解析数组

v := Value{}
Parse(&v, "[1,2,3]")
fmt.Println(v.String())
Output:
[1,2,3]
Example (Error)

示例 - 错误处理

v := Value{}
err := Parse(&v, "[1,2,")
fmt.Println(err)
Output:
缺少逗号或方括号
Example (Error02)
v := Value{}
err := Parse(&v, "{\"name\":\"John\",\"age\":")
fmt.Println(err)
Output:
期望一个值
Example (False)

示例 - 解析false值

v := Value{}
Parse(&v, "false")
fmt.Println(v.String())
Output:
false
Example (NestedArray)

示例 - 解析嵌套数组

v := Value{}
Parse(&v, "[[1,2],[3,4],5]")
fmt.Println(v.String())
Output:
[[1,2],[3,4],5]
Example (NestedObject)

示例 - 解析嵌套对象

v := Value{}
Parse(&v, "{\"person\":{\"name\":\"John\",\"age\":30},\"isActive\":true}")
fmt.Println(v.String())
Output:
{"person":{"name":"John","age":30},"isActive":true}
Example (Null)

示例 - 解析null值

v := Value{}
Parse(&v, "null")
fmt.Println(v.String())
Output:
null
Example (Number)

示例 - 解析数字

v := Value{}
Parse(&v, "123.456")
fmt.Println(v.String())
Output:
123.456
Example (Object)

示例 - 解析对象

v := Value{}
Parse(&v, "{\"name\":\"John\",\"age\":30}")
fmt.Println(v.String())
Output:
{"name":"John","age":30}
Example (String)

示例 - 解析字符串

v := Value{}
Parse(&v, "\"Hello, World!\"")
fmt.Println(v.String())
Output:
"Hello, World!"
Example (True)

示例 - 解析true值

v := Value{}
Parse(&v, "true")
fmt.Println(v.String())
Output:
true

func (ParseError) Error

func (e ParseError) Error() string

错误信息

type Value

type Value struct {
	Type ValueType `json:"type"` // 值类型
	N    float64   `json:"n"`    // 数字值(当Type为NUMBER时有效)
	S    string    `json:"s"`    // 字符串值(当Type为STRING时有效)
	A    []*Value  `json:"a"`    // 数组值(当Type为ARRAY时有效)
	O    []Member  `json:"o"`    // 对象值(当Type为OBJECT时有效)
}

Value 表示一个JSON值

func GetArrayElement

func GetArrayElement(v *Value, index int) *Value

GetArrayElement 获取JSON数组的元素

func GetObjectValue

func GetObjectValue(v *Value, index int) *Value

GetObjectValue 获取JSON对象的值

func GetObjectValueByKey

func GetObjectValueByKey(v *Value, key string) *Value

GetObjectValueByKey 根据键获取JSON对象的值

func (Value) String

func (v Value) String() string

String 返回Value的字符串表示

type ValueType

type ValueType int

ValueType 表示JSON值的类型

const (
	NULL ValueType = iota
	FALSE
	TRUE
	NUMBER
	STRING
	ARRAY
	OBJECT
)

JSON值类型常量

func GetType

func GetType(v *Value) ValueType

GetType 获取JSON值的类型

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL