jsonx

package module
v0.0.0-...-b01e255 Latest Latest
Warning

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

Go to latest
Published: Aug 3, 2021 License: MIT Imports: 14 Imported by: 0

README

JSONX: extended JSON syntax for Go

JSONX is a superset of JSON that allows additional types and has a more relaxed syntax.

Example:

{
  k01: null,
  k02: false,
  k03: true,
  k04: "test",
  k05: 1.45678e-98,
  k06: int(-454365464),
  k07: uint(455645765),
  k08: int8(-128),
  k09: uint8(255),
  k10: int16(32767),
  k11: uint16(65535),
  k12: int32(2147483647),
  k13: uint32(4294967295),
  k14: int64("9223372036854775807"),
  k15: uint64("18446744073709551615"),
  k16: datetime("2017-12-25T15:00:00Z"),
  k17: ip("192.168.1.2"),
  k18: ipport("192.168.1.2:65000"),
  k19: ip("::1"),
  k20: ipport("[::1]:65000"),
  k21: bytes("YWJjZA==")),
  k22: [
    "test",
    int(123),
  ],
  k23: {
    test: true
  }
}

Differences from JSON:

  • Keys may be unquoted as long as they match ^[A-Za-z_][0-9A-Za-z_]*$.
  • Trailing commas after the last array or object elements are permitted.
  • Additional types can be represented as 'type(value)'. The example above contains all currently supported types.

Usage

The package includes a parser and a serialiser. They are both schemaless (i.e. only accept and produce primitive values, []interface{} and map[string]interface{})

Because JSONX is a superset of JSON you can use the parser as a faster alternative to the standard json.Unmarshal():

var v interface{}
err := json.Unmarshal(data, &v)

// or
v, err := jsonx.Decode(data)

Non-greedy decoding example:

b := []byte(`{test: 1} blah`)
v, err := jsonx.Decode(b)
if err, ok := err.(*jsonx.ExtraDataError); ok {
    tail := b[err.Offset:] // "blah"
    // parse tail
}

As JSONX is a valid ES5 expression you could parse it in Javascript using eval() providing the type functions (int(), int8(), datetime(), etc..) are defined.

Encoding example:

v := map[string]interface{}{"test": true}

// encoding/json compatible API
b, err := jsonx.Marshal(v)
b1, err := jsonx.MarshalIndent(v, ">", "\t")

// encoding to an io.Writer
enc := jsonx.NewEncoder(os.Stdout)
err = enc.Encode(v)
enc1 := jsonx.NewEncoderIndent(os.Stdout, ">", "\t")
err = enc1.Encode(v)

Acknowledgements

Decoder is based on djson which saved me a lot of time writing it from scratch.

The code was tested with go-fuzz which I think is a must for any projects like this.

Documentation

Index

Constants

View Source
const (
	MAX_SAFE_INTEGER = 1<<53 - 1
	MIN_SAFE_INTEGER = -(1<<53 - 1)
)

Variables

View Source
var (
	ErrUnexpectedEOF    = &SyntaxError{"unexpected end of JSON input", -1}
	ErrInvalidHexEscape = &SyntaxError{"invalid hexadecimal escape sequence", -1}
	ErrStringEscape     = &SyntaxError{"encountered an invalid escape sequence in a string", -1}
)

Predefined errors

Functions

func Decode

func Decode(data []byte) (interface{}, error)

Decode parses the JSON-encoded data and returns an interface value. Equivalent of NewDecoder(data).Decode()

func DecodeArray

func DecodeArray(data []byte) ([]interface{}, error)

DecodeArray is the same as Decode but it returns []interface{}. Equivalent of NewDecoder(data).DecodeArray()

func DecodeObject

func DecodeObject(data []byte) (map[string]interface{}, error)

DecodeObject is the same as Decode but it returns map[string]interface{}. Equivalent of NewDecoder(data).DecodeObject()

func Marshal

func Marshal(v interface{}) ([]byte, error)

func MarshalIndent

func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)

Types

type Decoder

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

Decoder is the object that holds the state of the decoding

func NewDecoder

func NewDecoder(data []byte) *Decoder

NewDecoder creates new Decoder from the JSON-encoded data

func (*Decoder) AllocString

func (d *Decoder) AllocString()

AllocString pre-allocates a string version of the data before starting to decode the data. It is used to make the decode operation faster(see below) by doing one allocation operation for string conversion(from bytes), and then uses "slicing" to create non-escaped strings in the "Decoder.string" method. However, string is a read-only slice, and since the slice references the original array, as long as the slice is kept around, the garbage collector can't release the array. For this reason, you want to use this method only when the Decoder's result is a "read-only" or you are adding more elements to it. see example below.

Here are the improvements:

small payload  - 0.13~ time faster, does 0.45~ less memory allocations but
		 the total number of bytes that are allocated is 0.03~ bigger

medium payload - 0.16~ time faster, does 0.5~ less memory allocations but
		 the total number of bytes that are allocated is 0.05~ bigger

large payload  - 0.13~ time faster, does 0.50~ less memory allocations but
		 the total number of bytes that are allocated is 0.02~ bigger

Here is an example to illustrate when you don't want to use this method

str := fmt.Sprintf(`{"foo": "bar", "baz": "%s"}`, strings.Repeat("#", 1024 * 1024))
dec := djson.NewDecoder([]byte(str))
dec.AllocString()
ev, err := dec.DecodeObject()

// inspect memory stats here; MemStats.Alloc ~= 1M

delete(ev, "baz") // or ev["baz"] = "qux"

// inspect memory stats again; MemStats.Alloc ~= 1M
// it means that the chunk that was located in the "baz" value is not freed

func (*Decoder) Decode

func (d *Decoder) Decode() (interface{}, error)

Decode parses the JSONX-encoded data and returns an interface value. The interface value could be one of these:

	bool, for booleans
	float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, for numbers
	string, for strings
 net.IP for IP addresses (ip("1.2.3.4") or ip("fd00::1"))
 net.TCPAddr for ip/port pairs (ipport("1.2.3.4:5678") or ipport("[fd00::1]:5678")
 time.Time for timestamps (datetime("2006-01-02T15:04:05Z07:00"))
 []byte for base64-encoded bytes (bytes("YWJjZA=="))
	[]interface{}, for arrays
	map[string]interface{}, for objects
	nil for null

If any extra non-space characters found after decoding the top level value, the decoded value and the error are returned allowing to implement non-greedy decoding.

func (*Decoder) DecodeArray

func (d *Decoder) DecodeArray() ([]interface{}, error)

DecodeArray is the same as Decode but it returns []interface{}.

func (*Decoder) DecodeObject

func (d *Decoder) DecodeObject() (map[string]interface{}, error)

DecodeObject is the same as Decode but it returns map[string]interface{}.

type Encoder

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

func NewEncoder

func NewEncoder(w io.Writer) *Encoder

func NewEncoderIndent

func NewEncoderIndent(w io.Writer, prefix, indent string) *Encoder

func (*Encoder) Encode

func (e *Encoder) Encode(v interface{}) error

type ExtraDataError

type ExtraDataError struct {
	Offset int
}

ExtraDataError is returned when a non-space data was found after parsing the top-level value. Offset contains the position of the first byte.

func (*ExtraDataError) Error

func (e *ExtraDataError) Error() string

type SyntaxError

type SyntaxError struct {
	Offset int // error occurred after reading Offset bytes
	// contains filtered or unexported fields
}

A SyntaxError is a description of a JSON syntax error.

func (*SyntaxError) Error

func (e *SyntaxError) Error() string

type ValueType

type ValueType int

ValueType identifies the type of a parsed value.

const (
	Null ValueType = iota
	Bool
	String
	Number
	Object
	Array
	Unknown
)

func Type

func Type(v interface{}) ValueType

Type returns the JSON-type of the given value

func (ValueType) String

func (v ValueType) String() string

Jump to

Keyboard shortcuts

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