Golang中如何优雅地格式化JSON

Golang中如何优雅地格式化JSON 我有一堆JSON对象想要打印到屏幕上。 我希望对键进行排序,以便更容易阅读。 这段Python代码实现了我的需求:

#!/usr/bin/env python3
import json

def reorder(s):
    o = json.loads(s)
    return json.dumps(o, sort_keys=True)

input1 = '{"b": 2, "A":1, "c": {"d": 3}}'
input2 = '{"c": {"d": 3}, "b": 2, "A":1}'
print(input1)
print(input2)
print()
print(reorder(input1))
print(reorder(input2))

输出:

{"b": 2, "A":1, "c": {"d": 3}}
{"c": {"d": 3}, "b": 2, "A":1}

{"A": 1, "b": 2, "c": {"d": 3}}
{"A": 1, "b": 2, "c": {"d": 3}}

我尝试使用json.Unmarshal和json.Marshal,但它会过滤掉小写键。 而且输出中没有空格,这有点难以阅读。

我事先不知道消息的格式。 我只知道它们将是JSON对象。

有人有好的方法来实现这个吗?


更多关于Golang中如何优雅地格式化JSON的实战教程也可以访问 https://www.itying.com/category-94-b0.html

8 回复

你能使用 jq 吗?

更多关于Golang中如何优雅地格式化JSON的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


感谢。 那里有很多有用的信息,但大多数答案都将 JSON 打印在多行上。我需要它全部在一行上。

谢谢。 jq 是个好主意。我可以用它,但如果可能的话,更倾向于一个纯 Go 的解决方案,不依赖外部库。

感谢大家的建议!

我发现实现我需求的唯一方法是解析标准库中JSON编码器的Token函数的输出。

这个:

Go Playground - The Go Programming Language

似乎满足了我的需求。具体是:

  • 按键字母顺序打印对象。
  • 在单行上打印。
  • 在每个逗号和分号后打印一个空格。
  • 最外层的JSON始终是一个对象。其内部可以包含任何有效的JSON。

仅仅为了给JSON添加一些空格,这似乎需要相当多的代码,但至少写起来很有趣。

func main() {
    fmt.Println("hello world")
}

MikeSolem: 有人有好的方法来做这个吗?

Brad Peabody

如何使用 Go 漂亮地打印 JSON?

标签: json, go, pretty-print

Brad Peabody 提问于 08:59PM - 26 Sep 13 UTC

要美化打印你的JSON,你可以简单地使用 MarshalIndent 而不是 Marshal 函数。关于重新排序,是的,你可以设想一个函数来实现这一点,但请记住,JSON是一种用于数据交换的格式,元素的顺序无法保证,也不应成为关注点。

JSON数据交换标准在 json.org 上的定义明确指出:“对象是一个无序的[强调为我所加]名称/值对集合”,而数组是“值的有序集合”。换句话说,根据定义,JSON对象内部键/值对的顺序根本无关紧要,也不应该重要

在JSON对象中排序元素 – FileMakerHacks

我在原始问题中犯了一个错误。 json.Unmarshaljson.Marshal 并不会过滤掉小写键。 它只是把小写的内容放到了末尾,所以我在输出中没有看到。

所以这段代码正确地进行了字母顺序排序。

package main

import (
	"encoding/json"
	"fmt"
)

func reorder(obj string) string {
	var o map[string]interface{}
	json.Unmarshal([]byte(obj), &o)
	r, _ := json.Marshal(o)
	return string(r)
}

func main() {
	input1 := `{"b": 2, "A":1, "c": {"d": 3}}`
	input2 := `{"c": {"d": 3}, "b": 2, "A":1}`
	fmt.Println(input1)
	fmt.Println(input2)
	fmt.Println()
	fmt.Println(reorder(input1))
	fmt.Println(reorder(input2))
}

输出:

{"b": 2, "A":1, "c": {"d": 3}}
{"c": {"d": 3}, "b": 2, "A":1}

{"A":1,"b":2,"c":{"d":3}}
{"A":1,"b":2,"c":{"d":3}}

如果每个冒号和逗号后面都有一个空格,可读性会好得多。

在Go中可以使用json.MarshalIndent配合自定义排序来实现JSON的优雅格式化。以下是示例代码:

package main

import (
    "encoding/json"
    "fmt"
    "sort"
)

func formatJSON(input string) (string, error) {
    var data map[string]interface{}
    if err := json.Unmarshal([]byte(input), &data); err != nil {
        return "", err
    }
    
    // 递归排序所有嵌套map的键
    sortKeys(data)
    
    // 使用MarshalIndent添加缩进和换行
    formatted, err := json.MarshalIndent(data, "", "  ")
    if err != nil {
        return "", err
    }
    
    return string(formatted), nil
}

func sortKeys(m map[string]interface{}) {
    // 先对当前层级的键进行排序
    keys := make([]string, 0, len(m))
    for k := range m {
        keys = append(keys, k)
    }
    sort.Strings(keys)
    
    // 创建新的有序map
    sorted := make(map[string]interface{})
    for _, k := range keys {
        sorted[k] = m[k]
        
        // 递归处理嵌套的map
        if nested, ok := m[k].(map[string]interface{}); ok {
            sortKeys(nested)
        }
    }
    
    // 将排序后的内容复制回原map
    for k, v := range sorted {
        m[k] = v
    }
}

func main() {
    inputs := []string{
        `{"b": 2, "A":1, "c": {"d": 3}}`,
        `{"c": {"d": 3}, "b": 2, "A":1}`,
    }
    
    for _, input := range inputs {
        fmt.Println("原始JSON:")
        fmt.Println(input)
        
        formatted, err := formatJSON(input)
        if err != nil {
            fmt.Printf("错误: %v\n", err)
            continue
        }
        
        fmt.Println("\n格式化后:")
        fmt.Println(formatted)
        fmt.Println()
    }
}

输出结果:

原始JSON:
{"b": 2, "A":1, "c": {"d": 3}}

格式化后:
{
  "A": 1,
  "b": 2,
  "c": {
    "d": 3
  }
}

原始JSON:
{"c": {"d": 3}, "b": 2, "A":1}

格式化后:
{
  "A": 1,
  "b": 2,
  "c": {
    "d": 3
  }
}

如果需要处理JSON数组或更复杂的嵌套结构,可以使用以下扩展版本:

func sortKeysRecursive(v interface{}) {
    switch val := v.(type) {
    case map[string]interface{}:
        // 排序当前map的键
        keys := make([]string, 0, len(val))
        for k := range val {
            keys = append(keys, k)
        }
        sort.Strings(keys)
        
        sorted := make(map[string]interface{})
        for _, k := range keys {
            sorted[k] = val[k]
            // 递归处理值
            sortKeysRecursive(val[k])
        }
        
        // 更新原map
        for k, v := range sorted {
            val[k] = v
        }
        
    case []interface{}:
        // 处理数组中的每个元素
        for _, item := range val {
            sortKeysRecursive(item)
        }
    }
}

func formatJSONAdvanced(input string) (string, error) {
    var data interface{}
    if err := json.Unmarshal([]byte(input), &data); err != nil {
        return "", err
    }
    
    sortKeysRecursive(data)
    
    formatted, err := json.MarshalIndent(data, "", "  ")
    if err != nil {
        return "", err
    }
    
    return string(formatted), nil
}

这个实现会:

  1. 保持所有键(包括大小写敏感)
  2. 按键名排序(Go的sort.Strings按字典序排序)
  3. 使用两个空格缩进格式化输出
  4. 递归处理嵌套的JSON对象和数组
回到顶部