使用特定顺序序列化地图

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!

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 := &bytes.Buffer{}
    buf.Write([]byte{'{', '
'})
    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('
')
    }
    buf.Write([]byte{'}', '
'})
    return buf.String()
}
func main() {
    om := OrderedMap{
        "age":  "20",
        "name": "John",
    }
    fmt.Println(om.ToJson("name", "age"))
}

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
", dump)
}

Outputs

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

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

Although it might be slow as it's mentioned here