Skip to content

encoding/json: allow non-string map keys that implement encoding.TextMarshaler/TextUnmarshaler #12146

Closed
@augustoroman

Description

@augustoroman

Original discussion at https://groups.google.com/forum/#!searchin/golang-dev/json$20map/golang-dev/5gSHNrJQpUI/vZGSGRmUrC0J

Currently, json.Marshal will fail to marshal Go maps that have non-string keys, e.g.:

// http://play.golang.org/p/2m9wLZATqw
type Coord struct { X,Y int }
occupied := map[Coord]bool{}
occupied[Coord{1,2}] = true
data, err := json.Marshal(occupied)
fmt.Printf("Data: %s\nErr: %v", data, err)

I propose to enhance the encoding/json package such that:

  1. for json.Marshal
    • If the map key is a string kind it is used directly.
    • Otherwise if the map key satisfies the encoding.TextMarshaler interface then that is used to generate the map key.
    • Otherwise it fails as it does today.
  2. for json.Unmarshal
    • If the map key is a string kind it is written directly.
    • Otherwise if the map key satisfies the encoding.TextUnmarshaler interface then that is used to decode the map key.
    • Otherwise it fails as it does today.

Example

(http://play.golang.org/p/VxhFluFKTX)

This would, for example, allow a map[Coord]bool to be Marshaled & Unmarshaled if Coord was defined as:

type Coord struct{ X, Y int }
func (c Coord) MarshalText() ([]byte, error) {
    return []byte(fmt.Sprintf("%d,%d", c.X, c.Y)), nil
}
func (c *Coord) UnmarshalText(p []byte) error {
    if n, err := fmt.Sscanf(string(p), "%d,%d", &c.X, &c.Y); err != nil {
        return err
    } else if n != 2 {
        return errors.New("Cannot parse coord: " + string(p))
    }
    return nil
}

And the Go struct

map[Coord]bool{Coord{1, 2}: true, Coord{3, 4}: true}

would correspond to

{"1,2": true,"3,4":true}

Considerations

If the struct marshals to a non-unique value, e.g.

func (c Coord) MarshalText() ([]byte, error) {
    return []byte("always the same"), nil
}

The the json encoder would output JSON that has repeated keys, e.g.:

{"always the same": true,"always the same":true}

This is valid JSON.

Similarly, when decoding a map, it would unmarshal each key and value and then assign that into the map, so last-write-wins, as is currently done.

An interesting side effect is that it would then be possible to explicitly handle JSON with repeated keys (or even explicitly record the order of the json keys) by having the go-side map key TextUnmarshaler have non-deterministic unmarshaling (e.g. record timestamp for each unmarshal).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions