使用特定顺序对地图进行序列化。

huangapple go评论81阅读模式
英文:

Serialize a map using a specific order

问题

我有一个使用字符串作为键和值的映射表。我有一个键的数组,指定了映射表值的顺序。

我想将该映射表序列化为JSON格式,但要保持数组指定的顺序。

这里有一个示例代码:http://play.golang.org/p/A52GTDY6Wx

我想要序列化的结果是:

{
  "name": "John",
  "age": "20"
}

但是如果直接序列化映射表,键会按字母顺序排序:

{      
  "age": "20",
  "name": "John"
}

我可以将其序列化为映射表的数组,以保持顺序,但会生成很多不必要的字符:

[
  {
    "name": "John"
  },
  {
    "age": "20"
  }
]

在我的实际代码中,我需要序列化数据库查询的结果,该查询在文本文件中指定,并且我需要保持列的顺序。由于列在编译时是未知的,所以我不能使用结构体。

编辑:我不需要按指定顺序读取JSON。生成的JSON是供人阅读的,所以我只希望它尽可能地易读。

我可以使用自定义格式,但JSON对我来说非常适用。

谢谢!

英文:

I have a map that uses string for both key and value. I have an array of keys that specifies the order of the values of the map.

I want to serialize that map to a JSON, but keeping the order defined on the array.

There is a sample code here: http://play.golang.org/p/A52GTDY6Wx

I want to serialize it as:

{
  "name": "John",
  "age": "20"
}

But if I serialize the map directly, the keys are ordered alphabetically:

{      
  "age": "20",
  "name": "John"
}

I can serialize it as an array of maps, thus keeping the order, however that generates a lot of undesired characters:

[
  {
    "name": "John"
  },
  {
    "age": "20"
  }
]

In my real code I need to serialize the results of a database query which is specified in a text file, and I need to maintain the column order. I cannot use structs because the columns are not known at compile time.

EDIT: I don't need to read the JSON later in the specified order. The generated JSON is meant to be read by people, so I just want it to be as humanly readable as possible.

I could use a custom format but JSON suits me perfectly for this.

Thanks!

答案1

得分: 16

你需要在自定义类型上实现json.Marshaler接口。这样做的好处是可以与其他结构类型很好地配合使用。

抱歉,你总是需要编写一小段JSON编码代码。

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
)

type KeyVal struct {
	Key string
	Val interface{}
}

// 定义一个有序映射
type OrderedMap []KeyVal

// 实现json.Marshaler接口
func (omap OrderedMap) MarshalJSON() ([]byte, error) {
	var buf bytes.Buffer

	buf.WriteString("{")
	for i, kv := range omap {
		if i != 0 {
			buf.WriteString(",")
		}
		// 编码键
		key, err := json.Marshal(kv.Key)
		if err != nil {
			return nil, err
		}
		buf.Write(key)
		buf.WriteString(":")
		// 编码值
		val, err := json.Marshal(kv.Val)
		if err != nil {
			return nil, err
		}
		buf.Write(val)
	}

	buf.WriteString("}")
	return buf.Bytes(), nil
}

func main() {
	dict := map[string]interface{}{
		"orderedMap": OrderedMap{
			{"name", "John"},
			{"age", 20},
		},
	}
	dump, err := json.Marshal(dict)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", dump)
}

输出结果为:

{"orderedMap":{"name":"John","age":20}}
英文:

You need to implement the json.Marshaler interface on a custom type. This has the advantage of playing well within other struct types.

Sorry, you're always going to have to write a little bit of JSON encoding code.

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
)

type KeyVal struct {
	Key string
	Val interface{}
}

// Define an ordered map
type OrderedMap []KeyVal

// Implement the json.Marshaler interface
func (omap OrderedMap) MarshalJSON() ([]byte, error) {
	var buf bytes.Buffer

	buf.WriteString("{")
	for i, kv := range omap {
		if i != 0 {
			buf.WriteString(",")
		}
		// marshal key
		key, err := json.Marshal(kv.Key)
		if err != nil {
			return nil, err
		}
		buf.Write(key)
		buf.WriteString(":")
		// marshal value
		val, err := json.Marshal(kv.Val)
		if err != nil {
			return nil, err
		}
		buf.Write(val)
	}

	buf.WriteString("}")
	return buf.Bytes(), nil
}

func main() {
	dict := map[string]interface{}{
		"orderedMap": OrderedMap{
			{"name", "John"},
			{"age", 20},
		},
	}
	dump, err := json.Marshal(dict)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", dump)
}

Outputs

{"orderedMap":{"name":"John","age":20}}

答案2

得分: 2

对于这个特定的需求,你实际上不需要使用json.Marshal,你可以简单地实现自己的函数,就像这样:

type OrderedMap map[string]string

func (om OrderedMap) ToJson(order ...string) string {
    buf := &bytes.Buffer{}
    buf.Write([]byte{'{', '\n'})
    l := len(order)
    for i, k := range order {
        fmt.Fprintf(buf, "\t\"%s\": \"%v\"", k, om[k])
        if i < l-1 {
            buf.WriteByte(',')
        }
        buf.WriteByte('\n')
    }
    buf.Write([]byte{'}', '\n'})
    return buf.String()
}

func main() {
    om := OrderedMap{
        "age":  "20",
        "name": "John",
    }
    fmt.Println(om.ToJson("name", "age"))
}

你可以点击这里查看示例代码。

英文:

For that specific requirement you really don't need to use json.Marshal at all, you can simply implement your own function like this:

type OrderedMap map[string]string

func (om OrderedMap) ToJson(order ...string) string {
	buf := &amp;bytes.Buffer{}
	buf.Write([]byte{&#39;{&#39;, &#39;\n&#39;})
	l := len(order)
	for i, k := range order {
		fmt.Fprintf(buf, &quot;\t\&quot;%s\&quot;: \&quot;%v\&quot;&quot;, k, om[k])
		if i &lt; l-1 {
			buf.WriteByte(&#39;,&#39;)
		}
		buf.WriteByte(&#39;\n&#39;)
	}
	buf.Write([]byte{&#39;}&#39;, &#39;\n&#39;})
	return buf.String()
}
func main() {
	om := OrderedMap{
		&quot;age&quot;:  &quot;20&quot;,
		&quot;name&quot;: &quot;John&quot;,
	}
	fmt.Println(om.ToJson(&quot;name&quot;, &quot;age&quot;))
}

答案3

得分: 2

可能最简单的解决方案是:https://github.com/iancoleman/orderedmap

尽管在这里提到它可能会比较慢。1

英文:

Probably the easiest solution: https://github.com/iancoleman/orderedmap

Although it might be slow as it's mentioned here

答案4

得分: 0

这是一个类似于YAML v2提供的MapSlice实现。它可以进行Marshal和Unmarshal操作。

https://github.com/mickep76/mapslice-json

英文:

Here is a MapSlice implementation similar to what YAML v2 offers. It can do both Marshal and Unmarshal.

https://github.com/mickep76/mapslice-json

huangapple
  • 本文由 发表于 2014年8月7日 20:45:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/25182923.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定