ordmap

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 10, 2025 License: BSD-3-Clause Imports: 7 Imported by: 0

README

Ordmap - serializable ordered maps in go

This package contains an ordered Map type, which keeps track of the order in which the keys have been created in it, and which is compatible with encoding/json and gopkg.in/yaml.v3 for unmarshalling/marshalling.

It also contains an Any type, which can serve as a generic placeholder to unmarshal json or yaml data, and keeping the keys ordered for objects nested at any level in the payload.

Check the doc for more details, and examples.

Goals and non goals

This package aims at being a correct target to unmarshal/marshal json and yaml, and tries to follow as close as possible the behavior of the standard json package, as well as the gopkg.in/yaml.v3 package.

It was not designed with performance in mind, so it may compare quite badly with other packages performance wise.

Documentation

Overview

Example (Compared)
package main

import (
	"encoding/json"
	"fmt"
	"os"

	"github.com/LeGEC/ordmap"
)

func main() {
	input := `{
  "last_name": "Doe",
  "first_name": "John",
  "age": 42,
  "skills": {
    "go": 5,
    "python": 3,
    "ada": 2,
    "rust": 1
  }
}`

	var std any
	var oMap ordmap.Map[string, any]
	var oAny ordmap.Any

	_ = json.Unmarshal([]byte(input), &std)
	_ = json.Unmarshal([]byte(input), &oMap)
	_ = json.Unmarshal([]byte(input), &oAny)

	var output = json.NewEncoder(os.Stdout)
	output.SetIndent("", "  ")
	output.SetEscapeHTML(false)

	fmt.Println("// standard 'any' object: order of keys is not preserved")
	output.Encode(std)
	fmt.Println()

	fmt.Println("// ordmap.Map[string, any]: order of keys is preserved in the root object (last_name, first_name, age, skills),")
	fmt.Println("//     but with values of 'any' type, not in nested objects:")
	fmt.Println("//       inside 'skills' object: the order (go, python, ada, rust) is not preserved")
	output.Encode(oMap)
	fmt.Println()

	fmt.Println("// ordmap.Any: order of keys is preserved everywhere")
	output.Encode(oAny)

}
Output:

// standard 'any' object: order of keys is not preserved
{
  "age": 42,
  "first_name": "John",
  "last_name": "Doe",
  "skills": {
    "ada": 2,
    "go": 5,
    "python": 3,
    "rust": 1
  }
}

// ordmap.Map[string, any]: order of keys is preserved in the root object (last_name, first_name, age, skills),
//     but with values of 'any' type, not in nested objects:
//       inside 'skills' object: the order (go, python, ada, rust) is not preserved
{
  "last_name": "Doe",
  "first_name": "John",
  "age": 42,
  "skills": {
    "ada": 2,
    "go": 5,
    "python": 3,
    "rust": 1
  }
}

// ordmap.Any: order of keys is preserved everywhere
{
  "last_name": "Doe",
  "first_name": "John",
  "age": 42,
  "skills": {
    "go": 5,
    "python": 3,
    "ada": 2,
    "rust": 1
  }
}
Example (OpenapiUseCase)
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"

	"github.com/LeGEC/ordmap"
)

func main() {
	// a Schema declaration, to match a 'schema' entry in an OpenAPI document,
	// limited to the fields visible in the example below (excerpt from the github openapi specification)
	type Schema struct {
		Ref   string    `yaml:"$ref,omitempty" json:"$ref,omitempty"`
		AnyOf []*Schema `yaml:"anyOf,omitempty" json:"anyOf,omitempty"`
		OneOf []*Schema `yaml:"oneOf,omitempty" json:"oneOf,omitempty"`

		Title       string  `yaml:"title,omitempty" json:"title,omitempty"`
		Description string  `yaml:"description,omitempty" json:"description,omitempty"`
		Type        any     `yaml:"type,omitempty" json:"type,omitempty"`
		Enum        []any   `yaml:"enum,omitempty" json:"enum,omitempty"`
		Default     any     `yaml:"default,omitempty" json:"default,omitempty"`
		Format      string  `yaml:"format,omitempty" json:"format,omitempty"`
		Items       *Schema `yaml:"items,omitempty" json:"items,omitempty"`

		// The 'example' field may hold any value, be it a simple scalar or a fully fledged object.
		// Using 'ordpmap.Any' here allows to keep the original order if you are into unmarshalling/marshalling
		// a schema, and want to have a stable value here
		Example *ordmap.Any `yaml:"example,omitempty" json:"example,omitempty"`

		Examples []*ordmap.Any `yaml:"examples,omitempty" json:"examples,omitempty"`

		// keep Properties in same order as in the original schema
		Properties *ordmap.Map[string, *Schema] `yaml:"properties,omitempty" json:"properties,omitempty"`

		ReadOnly  bool     `yaml:"readOnly,omitempty" json:"readOnly,omitempty"`
		Required  []string `yaml:"required,omitempty" json:"required,omitempty"`
		WriteOnly bool     `yaml:"writeOnly,omitempty" json:"writeOnly,omitempty"`
	}

	type Components struct {
		Schemas *ordmap.Map[string, *Schema] `yaml:"schemas,omitempty" json:"schemas,omitempty"`
		// other fields ...
	}

	type Document struct {
		Components *Components `yaml:"components,omitempty" json:"components,omitempty"`
		// other fields ...
	}

	// file testdata/github.excerpt.3.14.json:
	// excerpt from the github openapi specification at:
	//  https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/ghes-3.14/ghes-3.14.2022-11-28.json
	//
	// you will note that the order of the "properties", for example, is preserved,
	// as well as, in the "authentication-token" schema, the "examples" section for "properties.permissions"
	inputFile := "testdata/github.excerpt.3.14.json"
	input, err := os.ReadFile(inputFile)

	var doc Document
	err = json.Unmarshal(input, &doc)
	if err != nil {
		panic(err)
	}

	marshalled, err := json.MarshalIndent(doc, "", "    ")
	if err != nil {
		panic(err)
	}

	if bytes.Equal(marshalled, input) {
		fmt.Println("fields order from input is preserved")
	} else {
		fmt.Println("!!! fields order from input is not preserved")
	}

}
Output:


fields order from input is preserved

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Any

type Any struct {
	// contains filtered or unexported fields
}

Any wraps an 'any' value.

Its purpose is to be used as the target for `json.Umarshal()` or `yaml.Unmarshal()`, and to create a go structure which keeps track of the order of the keys as they appeared in the initial document.

See the implementation of `Any.UnmarshalJSON()` and `Any.UnmarshalYAML()`

Example (Json)
package main

import (
	"encoding/json"
	"fmt"
	"os"

	"github.com/LeGEC/ordmap"
)

func main() {
	input := `{
  "last_name": "Doe",
  "first_name": "John",
  "age": 42,
  "skills": {
    "go": 5,
    "python": 3,
    "ada": 2,
    "rust": 1
  }
}`

	var x ordmap.Any
	_ = json.Unmarshal([]byte(input), &x)

	// ordmap.Any: all objects, including nested, are unmarshalled as ordmap.Map,
	//   the order of keys is preserved everywhere
	enc := json.NewEncoder(os.Stdout)
	enc.SetIndent("", "  ")
	enc.Encode(x)

	fmt.Println()

	obj := x.V().(*ordmap.Map[string, any])
	// the type for any json object is '*ordmap.Map[string, any]':
	fmt.Printf("type for skills: %T\n", obj.Get("skills"))
	// the type for other fields is the regular go type for generic unmarshalling
	fmt.Printf("type for first_name: %T\n", obj.Get("first_name"))

}
Output:

{
  "last_name": "Doe",
  "first_name": "John",
  "age": 42,
  "skills": {
    "go": 5,
    "python": 3,
    "ada": 2,
    "rust": 1
  }
}

type for skills: *ordmap.Map[string,interface {}]
type for first_name: string
Example (Yaml)
input := `
last_name: Doe
first_name: John
age: 42
skills:
  go: 5
  python: 3
  ada: 2
  rust: 1`

var x ordmap.Any
_ = yaml.Unmarshal([]byte(input), &x)

// ordmap.Any: all objects, including nested, are unmarshalled as ordmap.Map,
//   the order of keys is preserved everywhere
enc := yaml.NewEncoder(os.Stdout)
enc.SetIndent(2)
enc.Encode(x)
Output:


