Source File
value.go
Belonging Package
encoding/json/jsontext
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build goexperiment.jsonv2
package jsontext
import (
)
// NOTE: Value is analogous to v1 json.RawMessage.
// AppendFormat formats the JSON value in src and appends it to dst
// according to the specified options.
// See [Value.Format] for more details about the formatting behavior.
//
// The dst and src may overlap.
// If an error is reported, then the entirety of src is appended to dst.
func (, []byte, ...Options) ([]byte, error) {
:= getBufferedEncoder(...)
defer putBufferedEncoder()
.s.Flags.Set(jsonflags.OmitTopLevelNewline | 1)
if := .s.WriteValue(); != nil {
return append(, ...),
}
return append(, .s.Buf...), nil
}
// Value represents a single raw JSON value, which may be one of the following:
// - a JSON literal (i.e., null, true, or false)
// - a JSON string (e.g., "hello, world!")
// - a JSON number (e.g., 123.456)
// - an entire JSON object (e.g., {"fizz":"buzz"} )
// - an entire JSON array (e.g., [1,2,3] )
//
// Value can represent entire array or object values, while [Token] cannot.
// Value may contain leading and/or trailing whitespace.
type Value []byte
// Clone returns a copy of v.
func ( Value) () Value {
return bytes.Clone()
}
// String returns the string formatting of v.
func ( Value) () string {
if == nil {
return "null"
}
return string()
}
// IsValid reports whether the raw JSON value is syntactically valid
// according to the specified options.
//
// By default (if no options are specified), it validates according to RFC 7493.
// It verifies whether the input is properly encoded as UTF-8,
// that escape sequences within strings decode to valid Unicode codepoints, and
// that all names in each object are unique.
// It does not verify whether numbers are representable within the limits
// of any common numeric type (e.g., float64, int64, or uint64).
//
// Relevant options include:
// - [AllowDuplicateNames]
// - [AllowInvalidUTF8]
//
// All other options are ignored.
func ( Value) ( ...Options) bool {
// TODO: Document support for [WithByteLimit] and [WithDepthLimit].
:= getBufferedDecoder(, ...)
defer putBufferedDecoder()
, := .ReadValue()
, := .ReadToken()
return == nil && == io.EOF
}
// Format formats the raw JSON value in place.
//
// By default (if no options are specified), it validates according to RFC 7493
// and produces the minimal JSON representation, where
// all whitespace is elided and JSON strings use the shortest encoding.
//
// Relevant options include:
// - [AllowDuplicateNames]
// - [AllowInvalidUTF8]
// - [EscapeForHTML]
// - [EscapeForJS]
// - [PreserveRawStrings]
// - [CanonicalizeRawInts]
// - [CanonicalizeRawFloats]
// - [ReorderRawObjects]
// - [SpaceAfterColon]
// - [SpaceAfterComma]
// - [Multiline]
// - [WithIndent]
// - [WithIndentPrefix]
//
// All other options are ignored.
//
// It is guaranteed to succeed if the value is valid according to the same options.
// If the value is already formatted, then the buffer is not mutated.
func ( *Value) ( ...Options) error {
// TODO: Document support for [WithByteLimit] and [WithDepthLimit].
return .format(, nil)
}
// format accepts two []Options to avoid the allocation appending them together.
// It is equivalent to v.Format(append(opts1, opts2...)...).
func ( *Value) (, []Options) error {
:= getBufferedEncoder(...)
defer putBufferedEncoder()
.s.Join(...)
.s.Flags.Set(jsonflags.OmitTopLevelNewline | 1)
if := .s.WriteValue(*); != nil {
return
}
if !bytes.Equal(*, .s.Buf) {
* = append((*)[:0], .s.Buf...)
}
return nil
}
// Compact removes all whitespace from the raw JSON value.
//
// It does not reformat JSON strings or numbers to use any other representation.
// To maximize the set of JSON values that can be formatted,
// this permits values with duplicate names and invalid UTF-8.
//
// Compact is equivalent to calling [Value.Format] with the following options:
// - [AllowDuplicateNames](true)
// - [AllowInvalidUTF8](true)
// - [PreserveRawStrings](true)
//
// Any options specified by the caller are applied after the initial set
// and may deliberately override prior options.
func ( *Value) ( ...Options) error {
return .format([]Options{
AllowDuplicateNames(true),
AllowInvalidUTF8(true),
PreserveRawStrings(true),
}, )
}
// Indent reformats the whitespace in the raw JSON value so that each element
// in a JSON object or array begins on a indented line according to the nesting.
//
// It does not reformat JSON strings or numbers to use any other representation.
// To maximize the set of JSON values that can be formatted,
// this permits values with duplicate names and invalid UTF-8.
//
// Indent is equivalent to calling [Value.Format] with the following options:
// - [AllowDuplicateNames](true)
// - [AllowInvalidUTF8](true)
// - [PreserveRawStrings](true)
// - [Multiline](true)
//
// Any options specified by the caller are applied after the initial set
// and may deliberately override prior options.
func ( *Value) ( ...Options) error {
return .format([]Options{
AllowDuplicateNames(true),
AllowInvalidUTF8(true),
PreserveRawStrings(true),
Multiline(true),
}, )
}
// Canonicalize canonicalizes the raw JSON value according to the
// JSON Canonicalization Scheme (JCS) as defined by RFC 8785
// where it produces a stable representation of a JSON value.
//
// JSON strings are formatted to use their minimal representation,
// JSON numbers are formatted as double precision numbers according
// to some stable serialization algorithm.
// JSON object members are sorted in ascending order by name.
// All whitespace is removed.
//
// The output stability is dependent on the stability of the application data
// (see RFC 8785, Appendix E). It cannot produce stable output from
// fundamentally unstable input. For example, if the JSON value
// contains ephemeral data (e.g., a frequently changing timestamp),
// then the value is still unstable regardless of whether this is called.
//
// Canonicalize is equivalent to calling [Value.Format] with the following options:
// - [CanonicalizeRawInts](true)
// - [CanonicalizeRawFloats](true)
// - [ReorderRawObjects](true)
//
// Any options specified by the caller are applied after the initial set
// and may deliberately override prior options.
//
// Note that JCS treats all JSON numbers as IEEE 754 double precision numbers.
// Any numbers with precision beyond what is representable by that form
// will lose their precision when canonicalized. For example, integer values
// beyond ±2⁵³ will lose their precision. To preserve the original representation
// of JSON integers, additionally set [CanonicalizeRawInts] to false:
//
// v.Canonicalize(jsontext.CanonicalizeRawInts(false))
func ( *Value) ( ...Options) error {
return .format([]Options{
CanonicalizeRawInts(true),
CanonicalizeRawFloats(true),
ReorderRawObjects(true),
}, )
}
// MarshalJSON returns v as the JSON encoding of v.
// It returns the stored value as the raw JSON output without any validation.
// If v is nil, then this returns a JSON null.
func ( Value) () ([]byte, error) {
// NOTE: This matches the behavior of v1 json.RawMessage.MarshalJSON.
if == nil {
return []byte("null"), nil
}
return , nil
}
// UnmarshalJSON sets v as the JSON encoding of b.
// It stores a copy of the provided raw JSON input without any validation.
func ( *Value) ( []byte) error {
// NOTE: This matches the behavior of v1 json.RawMessage.UnmarshalJSON.
if == nil {
return errors.New("jsontext.Value: UnmarshalJSON on nil pointer")
}
* = append((*)[:0], ...)
return nil
}
// Kind returns the starting token kind.
// For a valid value, this will never include '}' or ']'.
func ( Value) () Kind {
if := [jsonwire.ConsumeWhitespace():]; len() > 0 {
return Kind([0]).normalize()
}
return invalidKind
}
const commaAndWhitespace = ", \n\r\t"
type objectMember struct {
// name is the unquoted name.
name []byte // e.g., "name"
// buffer is the entirety of the raw JSON object member
// starting from right after the previous member (or opening '{')
// until right after the member value.
buffer []byte // e.g., `, \n\r\t"name": "value"`
}
func ( objectMember) ( objectMember) int {
if := jsonwire.CompareUTF16(.name, .name); != 0 {
return
}
// With [AllowDuplicateNames] or [AllowInvalidUTF8],
// names could be identical, so also sort using the member value.
return jsonwire.CompareUTF16(
bytes.TrimLeft(.buffer, commaAndWhitespace),
bytes.TrimLeft(.buffer, commaAndWhitespace))
}
var objectMemberPool = sync.Pool{New: func() any { return new([]objectMember) }}
func getObjectMembers() *[]objectMember {
:= objectMemberPool.Get().(*[]objectMember)
* = (*)[:0]
return
}
func putObjectMembers( *[]objectMember) {
if cap(*) < 1<<10 {
clear(*) // avoid pinning name and buffer
objectMemberPool.Put()
}
}
// mustReorderObjects reorders in-place all object members in a JSON value,
// which must be valid otherwise it panics.
func mustReorderObjects( []byte) {
// Obtain a buffered encoder just to use its internal buffer as
// a scratch buffer for reordering object members.
:= getBufferedEncoder()
defer putBufferedEncoder()
// Disable unnecessary checks to syntactically parse the JSON value.
:= getBufferedDecoder()
defer putBufferedDecoder()
.s.Flags.Set(jsonflags.AllowDuplicateNames | jsonflags.AllowInvalidUTF8 | 1)
mustReorderObjectsFromDecoder(, &.s.Buf) // per RFC 8785, section 3.2.3
}
// mustReorderObjectsFromDecoder recursively reorders all object members in place
// according to the ordering specified in RFC 8785, section 3.2.3.
//
// Pre-conditions:
// - The value is valid (i.e., no decoder errors should ever occur).
// - Initial call is provided a Decoder reading from the start of v.
//
// Post-conditions:
// - Exactly one JSON value is read from the Decoder.
// - All fully-parsed JSON objects are reordered by directly moving
// the members in the value buffer.
//
// The runtime is approximately O(n·log(n)) + O(m·log(m)),
// where n is len(v) and m is the total number of object members.
func mustReorderObjectsFromDecoder( *Decoder, *[]byte) {
switch , := .ReadToken(); .Kind() {
case '{':
// Iterate and collect the name and offsets for every object member.
:= getObjectMembers()
defer putObjectMembers()
var objectMember
:= true
:= .InputOffset() // offset after '{'
for .PeekKind() != '}' {
:= .InputOffset()
var jsonwire.ValueFlags
, := .s.ReadValue(&)
= jsonwire.UnquoteMayCopy(, .IsVerbatim())
(, )
:= .InputOffset()
:= objectMember{, .s.buf[:]}
if && len(*) > 0 {
= objectMember.Compare(, ) < 0
}
* = append(*, )
=
}
:= .InputOffset() // offset before '}'
.ReadToken()
// Sort the members; return early if it's already sorted.
if {
return
}
:= (*)[0].buffer
slices.SortFunc(*, objectMember.Compare)
:= (*)[0].buffer
// Append the reordered members to a new buffer,
// then copy the reordered members back over the original members.
// Avoid swapping in place since each member may be a different size
// where moving a member over a smaller member may corrupt the data
// for subsequent members before they have been moved.
//
// The following invariant must hold:
// sum([m.after-m.before for m in members]) == afterBody-beforeBody
:= func( []byte) []byte {
return [:len()-len(bytes.TrimLeft(, commaAndWhitespace))]
}
:= (*)[:0]
for , := range * {
switch {
case == 0 && &.buffer[0] != &[0]:
// First member after sorting is not the first member before sorting,
// so use the prefix of the first member before sorting.
= append(, ()...)
= append(, bytes.TrimLeft(.buffer, commaAndWhitespace)...)
case != 0 && &.buffer[0] == &[0]:
// Later member after sorting is the first member before sorting,
// so use the prefix of the first member after sorting.
= append(, ()...)
= append(, bytes.TrimLeft(.buffer, commaAndWhitespace)...)
default:
= append(, .buffer...)
}
}
if int(-) != len() {
panic("BUG: length invariant violated")
}
copy(.s.buf[:], )
// Update scratch buffer to the largest amount ever used.
if len() > len(*) {
* =
}
case '[':
for .PeekKind() != ']' {
(, )
}
.ReadToken()
default:
if != nil {
panic("BUG: " + .Error())
}
}
}
![]() |
The pages are generated with Golds v0.7.7-preview. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |