Skip to content

删除函数

json 包提供的 JSON 删除函数,用于移除指定路径的节点,并可选地清理因删除产生的空父节点。所有删除函数都是不可变的——返回修改后的新 JSON 字符串,原字符串保持不变;出错时返回原输入。

Delete

签名:func Delete(jsonStr, path string, cfg ...Config) (string, error)

删除指定路径的值,返回修改后的 JSON 字符串。

参数

名称类型必需说明
jsonStrstringJSON 字符串
pathstring路径表达式(点号、索引、通配符、切片、多字段)
cfgConfig可选配置(影响清理与校验行为)

返回值

返回值说明
result string修改后的 JSON 字符串(成功);出错时为原 jsonStr
err error成功为 nil;失败为包装了底层哨兵错误的 *JsonsError

删除对象属性

删除单个嵌套属性,返回不包含该键的新对象。

go
package main

import (
	"fmt"

	"github.com/cybergodev/json"
)

func main() {
	data := `{"user":{"name":"Alice","temp":"value","age":30}}`

	result, err := json.Delete(data, "user.temp")
	if err != nil {
		panic(err)
	}
	fmt.Println(result)
	// 输出:{"user":{"age":30,"name":"Alice"}}
}

删除数组元素

删除数组中的元素(索引从 0 开始)。元素被移除而非置空,后续元素自动前移、索引重排,不留空洞。

go
package main

import (
	"fmt"

	"github.com/cybergodev/json"
)

func main() {
	data := `{"items":["a","b","c","d"]}`

	// 删除索引 1 的元素 "b","c"/"d" 自动前移
	result, err := json.Delete(data, "items[1]")
	if err != nil {
		panic(err)
	}
	fmt.Println(result)
	// 输出:{"items":["a","c","d"]}
}

支持负数索引(从末尾倒数,-1 为最后一个):

go
package main

import (
	"fmt"

	"github.com/cybergodev/json"
)

func main() {
	data := `{"items":["a","b","c","d"]}`

	// -1 指向最后一个元素 "d"
	result, err := json.Delete(data, "items[-1]")
	if err != nil {
		panic(err)
	}
	fmt.Println(result)
	// 输出:{"items":["a","b","c"]}
}

嵌套路径删除

通过点号路径深入嵌套结构,删除任意层级的节点。

go
package main

import (
	"fmt"

	"github.com/cybergodev/json"
)

func main() {
	data := `{"config":{"database":{"host":"localhost","port":5432,"password":"secret"}}}`

	result, err := json.Delete(data, "config.database.password")
	if err != nil {
		panic(err)
	}
	fmt.Println(result)
	// 输出:{"config":{"database":{"host":"localhost","port":5432}}}
}

不可变语义

Delete 返回新字符串,jsonStr 不会被修改。你可以安全地在多处复用同一份输入:

go
package main

import (
	"fmt"

	"github.com/cybergodev/json"
)

func main() {
	data := `{"a":1,"b":2,"c":3}`

	r1, _ := json.Delete(data, "a")
	r2, _ := json.Delete(data, "b")

	fmt.Println(data) // 原数据不变:{"a":1,"b":2,"c":3}
	fmt.Println(r1)   // 输出:{"b":2,"c":3}
	fmt.Println(r2)   // 输出:{"a":1,"c":3}
}

高级路径删除

Delete 复用与 Get/Set 相同的递归路径引擎,支持通配符、切片范围、多字段提取等批量语义。批量路径(含 *{}:)对缺失目标采用容错策略——命中即删,缺失静默跳过,不返回错误

通配符删除

items[*] 删除数组全部元素;[*].field 删除每个元素的指定属性。

go
package main

import (
	"fmt"

	"github.com/cybergodev/json"
)

func main() {
	data := `{"users":[{"name":"Alice","temp":"x"},{"name":"Bob","temp":"y"}]}`

	// 删除每个用户对象的 temp 属性
	result, err := json.Delete(data, "users[*].temp")
	if err != nil {
		panic(err)
	}
	fmt.Println(result)
	// 输出:{"users":[{"name":"Alice"},{"name":"Bob"}]}
}

某些元素缺少目标属性时也不会报错(幂等语义,与 Go 原生 delete() 对 absent key 的行为一致):

go
// data = `[{"a":1},{"b":2}]` — 第二个元素没有 a,仍正常返回
result, err := json.Delete(data, "[*].a")
// err == nil,result:[{"b":2}]

切片范围删除

items[0:2] 删除一段连续区间的元素(左闭右开)。

go
package main

import (
	"fmt"

	"github.com/cybergodev/json"
)

func main() {
	data := `{"items":["a","b","c","d","e"]}`

	// 删除索引 0、1(不含 2)的 "a"、"b"
	result, err := json.Delete(data, "items[0:2]")
	if err != nil {
		panic(err)
	}
	fmt.Println(result)
	// 输出:{"items":["c","d","e"]}
}

多字段提取删除

[*].{a,b} 一次性删除每个元素的多个指定属性。

go
package main

import (
	"fmt"

	"github.com/cybergodev/json"
)

func main() {
	data := `[{"name":"Alice","pwd":"x","token":"y"},{"name":"Bob","pwd":"z"}]`

	// 同时删除 pwd 和 token 两个字段
	result, err := json.Delete(data, "[*].{pwd,token}")
	if err != nil {
		panic(err)
	}
	fmt.Println(result)
	// 输出:[{"name":"Alice"},{"name":"Bob"}]
}

精确路径 vs 批量路径

  • 精确路径(仅含属性名/索引,如 user.tempitems[1]):目标不存在时返回 ErrPathNotFound 错误。
  • 批量路径(含 *{}:,如 items[*][*].{a,b}items[0:2]):目标缺失时静默跳过,不报错。需要严格校验时用精确路径;需要"尽力删除"时用批量路径。

错误处理

精确路径的目标不存在时,Delete 返回包装了 ErrPathNotFound*JsonsError,且返回的原输入不变。用 errors.Is 判定具体错误类型:

go
package main

import (
	"errors"
	"fmt"

	"github.com/cybergodev/json"
)

func main() {
	data := `{"a":1}`

	result, err := json.Delete(data, "nonexistent.path")
	if err != nil {
		if errors.Is(err, json.ErrPathNotFound) {
			fmt.Println("路径不存在,已跳过")
		} else {
			fmt.Println("其他错误:", err)
		}
	}
	// result 仍为原始 JSON:{"a":1}
	fmt.Println(result)
	// 输出:
	// 路径不存在,已跳过
	// {"a":1}
}

常见的删除错误哨兵值:

错误触发场景
ErrPathNotFound精确路径的某个中间段或目标键/索引不存在
ErrInvalidJSONjsonStr 不是合法 JSON
ErrInvalidPath路径表达式语法非法(如未闭合的方括号)

DeleteClean

签名:func DeleteClean(jsonStr, path string, cfg ...Config) (string, error)

删除指定路径,并递归清理因删除产生的 null 值与空对象/空数组。等价于 Delete(jsonStr, path, cfg) 且强制开启 CleanupNulls: true + CompactArrays: true

级联清理示例

当删除后父对象变为空,DeleteClean 会继续把空父对象一并移除,逐层向上级联:

go
package main

import (
	"fmt"

	"github.com/cybergodev/json"
)

func main() {
	// temp 是 user 唯一的属性
	data := `{"user":{"temp":"value"}}`

	// 普通删除:user 变成空对象 {},但保留
	r1, _ := json.Delete(data, "user.temp")
	fmt.Println(r1) // 输出:{"user":{}}

	// DeleteClean:user 变空后连同 user 键一起清理
	r2, err := json.DeleteClean(data, "user.temp")
	if err != nil {
		panic(err)
	}
	fmt.Println(r2) // 输出:{}
}

清理 API 响应中的临时字段

DeleteClean 适合清理 API 响应:删除目标字段的同时,连带扫除其他 null 值与残留的空容器,避免把"空壳"对象暴露给前端。

go
package main

import (
	"fmt"

	"github.com/cybergodev/json"
)

func main() {
	apiResp := `{"data":{"id":1,"name":"Product","desc":null,"price":29.99,"note":null}}`

	// 一次 DeleteClean 删除 desc,并扫除整棵树中其他的 null(note)
	cleaned, err := json.DeleteClean(apiResp, "data.desc")
	if err != nil {
		panic(err)
	}
	fmt.Println(cleaned)
	// 输出:{"data":{"id":1,"name":"Product","price":29.99}}
}

DeleteClean 会清扫整棵树的 null

DeleteClean 的清理是全局的:它对整个 JSON 树递归执行 CleanupNullValues,因此会移除文档中所有预先存在的 null 值与空容器,而不仅仅是删除点产生的那个。如果你只想移除指定字段、保留其余 null,请用普通 Delete

DeleteClean 与 Config 的关系

DeleteClean 本质是 Delete + 两个配置项的语法糖。你也可以在普通 Delete 上显式传入同样的配置,效果完全等价:

go
package main

import (
	"fmt"

	"github.com/cybergodev/json"
)

func main() {
	data := `{"user":{"temp":"value"}}`

	// 方式一:DeleteClean
	r1, _ := json.DeleteClean(data, "user.temp")

	// 方式二:Delete + 显式配置(完全等价)
	cfg := json.DefaultConfig()
	cfg.CleanupNulls = true
	cfg.CompactArrays = true
	r2, _ := json.Delete(data, "user.temp", cfg)

	fmt.Println(r1) // 输出:{}
	fmt.Println(r2) // 输出:{}
}

影响删除行为的 Config 字段:

字段默认对删除的影响
CleanupNullsfalse递归移除结果中的 null 值与空对象/空数组(级联清理)
CompactArraysfalse移除数组中的 null/空元素;开启时隐含 CleanupNulls
CreatePathstrue不影响删除(删除从不创建路径,此处仅为对比说明)

Delete 与 DeleteClean 对比

特性DeleteDeleteClean
删除目标节点
数组元素移除后重排(无空洞)
精确路径缺失时报错是(ErrPathNotFound是(ErrPathNotFound
清理删除产生的 null
清理空对象/空数组(级联)是(逐层向上)
扫除整棵树预存的 null是(全局清理)
等价配置默认CleanupNulls+CompactArrays
相对开销较低略高(额外一次全树清理遍历)

常见陷阱

数组删除不留空洞

Delete 删除数组元素时,元素被整体移除、后续元素自动前移,不会留下 null 占位或空洞。如果你期望删除后索引保持不变(留下空位),CyberGo 的删除语义不满足该需求——应改用 Set 将该位置设为 null

DeleteClean 可能误删"恰好为空"的合法数据

DeleteClean 的级联清理会把所有空对象 {}、空数组 [] 视为需要清理的对象。如果你的业务语义中"空数组"或"空对象"是有意义的状态(例如 "tags":[] 表示"无标签"而非"字段缺失"),DeleteClean 会将其连同键一起移除。需要保留这类字段时,请使用普通 Delete

批量删除是容错的

通配符/切片/多字段路径对缺失目标静默跳过,不返回错误。如果你依赖"目标必须存在"的强校验语义,请改用精确路径(如 items[1] 而非 items[*])。

批量删除多个字段

需要一次性删除多个不相关字段时,对普通 Delete 循环调用即可(每次基于上一次的结果):

go
package main

import (
	"fmt"

	"github.com/cybergodev/json"
)

func main() {
	data := `{"user":{"id":1,"name":"Alice","password":"secret","ssn":"123-45-6789"}}`

	sensitive := []string{"user.password", "user.ssn"}
	result := data
	for _, field := range sensitive {
		var err error
		result, err = json.Delete(result, field)
		if err != nil {
			fmt.Printf("删除 %s 失败:%v\n", field, err)
		}
	}
	fmt.Println(result)
	// 输出:{"user":{"id":1,"name":"Alice"}}
}

相关