last_name: Doe
first_name: John
age: 42
skills:
  go: 5
  python: 3
  ada: 2
  rust: 1

func (Any) MarshalJSON

func (x Any) MarshalJSON() ([]byte, error)

func (Any) MarshalYAML

func (x Any) MarshalYAML() (interface{}, error)

func (*Any) UnmarshalJSON

func (x *Any) UnmarshalJSON(p []byte) error

func (*Any) UnmarshalYAML

func (x *Any) UnmarshalYAML(node *yaml.Node) error

func (*Any) V

func (x *Any) V() any

type Map

type Map[K comparable, V any] struct {
	// contains filtered or unexported fields
}

Map is a map which preserves the order in which the keys were inserted.

Its main feature is to implement the 4 following interfaces:

  • `json.Marshaler` (from standard package `encoding/json`)
  • `json.Unmarshaler` (from standard package `encoding/json`)
  • `yaml.Marshaler` (from package `gopkg.in/yaml.v3`)
  • `yaml.Unmarshaler` (from package `gopkg.in/yaml.v3`)

in a way that preserves the order of the keys in the source data.

Example (Json)
package main

import (
	"encoding/json"
	"os"

	"github.com/LeGEC/ordmap"
)

func main() {
	input := `{
		"last_name": "Doe",
		"first_name": "John",
		"age": 42,
		"skills": {
			"go": 5,
			"python": 3,
			"ada": 2,
			"rust": 1
		}
	}`

	var x ordmap.Map[string, any]
	_ = json.Unmarshal([]byte(input), &x)

	enc := json.NewEncoder(os.Stdout)
	enc.SetIndent("", "  ")

	// ordmap.Map[string, any]: the order of the keys in the root object is preserved,
	//   but with values of type 'any', the order in nested objects is not preserved.
	enc.Encode(x)

}
Output:


{
  "last_name": "Doe",
  "first_name": "John",
  "age": 42,
  "skills": {
    "ada": 2,
    "go": 5,
    "python": 3,
    "rust": 1
  }
}
Example (Yaml)
input := `
last_name: Doe
first_name: John
age: 42
skills:
  go: 5
  python: 3
  ada: 2
  rust: 1`

var x ordmap.Map[string, any]
_ = yaml.Unmarshal([]byte(input), &x)

enc := yaml.NewEncoder(os.Stdout)
enc.SetIndent(2)

// ordmap.Map[string, any]: the order of the keys in the root object is preserved,
//   but with an 'any' type as value, the order in nested objects is not preserved.
enc.Encode(x)
Output:


last_name: Doe
first_name: John
age: 42
skills:
  ada: 2
  go: 5
  python: 3
  rust: 1

func (*Map[K, V]) Clear

func (m *Map[K, V]) Clear()

func (*Map[K, V]) Clone

func (m *Map[K, V]) Clone() *Map[K, V]

func (*Map[K, V]) Delete

func (m *Map[K, V]) Delete(key K) bool

func (*Map[K, V]) ForAll added in v0.3.0

func (m *Map[K, V]) ForAll() iter.Seq2[K, V]

func (*Map[K, V]) Get

func (m *Map[K, V]) Get(key K) V

func (*Map[K, V]) Get2

func (m *Map[K, V]) Get2(key K) (V, bool)

func (*Map[K, V]) Keys

func (m *Map[K, V]) Keys() []K

func (*Map[K, V]) Len

func (m *Map[K, V]) Len() int

func (Map[K, V]) MarshalJSON

func (m Map[K, V]) MarshalJSON() ([]byte, error)

func (Map[K, V]) MarshalYAML

func (m Map[K, V]) MarshalYAML() (any, error)

func (*Map[K, V]) Set

func (m *Map[K, V]) Set(key K, value V)

func (*Map[K, V]) UnmarshalJSON

func (m *Map[K, V]) UnmarshalJSON(p []byte) error

func (*Map[K, V]) UnmarshalYAML

func (m *Map[K, V]) UnmarshalYAML(value *yaml.Node) error

func (*Map[K, V]) Values added in v0.3.0

func (m *Map[K, V]) Values() iter.Seq2[int, V]

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL