Initial commit - Tutus Ordered JSON
This commit is contained in:
commit
8c9518167d
|
|
@ -0,0 +1,119 @@
|
||||||
|
name: Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
types: [opened, synchronize]
|
||||||
|
paths-ignore:
|
||||||
|
- '*.md'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
GO111MODULE: "on"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test_cover:
|
||||||
|
name: Coverage
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: 1.25
|
||||||
|
|
||||||
|
- name: Write coverage profile
|
||||||
|
run: go test -v ./... -coverprofile=./coverage.txt -covermode=atomic
|
||||||
|
|
||||||
|
- name: Upload coverage results to Codecov
|
||||||
|
uses: codecov/codecov-action@v2
|
||||||
|
with:
|
||||||
|
fail_ci_if_error: false
|
||||||
|
path_to_write_report: ./coverage.txt
|
||||||
|
verbose: true
|
||||||
|
|
||||||
|
tests:
|
||||||
|
name: Go
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go_versions: [ '1.24', '1.25' ]
|
||||||
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
|
exclude:
|
||||||
|
# Only latest Go version for Windows and MacOS.
|
||||||
|
- os: windows-latest
|
||||||
|
go_versions: '1.24'
|
||||||
|
- os: macos-latest
|
||||||
|
go_versions: '1.24'
|
||||||
|
# Exclude latest Go version for Ubuntu as Coverage uses it.
|
||||||
|
- os: ubuntu-latest
|
||||||
|
go_versions: '1.25'
|
||||||
|
fail-fast: false
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '${{ matrix.go_versions }}'
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: go test -v -race ./...
|
||||||
|
|
||||||
|
lint:
|
||||||
|
name: Linter
|
||||||
|
uses: nspcc-dev/.github/.github/workflows/go-linter.yml@master
|
||||||
|
|
||||||
|
codeql:
|
||||||
|
name: CodeQL
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'go' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||||
|
# Learn more:
|
||||||
|
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v3
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v3
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Tutus Ordered JSON
|
||||||
|
|
||||||
|
Ordered JSON serialization library for the Tutus blockchain.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Provides deterministic JSON marshaling with preserved key ordering.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/tutus-one/tutus-ordered-json"
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Part of the [Tutus](https://github.com/tutus-one/tutus-chain) blockchain infrastructure.
|
||||||
|
|
@ -0,0 +1,252 @@
|
||||||
|
// Copyright 2011 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.
|
||||||
|
|
||||||
|
// Large data benchmark.
|
||||||
|
// The JSON data is a summary of agl's changes in the
|
||||||
|
// go, webkit, and chromium open source projects.
|
||||||
|
// We benchmark converting between the JSON form
|
||||||
|
// and in-memory data structures.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type codeResponse struct {
|
||||||
|
Tree *codeNode `json:"tree"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type codeNode struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Kids []*codeNode `json:"kids"`
|
||||||
|
CLWeight float64 `json:"cl_weight"`
|
||||||
|
Touches int `json:"touches"`
|
||||||
|
MinT int64 `json:"min_t"`
|
||||||
|
MaxT int64 `json:"max_t"`
|
||||||
|
MeanT int64 `json:"mean_t"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var codeJSON []byte
|
||||||
|
var codeStruct codeResponse
|
||||||
|
|
||||||
|
func codeInit() {
|
||||||
|
f, err := os.Open("testdata/code.json.gz")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
gz, err := gzip.NewReader(f)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
data, err := io.ReadAll(gz)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
codeJSON = data
|
||||||
|
|
||||||
|
if err := Unmarshal(codeJSON, &codeStruct); err != nil {
|
||||||
|
panic("unmarshal code.json: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if data, err = Marshal(&codeStruct); err != nil {
|
||||||
|
panic("marshal code.json: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(data, codeJSON) {
|
||||||
|
println("different lengths", len(data), len(codeJSON))
|
||||||
|
for i := 0; i < len(data) && i < len(codeJSON); i++ {
|
||||||
|
if data[i] != codeJSON[i] {
|
||||||
|
println("re-marshal: changed at byte", i)
|
||||||
|
println("orig: ", string(codeJSON[i-10:i+10]))
|
||||||
|
println("new: ", string(data[i-10:i+10]))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("re-marshal code.json: different result")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCodeEncoder(b *testing.B) {
|
||||||
|
if codeJSON == nil {
|
||||||
|
b.StopTimer()
|
||||||
|
codeInit()
|
||||||
|
b.StartTimer()
|
||||||
|
}
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
enc := NewEncoder(io.Discard)
|
||||||
|
for pb.Next() {
|
||||||
|
if err := enc.Encode(&codeStruct); err != nil {
|
||||||
|
b.Fatal("Encode:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.SetBytes(int64(len(codeJSON)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCodeMarshal(b *testing.B) {
|
||||||
|
if codeJSON == nil {
|
||||||
|
b.StopTimer()
|
||||||
|
codeInit()
|
||||||
|
b.StartTimer()
|
||||||
|
}
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
if _, err := Marshal(&codeStruct); err != nil {
|
||||||
|
b.Fatal("Marshal:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.SetBytes(int64(len(codeJSON)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCodeDecoder(b *testing.B) {
|
||||||
|
if codeJSON == nil {
|
||||||
|
b.StopTimer()
|
||||||
|
codeInit()
|
||||||
|
b.StartTimer()
|
||||||
|
}
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
dec := NewDecoder(&buf)
|
||||||
|
var r codeResponse
|
||||||
|
for pb.Next() {
|
||||||
|
buf.Write(codeJSON)
|
||||||
|
// hide EOF
|
||||||
|
buf.WriteByte('\n')
|
||||||
|
buf.WriteByte('\n')
|
||||||
|
buf.WriteByte('\n')
|
||||||
|
if err := dec.Decode(&r); err != nil {
|
||||||
|
b.Fatal("Decode:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.SetBytes(int64(len(codeJSON)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecoderStream(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
var buf bytes.Buffer
|
||||||
|
dec := NewDecoder(&buf)
|
||||||
|
buf.WriteString(`"` + strings.Repeat("x", 1000000) + `"` + "\n\n\n")
|
||||||
|
var x any
|
||||||
|
if err := dec.Decode(&x); err != nil {
|
||||||
|
b.Fatal("Decode:", err)
|
||||||
|
}
|
||||||
|
ones := strings.Repeat(" 1\n", 300000) + "\n\n\n"
|
||||||
|
b.StartTimer()
|
||||||
|
for i := range b.N {
|
||||||
|
if i%300000 == 0 {
|
||||||
|
buf.WriteString(ones)
|
||||||
|
}
|
||||||
|
x = nil
|
||||||
|
if err := dec.Decode(&x); err != nil || x != 1.0 {
|
||||||
|
b.Fatalf("Decode: %v after %d", err, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCodeUnmarshal(b *testing.B) {
|
||||||
|
if codeJSON == nil {
|
||||||
|
b.StopTimer()
|
||||||
|
codeInit()
|
||||||
|
b.StartTimer()
|
||||||
|
}
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
var r codeResponse
|
||||||
|
if err := Unmarshal(codeJSON, &r); err != nil {
|
||||||
|
b.Fatal("Unmarshal:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.SetBytes(int64(len(codeJSON)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCodeUnmarshalReuse(b *testing.B) {
|
||||||
|
if codeJSON == nil {
|
||||||
|
b.StopTimer()
|
||||||
|
codeInit()
|
||||||
|
b.StartTimer()
|
||||||
|
}
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
var r codeResponse
|
||||||
|
for pb.Next() {
|
||||||
|
if err := Unmarshal(codeJSON, &r); err != nil {
|
||||||
|
b.Fatal("Unmarshal:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// TODO(bcmills): Is there a missing b.SetBytes here?
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalString(b *testing.B) {
|
||||||
|
data := []byte(`"hello, world"`)
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
var s string
|
||||||
|
for pb.Next() {
|
||||||
|
if err := Unmarshal(data, &s); err != nil {
|
||||||
|
b.Fatal("Unmarshal:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalFloat64(b *testing.B) {
|
||||||
|
data := []byte(`3.14`)
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
var f float64
|
||||||
|
for pb.Next() {
|
||||||
|
if err := Unmarshal(data, &f); err != nil {
|
||||||
|
b.Fatal("Unmarshal:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalInt64(b *testing.B) {
|
||||||
|
data := []byte(`3`)
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
var x int64
|
||||||
|
for pb.Next() {
|
||||||
|
if err := Unmarshal(data, &x); err != nil {
|
||||||
|
b.Fatal("Unmarshal:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkIssue10335(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
j := []byte(`{"a":{ }}`)
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
var s struct{}
|
||||||
|
for pb.Next() {
|
||||||
|
if err := Unmarshal(j, &s); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmapped(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
j := []byte(`{"s": "hello", "y": 2, "o": {"x": 0}, "a": [1, 99, {"x": 1}]}`)
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
var s struct{}
|
||||||
|
for pb.Next() {
|
||||||
|
if err := Unmarshal(j, &s); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,912 @@
|
||||||
|
// Copyright 2011 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.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Optionals struct {
|
||||||
|
Sr string `json:"sr"`
|
||||||
|
So string `json:"so,omitempty"`
|
||||||
|
Sw string `json:"-"`
|
||||||
|
|
||||||
|
Ir int `json:"omitempty"` // actually named omitempty, not an option
|
||||||
|
Io int `json:"io,omitempty"`
|
||||||
|
|
||||||
|
Slr []string `json:"slr,random"` //nolint:staticcheck // It's intentionally wrong.
|
||||||
|
Slo []string `json:"slo,omitempty"`
|
||||||
|
|
||||||
|
Mr map[string]any `json:"mr"`
|
||||||
|
Mo map[string]any `json:",omitempty"`
|
||||||
|
|
||||||
|
Fr float64 `json:"fr"`
|
||||||
|
Fo float64 `json:"fo,omitempty"`
|
||||||
|
|
||||||
|
Br bool `json:"br"`
|
||||||
|
Bo bool `json:"bo,omitempty"`
|
||||||
|
|
||||||
|
Ur uint `json:"ur"`
|
||||||
|
Uo uint `json:"uo,omitempty"`
|
||||||
|
|
||||||
|
Str struct{} `json:"str"`
|
||||||
|
Sto struct{} `json:"sto,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var optionalsExpected = `{
|
||||||
|
"sr": "",
|
||||||
|
"omitempty": 0,
|
||||||
|
"slr": null,
|
||||||
|
"mr": {},
|
||||||
|
"fr": 0,
|
||||||
|
"br": false,
|
||||||
|
"ur": 0,
|
||||||
|
"str": {},
|
||||||
|
"sto": {}
|
||||||
|
}`
|
||||||
|
|
||||||
|
func TestOmitEmpty(t *testing.T) {
|
||||||
|
var o Optionals
|
||||||
|
o.Sw = "something"
|
||||||
|
o.Mr = map[string]any{}
|
||||||
|
o.Mo = map[string]any{}
|
||||||
|
|
||||||
|
got, err := MarshalIndent(&o, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := string(got); got != optionalsExpected {
|
||||||
|
t.Errorf(" got: %s\nwant: %s\n", got, optionalsExpected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type StringTag struct {
|
||||||
|
BoolStr bool `json:",string"`
|
||||||
|
IntStr int64 `json:",string"`
|
||||||
|
StrStr string `json:",string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringTagExpected = `{
|
||||||
|
"BoolStr": "true",
|
||||||
|
"IntStr": "42",
|
||||||
|
"StrStr": "\u0022xzbit\u0022"
|
||||||
|
}`
|
||||||
|
|
||||||
|
func TestStringTag(t *testing.T) {
|
||||||
|
var s StringTag
|
||||||
|
s.BoolStr = true
|
||||||
|
s.IntStr = 42
|
||||||
|
s.StrStr = "xzbit"
|
||||||
|
got, err := MarshalIndent(&s, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := string(got); got != stringTagExpected {
|
||||||
|
t.Fatalf(" got: %s\nwant: %s\n", got, stringTagExpected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that it round-trips.
|
||||||
|
var s2 StringTag
|
||||||
|
err = NewDecoder(bytes.NewReader(got)).Decode(&s2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Decode: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(s, s2) {
|
||||||
|
t.Fatalf("decode didn't match.\nsource: %#v\nEncoded as:\n%s\ndecode: %#v", s, string(got), s2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// byte slices are special even if they're renamed types.
|
||||||
|
type renamedByte byte
|
||||||
|
type renamedByteSlice []byte
|
||||||
|
type renamedRenamedByteSlice []renamedByte
|
||||||
|
|
||||||
|
func TestEncodeRenamedByteSlice(t *testing.T) {
|
||||||
|
s := renamedByteSlice("abc")
|
||||||
|
result, err := Marshal(s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expect := `"YWJj"`
|
||||||
|
if string(result) != expect {
|
||||||
|
t.Errorf(" got %s want %s", result, expect)
|
||||||
|
}
|
||||||
|
r := renamedRenamedByteSlice("abc")
|
||||||
|
result, err = Marshal(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(result) != expect {
|
||||||
|
t.Errorf(" got %s want %s", result, expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var unsupportedValues = []any{
|
||||||
|
math.NaN(),
|
||||||
|
math.Inf(-1),
|
||||||
|
math.Inf(1),
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnsupportedValues(t *testing.T) {
|
||||||
|
for _, v := range unsupportedValues {
|
||||||
|
if _, err := Marshal(v); err != nil {
|
||||||
|
if _, ok := err.(*UnsupportedValueError); !ok { //nolint:errorlint // It must match exactly, it's a test.
|
||||||
|
t.Errorf("for %v, got %T want UnsupportedValueError", v, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("for %v, expected error", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ref has Marshaler and Unmarshaler methods with pointer receiver.
|
||||||
|
type Ref int
|
||||||
|
|
||||||
|
func (*Ref) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(`"ref"`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Ref) UnmarshalJSON([]byte) error {
|
||||||
|
*r = 12
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Val has Marshaler methods with value receiver.
|
||||||
|
type Val int
|
||||||
|
|
||||||
|
func (Val) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(`"val"`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefText has Marshaler and Unmarshaler methods with pointer receiver.
|
||||||
|
type RefText int
|
||||||
|
|
||||||
|
func (*RefText) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(`"ref"`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RefText) UnmarshalText([]byte) error {
|
||||||
|
*r = 13
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValText has Marshaler methods with value receiver.
|
||||||
|
type ValText int
|
||||||
|
|
||||||
|
func (ValText) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(`"val"`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRefValMarshal(t *testing.T) {
|
||||||
|
var s = struct {
|
||||||
|
R0 Ref
|
||||||
|
R1 *Ref
|
||||||
|
R2 RefText
|
||||||
|
R3 *RefText
|
||||||
|
V0 Val
|
||||||
|
V1 *Val
|
||||||
|
V2 ValText
|
||||||
|
V3 *ValText
|
||||||
|
}{
|
||||||
|
R0: 12,
|
||||||
|
R1: new(Ref),
|
||||||
|
R2: 14,
|
||||||
|
R3: new(RefText),
|
||||||
|
V0: 13,
|
||||||
|
V1: new(Val),
|
||||||
|
V2: 15,
|
||||||
|
V3: new(ValText),
|
||||||
|
}
|
||||||
|
const want = `{"R0":"ref","R1":"ref","R2":"\u0022ref\u0022","R3":"\u0022ref\u0022","V0":"val","V1":"val","V2":"\u0022val\u0022","V3":"\u0022val\u0022"}`
|
||||||
|
b, err := Marshal(&s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Marshal: %v", err)
|
||||||
|
}
|
||||||
|
if got := string(b); got != want {
|
||||||
|
t.Errorf("got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// C implements Marshaler and returns unescaped JSON.
|
||||||
|
type C int
|
||||||
|
|
||||||
|
func (C) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(`"<&>"`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CText implements Marshaler and returns unescaped text.
|
||||||
|
type CText int
|
||||||
|
|
||||||
|
func (CText) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(`"<&>"`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshaler_NeoGo_PR2174(t *testing.T) {
|
||||||
|
source := "IOU(欠条币):一种支持负数的NEP-17(非严格意义上的)资产,合约无存储区,账户由区块链浏览器统计"
|
||||||
|
b, err := Marshal(source)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Marshal(c): %v", err)
|
||||||
|
}
|
||||||
|
want := `"` + `IOU\uFF08\u6B20\u6761\u5E01\uFF09\uFF1A\u4E00\u79CD\u652F\u6301\u8D1F\u6570\u7684NEP-17\uFF08\u975E\u4E25\u683C\u610F\u4E49\u4E0A\u7684\uFF09\u8D44\u4EA7\uFF0C\u5408\u7EA6\u65E0\u5B58\u50A8\u533A\uFF0C\u8D26\u6237\u7531\u533A\u5757\u94FE\u6D4F\u89C8\u5668\u7EDF\u8BA1` + `"`
|
||||||
|
if got := string(b); got != want {
|
||||||
|
t.Errorf("Marshal(c) = %#q, want %#q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalerEscaping(t *testing.T) {
|
||||||
|
var c C
|
||||||
|
want := `"\u003C\u0026\u003E"`
|
||||||
|
b, err := Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Marshal(c): %v", err)
|
||||||
|
}
|
||||||
|
if got := string(b); got != want {
|
||||||
|
t.Errorf("Marshal(c) = %#q, want %#q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ct CText
|
||||||
|
want = `"\u0022\u003C\u0026\u003E\u0022"`
|
||||||
|
b, err = Marshal(ct)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Marshal(ct): %v", err)
|
||||||
|
}
|
||||||
|
if got := string(b); got != want {
|
||||||
|
t.Errorf("Marshal(ct) = %#q, want %#q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntType int
|
||||||
|
|
||||||
|
type MyStruct struct {
|
||||||
|
IntType
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnonymousNonstruct(t *testing.T) {
|
||||||
|
var i IntType = 11
|
||||||
|
a := MyStruct{i}
|
||||||
|
const want = `{"IntType":11}`
|
||||||
|
|
||||||
|
b, err := Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Marshal: %v", err)
|
||||||
|
}
|
||||||
|
if got := string(b); got != want {
|
||||||
|
t.Errorf("got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type unexportedIntType int
|
||||||
|
|
||||||
|
type MyStructWithUnexportedIntType struct {
|
||||||
|
unexportedIntType
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnonymousNonstructWithUnexportedType(t *testing.T) {
|
||||||
|
a := MyStructWithUnexportedIntType{11}
|
||||||
|
const want = `{}`
|
||||||
|
|
||||||
|
b, err := Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Marshal: %v", err)
|
||||||
|
}
|
||||||
|
if got := string(b); got != want {
|
||||||
|
t.Errorf("got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MyStructContainingUnexportedStruct struct {
|
||||||
|
unexportedStructType1
|
||||||
|
unexportedIntType
|
||||||
|
}
|
||||||
|
|
||||||
|
type unexportedStructType1 struct {
|
||||||
|
ExportedIntType1
|
||||||
|
unexportedIntType
|
||||||
|
unexportedStructType2
|
||||||
|
}
|
||||||
|
|
||||||
|
type unexportedStructType2 struct {
|
||||||
|
ExportedIntType2
|
||||||
|
unexportedIntType
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExportedIntType1 int
|
||||||
|
type ExportedIntType2 int
|
||||||
|
|
||||||
|
func TestUnexportedAnonymousStructWithExportedType(t *testing.T) {
|
||||||
|
s2 := unexportedStructType2{3, 4}
|
||||||
|
s1 := unexportedStructType1{1, 2, s2}
|
||||||
|
a := MyStructContainingUnexportedStruct{s1, 6}
|
||||||
|
const want = `{"ExportedIntType1":1,"ExportedIntType2":3}`
|
||||||
|
|
||||||
|
b, err := Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Marshal: %v", err)
|
||||||
|
}
|
||||||
|
if got := string(b); got != want {
|
||||||
|
t.Errorf("got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type BugA struct {
|
||||||
|
S string
|
||||||
|
}
|
||||||
|
|
||||||
|
type BugB struct {
|
||||||
|
BugA
|
||||||
|
S string
|
||||||
|
}
|
||||||
|
|
||||||
|
type BugC struct {
|
||||||
|
S string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legal Go: We never use the repeated embedded field (S).
|
||||||
|
type BugX struct {
|
||||||
|
A int
|
||||||
|
BugA
|
||||||
|
BugB
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue 16042. Even if a nil interface value is passed in
|
||||||
|
// as long as it implements MarshalJSON, it should be marshaled.
|
||||||
|
type nilMarshaler string
|
||||||
|
|
||||||
|
func (nm *nilMarshaler) MarshalJSON() ([]byte, error) {
|
||||||
|
if nm == nil {
|
||||||
|
return Marshal("0zenil0")
|
||||||
|
}
|
||||||
|
return Marshal("zenil:" + string(*nm))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue 16042.
|
||||||
|
func TestNilMarshal(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
v any
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{v: nil, want: `null`},
|
||||||
|
{v: new(float64), want: `0`},
|
||||||
|
{v: []any(nil), want: `null`},
|
||||||
|
{v: []string(nil), want: `null`},
|
||||||
|
{v: map[string]string(nil), want: `null`},
|
||||||
|
{v: []byte(nil), want: `null`},
|
||||||
|
{v: struct{ M string }{"gopher"}, want: `{"M":"gopher"}`},
|
||||||
|
{v: struct{ M Marshaler }{}, want: `{"M":null}`},
|
||||||
|
{v: struct{ M Marshaler }{(*nilMarshaler)(nil)}, want: `{"M":"0zenil0"}`},
|
||||||
|
{v: struct{ M any }{(*nilMarshaler)(nil)}, want: `{"M":null}`},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range testCases {
|
||||||
|
out, err := Marshal(tt.v)
|
||||||
|
if err != nil || string(out) != tt.want {
|
||||||
|
t.Errorf("Marshal(%#v) = %#q, %#v, want %#q, nil", tt.v, out, err, tt.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue 5245.
|
||||||
|
func TestEmbeddedBug(t *testing.T) {
|
||||||
|
v := BugB{
|
||||||
|
BugA{"A"},
|
||||||
|
"B",
|
||||||
|
}
|
||||||
|
b, err := Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Marshal:", err)
|
||||||
|
}
|
||||||
|
want := `{"S":"B"}`
|
||||||
|
got := string(b)
|
||||||
|
if got != want {
|
||||||
|
t.Fatalf("Marshal: got %s want %s", got, want)
|
||||||
|
}
|
||||||
|
// Now check that the duplicate field, S, does not appear.
|
||||||
|
x := BugX{
|
||||||
|
A: 23,
|
||||||
|
}
|
||||||
|
b, err = Marshal(x)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Marshal:", err)
|
||||||
|
}
|
||||||
|
want = `{"A":23}`
|
||||||
|
got = string(b)
|
||||||
|
if got != want {
|
||||||
|
t.Fatalf("Marshal: got %s want %s", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type BugD struct { // Same as BugA after tagging.
|
||||||
|
XXX string `json:"S"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BugD's tagged S field should dominate BugA's.
|
||||||
|
type BugY struct {
|
||||||
|
BugA
|
||||||
|
BugD
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that a field with a tag dominates untagged fields.
|
||||||
|
func TestTaggedFieldDominates(t *testing.T) {
|
||||||
|
v := BugY{
|
||||||
|
BugA{"BugA"},
|
||||||
|
BugD{"BugD"},
|
||||||
|
}
|
||||||
|
b, err := Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Marshal:", err)
|
||||||
|
}
|
||||||
|
want := `{"S":"BugD"}`
|
||||||
|
got := string(b)
|
||||||
|
if got != want {
|
||||||
|
t.Fatalf("Marshal: got %s want %s", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are no tags here, so S should not appear.
|
||||||
|
type BugZ struct {
|
||||||
|
BugA
|
||||||
|
BugC
|
||||||
|
BugY // Contains a tagged S field through BugD; should not dominate.
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDuplicatedFieldDisappears(t *testing.T) {
|
||||||
|
v := BugZ{
|
||||||
|
BugA{"BugA"},
|
||||||
|
BugC{"BugC"},
|
||||||
|
BugY{
|
||||||
|
BugA{"nested BugA"},
|
||||||
|
BugD{"nested BugD"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b, err := Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Marshal:", err)
|
||||||
|
}
|
||||||
|
want := `{}`
|
||||||
|
got := string(b)
|
||||||
|
if got != want {
|
||||||
|
t.Fatalf("Marshal: got %s want %s", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringBytes(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
// Test that encodeState.stringBytes and encodeState.string use the same encoding.
|
||||||
|
var r []rune
|
||||||
|
for i := '\u0000'; i <= unicode.MaxRune; i++ {
|
||||||
|
r = append(r, i)
|
||||||
|
}
|
||||||
|
s := string(r) + "\xff\xff\xffhello" // some invalid UTF-8 too
|
||||||
|
|
||||||
|
for _, escapeHTML := range []bool{true, false} {
|
||||||
|
es := &encodeState{}
|
||||||
|
es.string(s, escapeHTML)
|
||||||
|
|
||||||
|
esBytes := &encodeState{}
|
||||||
|
esBytes.stringBytes([]byte(s), escapeHTML)
|
||||||
|
|
||||||
|
enc := es.String()
|
||||||
|
encBytes := esBytes.String()
|
||||||
|
if enc != encBytes {
|
||||||
|
i := 0
|
||||||
|
for i < len(enc) && i < len(encBytes) && enc[i] == encBytes[i] {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
enc = enc[i:]
|
||||||
|
encBytes = encBytes[i:]
|
||||||
|
i = 0
|
||||||
|
for i < len(enc) && i < len(encBytes) && enc[len(enc)-i-1] == encBytes[len(encBytes)-i-1] {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
enc = enc[:len(enc)-i]
|
||||||
|
encBytes = encBytes[:len(encBytes)-i]
|
||||||
|
|
||||||
|
if len(enc) > 20 {
|
||||||
|
enc = enc[:20] + "..."
|
||||||
|
}
|
||||||
|
if len(encBytes) > 20 {
|
||||||
|
encBytes = encBytes[:20] + "..."
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Errorf("with escapeHTML=%t, encodings differ at %#q vs %#q",
|
||||||
|
escapeHTML, enc, encBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssue10281(t *testing.T) {
|
||||||
|
type Foo struct {
|
||||||
|
N Number
|
||||||
|
}
|
||||||
|
x := Foo{Number(`invalid`)}
|
||||||
|
|
||||||
|
b, err := Marshal(&x)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Marshal(&x) = %#q; want error", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTMLEscape(t *testing.T) {
|
||||||
|
var b, want bytes.Buffer
|
||||||
|
m := `{"M":"<html>foo &` + "\xe2\x80\xa8 \xe2\x80\xa9" + `</html>"}`
|
||||||
|
want.Write([]byte(`{"M":"\u003Chtml\u003Efoo \u0026\u2028 \u2029\u003C/html\u003E"}`))
|
||||||
|
HTMLEscape(&b, []byte(m))
|
||||||
|
if !bytes.Equal(b.Bytes(), want.Bytes()) {
|
||||||
|
t.Errorf("HTMLEscape(&b, []byte(m)) = %s; want %s", b.Bytes(), want.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// golang.org/issue/8582.
|
||||||
|
func TestEncodePointerString(t *testing.T) {
|
||||||
|
type stringPointer struct {
|
||||||
|
N *int64 `json:"n,string"`
|
||||||
|
}
|
||||||
|
var n int64 = 42
|
||||||
|
b, err := Marshal(stringPointer{N: &n})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Marshal: %v", err)
|
||||||
|
}
|
||||||
|
if got, want := string(b), `{"n":"42"}`; got != want {
|
||||||
|
t.Errorf("Marshal = %s, want %s", got, want)
|
||||||
|
}
|
||||||
|
var back stringPointer
|
||||||
|
err = Unmarshal(b, &back)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
if back.N == nil {
|
||||||
|
t.Fatalf("Unmarshaled nil N field")
|
||||||
|
}
|
||||||
|
if *back.N != 42 {
|
||||||
|
t.Fatalf("*N = %d; want 42", *back.N)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var encodeStringTests = []struct {
|
||||||
|
in string
|
||||||
|
out string
|
||||||
|
}{
|
||||||
|
{"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", `"\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000B\f\r\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F"`},
|
||||||
|
{"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f", `" !\u0022#$%\u0026\u0027()*\u002B,-./0123456789:;\u003C=\u003E?"`},
|
||||||
|
{"\x40\x41\x44\x45\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x54\x55\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f", `"@ADEDEFGHIJKLMNOPQTUTUVWXYZ[\\]^_"`},
|
||||||
|
{"\x60\x61\x66\x67\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x76\x77\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f", `"\u0060afgdefghijklmnopqvwtuvwxyz{|}~\u007F"`},
|
||||||
|
{"\x80\x81\x88\x89\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x98\x99\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f", `"\u0080\u0081\u0088\u0089\u0084\u0085\u0086\u0087\u0088\u0089\u008A\u008B\u008C\u008D\u008E\u008F\u0090\u0091\u0098\u0099\u0094\u0095\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009D\u009E\u009F"`},
|
||||||
|
{"\xa0\xa1\xaa\xab\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xba\xbb\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", `"\u00A0\u00A1\u00AA\u00AB\u00A4\u00A5\u00A6\u00A7\u00A8\u00A9\u00AA\u00AB\u00AC\u00AD\u00AE\u00AF\u00B0\u00B1\u00BA\u00BB\u00B4\u00B5\u00B6\u00B7\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF"`},
|
||||||
|
{"\xc0\xc1\xcc\xcd\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xdc\xdd\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf", `"\u00C0\u00C1\u00CC\u00CD\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00DC\u00DD\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF"`},
|
||||||
|
{"\xe0\xe1\xee\xef\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xfe\xff\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", `"\u00E0\u00E1\u00EE\u00EF\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00FE\u00FF\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF"`},
|
||||||
|
{"\xff\xd0", `"\u00FF\u00D0"`},
|
||||||
|
{"测试", `"\u6D4B\u8BD5"`},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeString(t *testing.T) {
|
||||||
|
for _, tt := range encodeStringTests {
|
||||||
|
b, err := Marshal(tt.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Marshal(%q): %v", tt.in, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out := string(b)
|
||||||
|
if out != tt.out {
|
||||||
|
t.Errorf("Marshal(%q) = %#q, want %#q", tt.in, out, tt.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonbyte byte
|
||||||
|
|
||||||
|
func (b jsonbyte) MarshalJSON() ([]byte, error) { return tenc(`{"JB":%d}`, b) }
|
||||||
|
|
||||||
|
type textbyte byte
|
||||||
|
|
||||||
|
func (b textbyte) MarshalText() ([]byte, error) { return tenc(`TB:%d`, b) }
|
||||||
|
|
||||||
|
type jsonint int
|
||||||
|
|
||||||
|
func (i jsonint) MarshalJSON() ([]byte, error) { return tenc(`{"JI":%d}`, i) }
|
||||||
|
|
||||||
|
type textint int
|
||||||
|
|
||||||
|
func (i textint) MarshalText() ([]byte, error) { return tenc(`TI:%d`, i) }
|
||||||
|
|
||||||
|
func tenc(format string, a ...any) ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fmt.Fprintf(&buf, format, a...)
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue 13783.
|
||||||
|
func TestEncodeBytekind(t *testing.T) {
|
||||||
|
testdata := []struct {
|
||||||
|
data any
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{byte(7), "7"},
|
||||||
|
{jsonbyte(7), `{"JB":7}`},
|
||||||
|
{textbyte(4), `"TB:4"`},
|
||||||
|
{jsonint(5), `{"JI":5}`},
|
||||||
|
{textint(1), `"TI:1"`},
|
||||||
|
{[]byte{0, 1}, `"AAE="`},
|
||||||
|
{[]jsonbyte{0, 1}, `[{"JB":0},{"JB":1}]`},
|
||||||
|
{[][]jsonbyte{{0, 1}, {3}}, `[[{"JB":0},{"JB":1}],[{"JB":3}]]`},
|
||||||
|
{[]textbyte{2, 3}, `["TB:2","TB:3"]`},
|
||||||
|
{[]jsonint{5, 4}, `[{"JI":5},{"JI":4}]`},
|
||||||
|
{[]textint{9, 3}, `["TI:9","TI:3"]`},
|
||||||
|
{[]int{9, 3}, `[9,3]`},
|
||||||
|
}
|
||||||
|
for _, d := range testdata {
|
||||||
|
js, err := Marshal(d.data)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
got, want := string(js), d.want
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("got %s, want %s", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTextMarshalerMapKeysAreSorted(t *testing.T) {
|
||||||
|
b, err := Marshal(map[unmarshalerText]int{
|
||||||
|
{"x", "y"}: 1,
|
||||||
|
{"y", "x"}: 2,
|
||||||
|
{"a", "z"}: 3,
|
||||||
|
{"z", "a"}: 4,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to Marshal text.Marshaler: %v", err)
|
||||||
|
}
|
||||||
|
const want = `{"a:z":3,"x:y":1,"y:x":2,"z:a":4}`
|
||||||
|
if string(b) != want {
|
||||||
|
t.Errorf("Marshal map with text.Marshaler keys: got %#q, want %#q", b, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var re = regexp.MustCompile
|
||||||
|
|
||||||
|
// syntactic checks on form of marshaled floating point numbers.
|
||||||
|
var badFloatREs = []*regexp.Regexp{
|
||||||
|
re(`p`), // no binary exponential notation
|
||||||
|
re(`^\+`), // no leading + sign
|
||||||
|
re(`^-?0[^.]`), // no unnecessary leading zeros
|
||||||
|
re(`^-?\.`), // leading zero required before decimal point
|
||||||
|
re(`\.(e|$)`), // no trailing decimal
|
||||||
|
re(`\.[0-9]+0(e|$)`), // no trailing zero in fraction
|
||||||
|
re(`^-?(0|[0-9]{2,})\..*e`), // exponential notation must have normalized mantissa
|
||||||
|
re(`e[0-9]`), // positive exponent must be signed
|
||||||
|
re(`e[+-]0`), // exponent must not have leading zeros
|
||||||
|
re(`e-[1-6]$`), // not tiny enough for exponential notation
|
||||||
|
re(`e+(.|1.|20)$`), // not big enough for exponential notation
|
||||||
|
re(`^-?0\.0000000`), // too tiny, should use exponential notation
|
||||||
|
re(`^-?[0-9]{22}`), // too big, should use exponential notation
|
||||||
|
re(`[1-9][0-9]{16}[1-9]`), // too many significant digits in integer
|
||||||
|
re(`[1-9][0-9.]{17}[1-9]`), // too many significant digits in decimal
|
||||||
|
// below here for float32 only
|
||||||
|
re(`[1-9][0-9]{8}[1-9]`), // too many significant digits in integer
|
||||||
|
re(`[1-9][0-9.]{9}[1-9]`), // too many significant digits in decimal
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalFloat(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
nfail := 0
|
||||||
|
test := func(f float64, bits int) {
|
||||||
|
vf := any(f)
|
||||||
|
if bits == 32 {
|
||||||
|
f = float64(float32(f)) // round
|
||||||
|
vf = float32(f)
|
||||||
|
}
|
||||||
|
bout, err := Marshal(vf)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Marshal(%T(%g)): %v", vf, vf, err)
|
||||||
|
nfail++
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out := string(bout)
|
||||||
|
|
||||||
|
// result must convert back to the same float
|
||||||
|
g, err := strconv.ParseFloat(out, bits)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Marshal(%T(%g)) = %q, cannot parse back: %v", vf, vf, out, err)
|
||||||
|
nfail++
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if f != g || fmt.Sprint(f) != fmt.Sprint(g) { // fmt.Sprint handles ±0
|
||||||
|
t.Errorf("Marshal(%T(%g)) = %q (is %g, not %g)", vf, vf, out, float32(g), vf)
|
||||||
|
nfail++
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bad := badFloatREs
|
||||||
|
if bits == 64 {
|
||||||
|
bad = bad[:len(bad)-2]
|
||||||
|
}
|
||||||
|
for _, re := range bad {
|
||||||
|
if re.MatchString(out) {
|
||||||
|
t.Errorf("Marshal(%T(%g)) = %q, must not match /%s/", vf, vf, out, re)
|
||||||
|
nfail++
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
bigger = math.Inf(+1)
|
||||||
|
smaller = math.Inf(-1)
|
||||||
|
)
|
||||||
|
|
||||||
|
var digits = "1.2345678901234567890123"
|
||||||
|
for i := len(digits); i >= 2; i-- {
|
||||||
|
for exp := -30; exp <= 30; exp++ {
|
||||||
|
for _, sign := range "+-" {
|
||||||
|
for bits := 32; bits <= 64; bits += 32 {
|
||||||
|
s := fmt.Sprintf("%c%se%d", sign, digits[:i], exp)
|
||||||
|
f, err := strconv.ParseFloat(s, bits)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
next := math.Nextafter
|
||||||
|
if bits == 32 {
|
||||||
|
next = func(g, h float64) float64 {
|
||||||
|
return float64(math.Nextafter32(float32(g), float32(h)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test(f, bits)
|
||||||
|
test(next(f, bigger), bits)
|
||||||
|
test(next(f, smaller), bits)
|
||||||
|
if nfail > 50 {
|
||||||
|
t.Fatalf("stopping test early")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test(0, 64)
|
||||||
|
test(math.Copysign(0, -1), 64)
|
||||||
|
test(0, 32)
|
||||||
|
test(math.Copysign(0, -1), 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalRawMessageValue(t *testing.T) {
|
||||||
|
type (
|
||||||
|
T1 struct {
|
||||||
|
M RawMessage `json:",omitempty"`
|
||||||
|
}
|
||||||
|
T2 struct {
|
||||||
|
M *RawMessage `json:",omitempty"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
rawNil = RawMessage(nil)
|
||||||
|
rawEmpty = RawMessage([]byte{})
|
||||||
|
rawText = RawMessage([]byte(`"foo"`))
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in any
|
||||||
|
want string
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
// Test with nil RawMessage.
|
||||||
|
{rawNil, "null", true},
|
||||||
|
{&rawNil, "null", true},
|
||||||
|
{[]any{rawNil}, "[null]", true},
|
||||||
|
{&[]any{rawNil}, "[null]", true},
|
||||||
|
{[]any{&rawNil}, "[null]", true},
|
||||||
|
{&[]any{&rawNil}, "[null]", true},
|
||||||
|
{struct{ M RawMessage }{rawNil}, `{"M":null}`, true},
|
||||||
|
{&struct{ M RawMessage }{rawNil}, `{"M":null}`, true},
|
||||||
|
{struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true},
|
||||||
|
{&struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true},
|
||||||
|
{map[string]any{"M": rawNil}, `{"M":null}`, true},
|
||||||
|
{&map[string]any{"M": rawNil}, `{"M":null}`, true},
|
||||||
|
{map[string]any{"M": &rawNil}, `{"M":null}`, true},
|
||||||
|
{&map[string]any{"M": &rawNil}, `{"M":null}`, true},
|
||||||
|
{T1{rawNil}, "{}", true},
|
||||||
|
{T2{&rawNil}, `{"M":null}`, true},
|
||||||
|
{&T1{rawNil}, "{}", true},
|
||||||
|
{&T2{&rawNil}, `{"M":null}`, true},
|
||||||
|
|
||||||
|
// Test with empty, but non-nil, RawMessage.
|
||||||
|
{rawEmpty, "", false},
|
||||||
|
{&rawEmpty, "", false},
|
||||||
|
{[]any{rawEmpty}, "", false},
|
||||||
|
{&[]any{rawEmpty}, "", false},
|
||||||
|
{[]any{&rawEmpty}, "", false},
|
||||||
|
{&[]any{&rawEmpty}, "", false},
|
||||||
|
{struct{ X RawMessage }{rawEmpty}, "", false},
|
||||||
|
{&struct{ X RawMessage }{rawEmpty}, "", false},
|
||||||
|
{struct{ X *RawMessage }{&rawEmpty}, "", false},
|
||||||
|
{&struct{ X *RawMessage }{&rawEmpty}, "", false},
|
||||||
|
{map[string]any{"nil": rawEmpty}, "", false},
|
||||||
|
{&map[string]any{"nil": rawEmpty}, "", false},
|
||||||
|
{map[string]any{"nil": &rawEmpty}, "", false},
|
||||||
|
{&map[string]any{"nil": &rawEmpty}, "", false},
|
||||||
|
{T1{rawEmpty}, "{}", true},
|
||||||
|
{T2{&rawEmpty}, "", false},
|
||||||
|
{&T1{rawEmpty}, "{}", true},
|
||||||
|
{&T2{&rawEmpty}, "", false},
|
||||||
|
|
||||||
|
// Test with RawMessage with some text.
|
||||||
|
//
|
||||||
|
// The tests below marked with Issue6458 used to generate "ImZvbyI=" instead "foo".
|
||||||
|
// This behavior was intentionally changed in Go 1.8.
|
||||||
|
// See https://github.com/golang/go/issues/14493#issuecomment-255857318
|
||||||
|
{rawText, `"foo"`, true}, // Issue6458
|
||||||
|
{&rawText, `"foo"`, true},
|
||||||
|
{[]any{rawText}, `["foo"]`, true}, // Issue6458
|
||||||
|
{&[]any{rawText}, `["foo"]`, true}, // Issue6458
|
||||||
|
{[]any{&rawText}, `["foo"]`, true},
|
||||||
|
{&[]any{&rawText}, `["foo"]`, true},
|
||||||
|
{struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, // Issue6458
|
||||||
|
{&struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true},
|
||||||
|
{struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true},
|
||||||
|
{&struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true},
|
||||||
|
{map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458
|
||||||
|
{&map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458
|
||||||
|
{map[string]any{"M": &rawText}, `{"M":"foo"}`, true},
|
||||||
|
{&map[string]any{"M": &rawText}, `{"M":"foo"}`, true},
|
||||||
|
{T1{rawText}, `{"M":"foo"}`, true}, // Issue6458
|
||||||
|
{T2{&rawText}, `{"M":"foo"}`, true},
|
||||||
|
{&T1{rawText}, `{"M":"foo"}`, true},
|
||||||
|
{&T2{&rawText}, `{"M":"foo"}`, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
b, err := Marshal(tt.in)
|
||||||
|
if ok := (err == nil); ok != tt.ok {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("test %d, unexpected failure: %v", i, err)
|
||||||
|
} else {
|
||||||
|
t.Errorf("test %d, unexpected success", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if got := string(b); got != tt.want {
|
||||||
|
t.Errorf("test %d, Marshal(%#v) = %q, want %q", i, tt.in, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ObjEnc struct {
|
||||||
|
A int
|
||||||
|
B OrderedObject
|
||||||
|
C any
|
||||||
|
}
|
||||||
|
|
||||||
|
var orderedObjectTests = []struct {
|
||||||
|
in any
|
||||||
|
out string
|
||||||
|
}{
|
||||||
|
{OrderedObject{}, `{}`},
|
||||||
|
{OrderedObject{Member{"A", []int{1, 2, 3}}, Member{"B", 23}, Member{"C", "C"}}, `{"A":[1,2,3],"B":23,"C":"C"}`},
|
||||||
|
{ObjEnc{A: 234, B: OrderedObject{Member{"K", "V"}, Member{"V", 4}}}, `{"A":234,"B":{"K":"V","V":4},"C":null}`},
|
||||||
|
{ObjEnc{A: 234, B: OrderedObject{}, C: OrderedObject{{"A", 0}}}, `{"A":234,"B":{},"C":{"A":0}}`},
|
||||||
|
{[]OrderedObject{{{"A", "Ay"}, {"B", "Bee"}}, {{"A", "Nay"}}}, `[{"A":"Ay","B":"Bee"},{"A":"Nay"}]`},
|
||||||
|
{map[string]OrderedObject{"A": {{"A", "Ay"}, {"B", "Bee"}}}, `{"A":{"A":"Ay","B":"Bee"}}`},
|
||||||
|
{map[string]any{"A": OrderedObject{{"A", "Ay"}, {"B", "Bee"}}}, `{"A":{"A":"Ay","B":"Bee"}}`},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeOrderedObject(t *testing.T) {
|
||||||
|
for i, o := range orderedObjectTests {
|
||||||
|
d, err := Marshal(o.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ds := string(d)
|
||||||
|
if o.out != ds {
|
||||||
|
t.Errorf("#%d expected '%v', was '%v'", i, o.out, ds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
package json_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Animal int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Unknown Animal = iota
|
||||||
|
Gopher
|
||||||
|
Zebra
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *Animal) UnmarshalJSON(b []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(b, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch strings.ToLower(s) {
|
||||||
|
default:
|
||||||
|
*a = Unknown
|
||||||
|
case "gopher":
|
||||||
|
*a = Gopher
|
||||||
|
case "zebra":
|
||||||
|
*a = Zebra
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Animal) MarshalJSON() ([]byte, error) {
|
||||||
|
var s string
|
||||||
|
switch a {
|
||||||
|
default:
|
||||||
|
s = "unknown"
|
||||||
|
case Gopher:
|
||||||
|
s = "gopher"
|
||||||
|
case Zebra:
|
||||||
|
s = "zebra"
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_customMarshalJSON() {
|
||||||
|
blob := `["gopher","armadillo","zebra","unknown","gopher","bee","gopher","zebra"]`
|
||||||
|
var zoo []Animal
|
||||||
|
if err := json.Unmarshal([]byte(blob), &zoo); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
census := make(map[Animal]int)
|
||||||
|
for _, animal := range zoo {
|
||||||
|
census[animal]++
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Zoo Census:\n* Gophers: %d\n* Zebras: %d\n* Unknown: %d\n",
|
||||||
|
census[Gopher], census[Zebra], census[Unknown])
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Zoo Census:
|
||||||
|
// * Gophers: 3
|
||||||
|
// * Zebras: 2
|
||||||
|
// * Unknown: 3
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,316 @@
|
||||||
|
// Copyright 2011 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.
|
||||||
|
|
||||||
|
package json_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
json "github.com/tutus-one/tutus-ordered-json"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleMarshal() {
|
||||||
|
type ColorGroup struct {
|
||||||
|
ID int
|
||||||
|
Name string
|
||||||
|
Colors []string
|
||||||
|
}
|
||||||
|
group := ColorGroup{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Reds",
|
||||||
|
Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(group)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("error:", err)
|
||||||
|
}
|
||||||
|
os.Stdout.Write(b)
|
||||||
|
// Output:
|
||||||
|
// {"ID":1,"Name":"Reds","Colors":["Crimson","Red","Ruby","Maroon"]}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleUnmarshal() {
|
||||||
|
var jsonBlob = []byte(`[
|
||||||
|
{"Name": "Platypus", "Order": "Monotremata"},
|
||||||
|
{"Name": "Quoll", "Order": "Dasyuromorphia"}
|
||||||
|
]`)
|
||||||
|
type Animal struct {
|
||||||
|
Name string
|
||||||
|
Order string
|
||||||
|
}
|
||||||
|
var animals []Animal
|
||||||
|
err := json.Unmarshal(jsonBlob, &animals)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("error:", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%+v", animals)
|
||||||
|
// Output:
|
||||||
|
// [{Name:Platypus Order:Monotremata} {Name:Quoll Order:Dasyuromorphia}]
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example uses a Decoder to decode a stream of distinct JSON values.
|
||||||
|
func ExampleDecoder() {
|
||||||
|
const jsonStream = `
|
||||||
|
{"Name": "Ed", "Text": "Knock knock."}
|
||||||
|
{"Name": "Sam", "Text": "Who's there?"}
|
||||||
|
{"Name": "Ed", "Text": "Go fmt."}
|
||||||
|
{"Name": "Sam", "Text": "Go fmt who?"}
|
||||||
|
{"Name": "Ed", "Text": "Go fmt yourself!"}
|
||||||
|
`
|
||||||
|
type Message struct {
|
||||||
|
Name, Text string
|
||||||
|
}
|
||||||
|
dec := json.NewDecoder(strings.NewReader(jsonStream))
|
||||||
|
for {
|
||||||
|
var m Message
|
||||||
|
if err := dec.Decode(&m); errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%s: %s\n", m.Name, m.Text)
|
||||||
|
}
|
||||||
|
// Output:
|
||||||
|
// Ed: Knock knock.
|
||||||
|
// Sam: Who's there?
|
||||||
|
// Ed: Go fmt.
|
||||||
|
// Sam: Go fmt who?
|
||||||
|
// Ed: Go fmt yourself!
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example uses a Decoder to decode a stream of distinct JSON values.
|
||||||
|
func ExampleDecoder_Token() {
|
||||||
|
const jsonStream = `
|
||||||
|
{"Message": "Hello", "Array": [1, 2, 3], "Null": null, "Number": 1.234}
|
||||||
|
`
|
||||||
|
dec := json.NewDecoder(strings.NewReader(jsonStream))
|
||||||
|
for {
|
||||||
|
t, err := dec.Token()
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%T: %v", t, t)
|
||||||
|
if dec.More() {
|
||||||
|
fmt.Printf(" (more)")
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
}
|
||||||
|
// Output:
|
||||||
|
// json.Delim: { (more)
|
||||||
|
// string: Message (more)
|
||||||
|
// string: Hello (more)
|
||||||
|
// string: Array (more)
|
||||||
|
// json.Delim: [ (more)
|
||||||
|
// float64: 1 (more)
|
||||||
|
// float64: 2 (more)
|
||||||
|
// float64: 3
|
||||||
|
// json.Delim: ] (more)
|
||||||
|
// string: Null (more)
|
||||||
|
// <nil>: <nil> (more)
|
||||||
|
// string: Number (more)
|
||||||
|
// float64: 1.234
|
||||||
|
// json.Delim: }
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example uses a Decoder to decode a streaming array of JSON objects.
|
||||||
|
func ExampleDecoder_Decode_stream() {
|
||||||
|
const jsonStream = `
|
||||||
|
[
|
||||||
|
{"Name": "Ed", "Text": "Knock knock."},
|
||||||
|
{"Name": "Sam", "Text": "Who's there?"},
|
||||||
|
{"Name": "Ed", "Text": "Go fmt."},
|
||||||
|
{"Name": "Sam", "Text": "Go fmt who?"},
|
||||||
|
{"Name": "Ed", "Text": "Go fmt yourself!"}
|
||||||
|
]
|
||||||
|
`
|
||||||
|
type Message struct {
|
||||||
|
Name, Text string
|
||||||
|
}
|
||||||
|
dec := json.NewDecoder(strings.NewReader(jsonStream))
|
||||||
|
|
||||||
|
// read open bracket
|
||||||
|
t, err := dec.Token()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%T: %v\n", t, t)
|
||||||
|
|
||||||
|
// while the array contains values
|
||||||
|
for dec.More() {
|
||||||
|
var m Message
|
||||||
|
// decode an array value (Message)
|
||||||
|
err := dec.Decode(&m)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%v: %v\n", m.Name, m.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// read closing bracket
|
||||||
|
t, err = dec.Token()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%T: %v\n", t, t)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// json.Delim: [
|
||||||
|
// Ed: Knock knock.
|
||||||
|
// Sam: Who's there?
|
||||||
|
// Ed: Go fmt.
|
||||||
|
// Sam: Go fmt who?
|
||||||
|
// Ed: Go fmt yourself!
|
||||||
|
// json.Delim: ]
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example uses RawMessage to delay parsing part of a JSON message.
|
||||||
|
func ExampleRawMessage_unmarshal() {
|
||||||
|
type Color struct {
|
||||||
|
Space string
|
||||||
|
Point json.RawMessage // delay parsing until we know the color space
|
||||||
|
}
|
||||||
|
type RGB struct {
|
||||||
|
R uint8
|
||||||
|
G uint8
|
||||||
|
B uint8
|
||||||
|
}
|
||||||
|
type YCbCr struct {
|
||||||
|
Y uint8
|
||||||
|
Cb int8
|
||||||
|
Cr int8
|
||||||
|
}
|
||||||
|
|
||||||
|
var j = []byte(`[
|
||||||
|
{"Space": "YCbCr", "Point": {"Y": 255, "Cb": 0, "Cr": -10}},
|
||||||
|
{"Space": "RGB", "Point": {"R": 98, "G": 218, "B": 255}}
|
||||||
|
]`)
|
||||||
|
var colors []Color
|
||||||
|
err := json.Unmarshal(j, &colors)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range colors {
|
||||||
|
var dst any
|
||||||
|
switch c.Space {
|
||||||
|
case "RGB":
|
||||||
|
dst = new(RGB)
|
||||||
|
case "YCbCr":
|
||||||
|
dst = new(YCbCr)
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(c.Point, dst)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("error:", err)
|
||||||
|
}
|
||||||
|
fmt.Println(c.Space, dst)
|
||||||
|
}
|
||||||
|
// Output:
|
||||||
|
// YCbCr &{255 0 -10}
|
||||||
|
// RGB &{98 218 255}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example uses RawMessage to use a precomputed JSON during marshal.
|
||||||
|
func ExampleRawMessage_marshal() {
|
||||||
|
h := json.RawMessage(`{"precomputed": true}`)
|
||||||
|
|
||||||
|
c := struct {
|
||||||
|
Header *json.RawMessage `json:"header"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
}{Header: &h, Body: "Hello Gophers!"}
|
||||||
|
|
||||||
|
b, err := json.MarshalIndent(&c, "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("error:", err)
|
||||||
|
}
|
||||||
|
os.Stdout.Write(b)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// {
|
||||||
|
// "header": {
|
||||||
|
// "precomputed": true
|
||||||
|
// },
|
||||||
|
// "body": "Hello Gophers!"
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleIndent() {
|
||||||
|
type Road struct {
|
||||||
|
Name string
|
||||||
|
Number int
|
||||||
|
}
|
||||||
|
roads := []Road{
|
||||||
|
{"Diamond Fork", 29},
|
||||||
|
{"Sheep Creek", 51},
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(roads)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var out bytes.Buffer
|
||||||
|
_ = json.Indent(&out, b, "=", "\t")
|
||||||
|
_, _ = out.WriteTo(os.Stdout)
|
||||||
|
// Output:
|
||||||
|
// [
|
||||||
|
// = {
|
||||||
|
// = "Name": "Diamond Fork",
|
||||||
|
// = "Number": 29
|
||||||
|
// = },
|
||||||
|
// = {
|
||||||
|
// = "Name": "Sheep Creek",
|
||||||
|
// = "Number": 51
|
||||||
|
// = }
|
||||||
|
// =]
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleOrderedObject() {
|
||||||
|
var jsonBlob = []byte(`[
|
||||||
|
{"name": "Issac Newton", "born": 1643, "died": 1727 },
|
||||||
|
{"name": "André-Marie Ampère", "born": 1777, "died": 1836 }
|
||||||
|
]`)
|
||||||
|
var people []json.OrderedObject
|
||||||
|
|
||||||
|
// Decode JSON while preserving the order of JSON key pairs.
|
||||||
|
err := json.Unmarshal(jsonBlob, &people)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("unmarshalling error:", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Decoded:\n")
|
||||||
|
for _, v := range people {
|
||||||
|
for _, a := range v {
|
||||||
|
fmt.Printf(" %v=%v", a.Key, a.Value)
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode JSON keys in the order defined by the OrderedObject.
|
||||||
|
person := json.OrderedObject{
|
||||||
|
{Key: "name", Value: "Hans Christian Ørsted"},
|
||||||
|
{Key: "born", Value: 1777},
|
||||||
|
{Key: "died", Value: 1851},
|
||||||
|
{Key: "nationality", Value: "Danish"},
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(person)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("marshalling error:", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Encoded:\n %v", string(b))
|
||||||
|
// Output:
|
||||||
|
// Decoded:
|
||||||
|
// name=Issac Newton born=1643 died=1727
|
||||||
|
// name=André-Marie Ampère born=1777 died=1836
|
||||||
|
// Encoded:
|
||||||
|
// {"name":"Hans Christian \u00D8rsted","born":1777,"died":1851,"nationality":"Danish"}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
// Copyright 2013 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.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
caseMask = ^byte(0x20) // Mask to ignore case in ASCII.
|
||||||
|
kelvin = '\u212a'
|
||||||
|
smallLongEss = '\u017f'
|
||||||
|
)
|
||||||
|
|
||||||
|
// foldFunc returns one of four different case folding equivalence
|
||||||
|
// functions, from most general (and slow) to fastest:
|
||||||
|
//
|
||||||
|
// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8
|
||||||
|
// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S')
|
||||||
|
// 3) asciiEqualFold, no special, but includes non-letters (including _)
|
||||||
|
// 4) simpleLetterEqualFold, no specials, no non-letters.
|
||||||
|
//
|
||||||
|
// The letters S and K are special because they map to 3 runes, not just 2:
|
||||||
|
// - S maps to s and to U+017F 'ſ' Latin small letter long s
|
||||||
|
// - k maps to K and to U+212A 'K' Kelvin sign
|
||||||
|
//
|
||||||
|
// See https://play.golang.org/p/tTxjOc0OGo
|
||||||
|
//
|
||||||
|
// The returned function is specialized for matching against s and
|
||||||
|
// should only be given s. It's not curried for performance reasons.
|
||||||
|
func foldFunc(s []byte) func(s, t []byte) bool {
|
||||||
|
nonLetter := false
|
||||||
|
special := false // special letter
|
||||||
|
for _, b := range s {
|
||||||
|
if b >= utf8.RuneSelf {
|
||||||
|
return bytes.EqualFold
|
||||||
|
}
|
||||||
|
upper := b & caseMask
|
||||||
|
if upper < 'A' || upper > 'Z' {
|
||||||
|
nonLetter = true
|
||||||
|
} else if upper == 'K' || upper == 'S' {
|
||||||
|
// See above for why these letters are special.
|
||||||
|
special = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if special {
|
||||||
|
return equalFoldRight
|
||||||
|
}
|
||||||
|
if nonLetter {
|
||||||
|
return asciiEqualFold
|
||||||
|
}
|
||||||
|
return simpleLetterEqualFold
|
||||||
|
}
|
||||||
|
|
||||||
|
// equalFoldRight is a specialization of bytes.EqualFold when s is
|
||||||
|
// known to be all ASCII (including punctuation), but contains an 's',
|
||||||
|
// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t.
|
||||||
|
// See comments on foldFunc.
|
||||||
|
func equalFoldRight(s, t []byte) bool {
|
||||||
|
for _, sb := range s {
|
||||||
|
if len(t) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
tb := t[0]
|
||||||
|
if tb < utf8.RuneSelf {
|
||||||
|
if sb != tb {
|
||||||
|
sbUpper := sb & caseMask
|
||||||
|
if 'A' <= sbUpper && sbUpper <= 'Z' {
|
||||||
|
if sbUpper != tb&caseMask {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t = t[1:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// sb is ASCII and t is not. t must be either kelvin
|
||||||
|
// sign or long s; sb must be s, S, k, or K.
|
||||||
|
tr, size := utf8.DecodeRune(t)
|
||||||
|
switch sb {
|
||||||
|
case 's', 'S':
|
||||||
|
if tr != smallLongEss {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case 'k', 'K':
|
||||||
|
if tr != kelvin {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
t = t[size:]
|
||||||
|
}
|
||||||
|
return len(t) <= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// asciiEqualFold is a specialization of bytes.EqualFold for use when
|
||||||
|
// s is all ASCII (but may contain non-letters) and contains no
|
||||||
|
// special-folding letters.
|
||||||
|
// See comments on foldFunc.
|
||||||
|
func asciiEqualFold(s, t []byte) bool {
|
||||||
|
if len(s) != len(t) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, sb := range s {
|
||||||
|
tb := t[i]
|
||||||
|
if sb == tb {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') {
|
||||||
|
if sb&caseMask != tb&caseMask {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// simpleLetterEqualFold is a specialization of bytes.EqualFold for
|
||||||
|
// use when s is all ASCII letters (no underscores, etc) and also
|
||||||
|
// doesn't contain 'k', 'K', 's', or 'S'.
|
||||||
|
// See comments on foldFunc.
|
||||||
|
func simpleLetterEqualFold(s, t []byte) bool {
|
||||||
|
if len(s) != len(t) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, b := range s {
|
||||||
|
if b&caseMask != t[i]&caseMask {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
// Copyright 2013 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.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
var foldTests = []struct {
|
||||||
|
fn func(s, t []byte) bool
|
||||||
|
s, t string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{equalFoldRight, "", "", true},
|
||||||
|
{equalFoldRight, "a", "a", true},
|
||||||
|
{equalFoldRight, "", "a", false},
|
||||||
|
{equalFoldRight, "a", "", false},
|
||||||
|
{equalFoldRight, "a", "A", true},
|
||||||
|
{equalFoldRight, "AB", "ab", true},
|
||||||
|
{equalFoldRight, "AB", "ac", false},
|
||||||
|
{equalFoldRight, "sbkKc", "ſbKKc", true},
|
||||||
|
{equalFoldRight, "SbKkc", "ſbKKc", true},
|
||||||
|
{equalFoldRight, "SbKkc", "ſbKK", false},
|
||||||
|
{equalFoldRight, "e", "é", false},
|
||||||
|
{equalFoldRight, "s", "S", true},
|
||||||
|
|
||||||
|
{simpleLetterEqualFold, "", "", true},
|
||||||
|
{simpleLetterEqualFold, "abc", "abc", true},
|
||||||
|
{simpleLetterEqualFold, "abc", "ABC", true},
|
||||||
|
{simpleLetterEqualFold, "abc", "ABCD", false},
|
||||||
|
{simpleLetterEqualFold, "abc", "xxx", false},
|
||||||
|
|
||||||
|
{asciiEqualFold, "a_B", "A_b", true},
|
||||||
|
{asciiEqualFold, "aa@", "aa`", false}, // verify 0x40 and 0x60 aren't case-equivalent
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFold(t *testing.T) {
|
||||||
|
for i, tt := range foldTests {
|
||||||
|
if got := tt.fn([]byte(tt.s), []byte(tt.t)); got != tt.want {
|
||||||
|
t.Errorf("%d. %q, %q = %v; want %v", i, tt.s, tt.t, got, tt.want)
|
||||||
|
}
|
||||||
|
truth := strings.EqualFold(tt.s, tt.t)
|
||||||
|
if truth != tt.want {
|
||||||
|
t.Errorf("strings.EqualFold doesn't agree with case %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFoldAgainstUnicode(t *testing.T) {
|
||||||
|
const bufSize = 5
|
||||||
|
buf1 := make([]byte, 0, bufSize)
|
||||||
|
buf2 := make([]byte, 0, bufSize)
|
||||||
|
var runes []rune
|
||||||
|
for i := 0x20; i <= 0x7f; i++ {
|
||||||
|
runes = append(runes, rune(i))
|
||||||
|
}
|
||||||
|
runes = append(runes, kelvin, smallLongEss)
|
||||||
|
|
||||||
|
funcs := []struct {
|
||||||
|
name string
|
||||||
|
fold func(s, t []byte) bool
|
||||||
|
letter bool // must be ASCII letter
|
||||||
|
simple bool // must be simple ASCII letter (not 'S' or 'K')
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "equalFoldRight",
|
||||||
|
fold: equalFoldRight,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "asciiEqualFold",
|
||||||
|
fold: asciiEqualFold,
|
||||||
|
simple: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "simpleLetterEqualFold",
|
||||||
|
fold: simpleLetterEqualFold,
|
||||||
|
simple: true,
|
||||||
|
letter: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ff := range funcs {
|
||||||
|
for _, r := range runes {
|
||||||
|
if r >= utf8.RuneSelf {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ff.letter && !isASCIILetter(byte(r)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ff.simple && (r == 's' || r == 'S' || r == 'k' || r == 'K') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, r2 := range runes {
|
||||||
|
buf1 := append(buf1[:0], 'x')
|
||||||
|
buf2 := append(buf2[:0], 'x')
|
||||||
|
buf1 = buf1[:1+utf8.EncodeRune(buf1[1:bufSize], r)]
|
||||||
|
buf2 = buf2[:1+utf8.EncodeRune(buf2[1:bufSize], r2)]
|
||||||
|
buf1 = append(buf1, 'x')
|
||||||
|
buf2 = append(buf2, 'x')
|
||||||
|
want := bytes.EqualFold(buf1, buf2)
|
||||||
|
if got := ff.fold(buf1, buf2); got != want {
|
||||||
|
t.Errorf("%s(%q, %q) = %v; want %v", ff.name, buf1, buf2, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isASCIILetter(b byte) bool {
|
||||||
|
return ('A' <= b && b <= 'Z') || ('a' <= b && b <= 'z')
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
module github.com/tutus-one/tutus-ordered-json
|
||||||
|
|
||||||
|
go 1.24
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
// Copyright 2010 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.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import "bytes"
|
||||||
|
|
||||||
|
// Compact appends to dst the JSON-encoded src with
|
||||||
|
// insignificant space characters elided.
|
||||||
|
func Compact(dst *bytes.Buffer, src []byte) error {
|
||||||
|
return compact(dst, src, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compact(dst *bytes.Buffer, src []byte, escape bool) error {
|
||||||
|
origLen := dst.Len()
|
||||||
|
var scan scanner
|
||||||
|
scan.reset()
|
||||||
|
start := 0
|
||||||
|
for i, c := range src {
|
||||||
|
if escape && (c == '<' || c == '>' || c == '&') {
|
||||||
|
if start < i {
|
||||||
|
dst.Write(src[start:i])
|
||||||
|
}
|
||||||
|
dst.WriteString(`\u00`)
|
||||||
|
dst.WriteByte(hex[c>>4])
|
||||||
|
dst.WriteByte(hex[c&0xF])
|
||||||
|
start = i + 1
|
||||||
|
}
|
||||||
|
// Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
|
||||||
|
if c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
|
||||||
|
if start < i {
|
||||||
|
dst.Write(src[start:i])
|
||||||
|
}
|
||||||
|
dst.WriteString(`\u202`)
|
||||||
|
dst.WriteByte(hex[src[i+2]&0xF])
|
||||||
|
start = i + 3
|
||||||
|
}
|
||||||
|
v := scan.step(&scan, c)
|
||||||
|
if v >= scanSkipSpace {
|
||||||
|
if v == scanError {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if start < i {
|
||||||
|
dst.Write(src[start:i])
|
||||||
|
}
|
||||||
|
start = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if scan.eof() == scanError {
|
||||||
|
dst.Truncate(origLen)
|
||||||
|
return scan.err
|
||||||
|
}
|
||||||
|
if start < len(src) {
|
||||||
|
dst.Write(src[start:])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newline(dst *bytes.Buffer, prefix, indent string, depth int) {
|
||||||
|
dst.WriteByte('\n')
|
||||||
|
dst.WriteString(prefix)
|
||||||
|
for range depth {
|
||||||
|
dst.WriteString(indent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indent appends to dst an indented form of the JSON-encoded src.
|
||||||
|
// Each element in a JSON object or array begins on a new,
|
||||||
|
// indented line beginning with prefix followed by one or more
|
||||||
|
// copies of indent according to the indentation nesting.
|
||||||
|
// The data appended to dst does not begin with the prefix nor
|
||||||
|
// any indentation, to make it easier to embed inside other formatted JSON data.
|
||||||
|
// Although leading space characters (space, tab, carriage return, newline)
|
||||||
|
// at the beginning of src are dropped, trailing space characters
|
||||||
|
// at the end of src are preserved and copied to dst.
|
||||||
|
// For example, if src has no trailing spaces, neither will dst;
|
||||||
|
// if src ends in a trailing newline, so will dst.
|
||||||
|
func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
|
||||||
|
origLen := dst.Len()
|
||||||
|
var scan scanner
|
||||||
|
scan.reset()
|
||||||
|
needIndent := false
|
||||||
|
depth := 0
|
||||||
|
for _, c := range src {
|
||||||
|
scan.bytes++
|
||||||
|
v := scan.step(&scan, c)
|
||||||
|
if v == scanSkipSpace {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v == scanError {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if needIndent && v != scanEndObject && v != scanEndArray {
|
||||||
|
needIndent = false
|
||||||
|
depth++
|
||||||
|
newline(dst, prefix, indent, depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit semantically uninteresting bytes
|
||||||
|
// (in particular, punctuation in strings) unmodified.
|
||||||
|
if v == scanContinue {
|
||||||
|
dst.WriteByte(c)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add spacing around real punctuation.
|
||||||
|
switch c {
|
||||||
|
case '{', '[':
|
||||||
|
// delay indent so that empty object and array are formatted as {} and [].
|
||||||
|
needIndent = true
|
||||||
|
dst.WriteByte(c)
|
||||||
|
|
||||||
|
case ',':
|
||||||
|
dst.WriteByte(c)
|
||||||
|
newline(dst, prefix, indent, depth)
|
||||||
|
|
||||||
|
case ':':
|
||||||
|
dst.WriteByte(c)
|
||||||
|
dst.WriteByte(' ')
|
||||||
|
|
||||||
|
case '}', ']':
|
||||||
|
if needIndent {
|
||||||
|
// suppress indent in empty object/array
|
||||||
|
needIndent = false
|
||||||
|
} else {
|
||||||
|
depth--
|
||||||
|
newline(dst, prefix, indent, depth)
|
||||||
|
}
|
||||||
|
dst.WriteByte(c)
|
||||||
|
|
||||||
|
default:
|
||||||
|
dst.WriteByte(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if scan.eof() == scanError {
|
||||||
|
dst.Truncate(origLen)
|
||||||
|
return scan.err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
// Copyright 2011 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.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNumberIsValid(t *testing.T) {
|
||||||
|
// From: http://stackoverflow.com/a/13340826
|
||||||
|
var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`)
|
||||||
|
|
||||||
|
validTests := []string{
|
||||||
|
"0",
|
||||||
|
"-0",
|
||||||
|
"1",
|
||||||
|
"-1",
|
||||||
|
"0.1",
|
||||||
|
"-0.1",
|
||||||
|
"1234",
|
||||||
|
"-1234",
|
||||||
|
"12.34",
|
||||||
|
"-12.34",
|
||||||
|
"12E0",
|
||||||
|
"12E1",
|
||||||
|
"12e34",
|
||||||
|
"12E-0",
|
||||||
|
"12e+1",
|
||||||
|
"12e-34",
|
||||||
|
"-12E0",
|
||||||
|
"-12E1",
|
||||||
|
"-12e34",
|
||||||
|
"-12E-0",
|
||||||
|
"-12e+1",
|
||||||
|
"-12e-34",
|
||||||
|
"1.2E0",
|
||||||
|
"1.2E1",
|
||||||
|
"1.2e34",
|
||||||
|
"1.2E-0",
|
||||||
|
"1.2e+1",
|
||||||
|
"1.2e-34",
|
||||||
|
"-1.2E0",
|
||||||
|
"-1.2E1",
|
||||||
|
"-1.2e34",
|
||||||
|
"-1.2E-0",
|
||||||
|
"-1.2e+1",
|
||||||
|
"-1.2e-34",
|
||||||
|
"0E0",
|
||||||
|
"0E1",
|
||||||
|
"0e34",
|
||||||
|
"0E-0",
|
||||||
|
"0e+1",
|
||||||
|
"0e-34",
|
||||||
|
"-0E0",
|
||||||
|
"-0E1",
|
||||||
|
"-0e34",
|
||||||
|
"-0E-0",
|
||||||
|
"-0e+1",
|
||||||
|
"-0e-34",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range validTests {
|
||||||
|
if !isValidNumber(test) {
|
||||||
|
t.Errorf("%s should be valid", test)
|
||||||
|
}
|
||||||
|
|
||||||
|
var f float64
|
||||||
|
if err := Unmarshal([]byte(test), &f); err != nil {
|
||||||
|
t.Errorf("%s should be valid but Unmarshal failed: %v", test, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !jsonNumberRegexp.MatchString(test) {
|
||||||
|
t.Errorf("%s should be valid but regexp does not match", test)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidTests := []string{
|
||||||
|
"",
|
||||||
|
"invalid",
|
||||||
|
"1.0.1",
|
||||||
|
"1..1",
|
||||||
|
"-1-2",
|
||||||
|
"012a42",
|
||||||
|
"01.2",
|
||||||
|
"012",
|
||||||
|
"12E12.12",
|
||||||
|
"1e2e3",
|
||||||
|
"1e+-2",
|
||||||
|
"1e--23",
|
||||||
|
"1e",
|
||||||
|
"e1",
|
||||||
|
"1e+",
|
||||||
|
"1ea",
|
||||||
|
"1a",
|
||||||
|
"1.a",
|
||||||
|
"1.",
|
||||||
|
"01",
|
||||||
|
"1.e1",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range invalidTests {
|
||||||
|
if isValidNumber(test) {
|
||||||
|
t.Errorf("%s should be invalid", test)
|
||||||
|
}
|
||||||
|
|
||||||
|
var f float64
|
||||||
|
if err := Unmarshal([]byte(test), &f); err == nil {
|
||||||
|
t.Errorf("%s should be invalid but unmarshal wrote %v", test, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
if jsonNumberRegexp.MatchString(test) {
|
||||||
|
t.Errorf("%s should be invalid but matches regexp", test)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkNumberIsValid(b *testing.B) {
|
||||||
|
s := "-61657.61667E+61673"
|
||||||
|
for b.Loop() {
|
||||||
|
isValidNumber(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkNumberIsValidRegexp(b *testing.B) {
|
||||||
|
var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`)
|
||||||
|
s := "-61657.61667E+61673"
|
||||||
|
for b.Loop() {
|
||||||
|
jsonNumberRegexp.MatchString(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,628 @@
|
||||||
|
// Copyright 2010 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.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
// JSON value parser state machine.
|
||||||
|
// Just about at the limit of what is reasonable to write by hand.
|
||||||
|
// Some parts are a bit tedious, but overall it nicely factors out the
|
||||||
|
// otherwise common code from the multiple scanning functions
|
||||||
|
// in this package (Compact, Indent, checkValid, nextValue, etc).
|
||||||
|
//
|
||||||
|
// This file starts with two simple examples using the scanner
|
||||||
|
// before diving into the scanner itself.
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
// Valid reports whether data is a valid JSON encoding.
|
||||||
|
func Valid(data []byte) bool {
|
||||||
|
return checkValid(data, &scanner{}) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkValid verifies that data is valid JSON-encoded data.
|
||||||
|
// scan is passed in for use by checkValid to avoid an allocation.
|
||||||
|
func checkValid(data []byte, scan *scanner) error {
|
||||||
|
scan.reset()
|
||||||
|
for _, c := range data {
|
||||||
|
scan.bytes++
|
||||||
|
if scan.step(scan, c) == scanError {
|
||||||
|
return scan.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if scan.eof() == scanError {
|
||||||
|
return scan.err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextValue splits data after the next whole JSON value,
|
||||||
|
// returning that value and the bytes that follow it as separate slices.
|
||||||
|
// scan is passed in for use by nextValue to avoid an allocation.
|
||||||
|
func nextValue(data []byte, scan *scanner) (value, rest []byte, err error) {
|
||||||
|
scan.reset()
|
||||||
|
for i, c := range data {
|
||||||
|
v := scan.step(scan, c)
|
||||||
|
if v >= scanEndObject {
|
||||||
|
switch v {
|
||||||
|
// probe the scanner with a space to determine whether we will
|
||||||
|
// get scanEnd on the next character. Otherwise, if the next character
|
||||||
|
// is not a space, scanEndTop allocates a needless error.
|
||||||
|
case scanEndObject, scanEndArray:
|
||||||
|
if scan.step(scan, ' ') == scanEnd {
|
||||||
|
return data[:i+1], data[i+1:], nil
|
||||||
|
}
|
||||||
|
case scanError:
|
||||||
|
return nil, nil, scan.err
|
||||||
|
case scanEnd:
|
||||||
|
return data[:i], data[i:], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if scan.eof() == scanError {
|
||||||
|
return nil, nil, scan.err
|
||||||
|
}
|
||||||
|
return data, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A SyntaxError is a description of a JSON syntax error.
|
||||||
|
type SyntaxError struct {
|
||||||
|
msg string // description of error
|
||||||
|
Offset int64 // error occurred after reading Offset bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *SyntaxError) Error() string { return e.msg }
|
||||||
|
|
||||||
|
// A scanner is a JSON scanning state machine.
|
||||||
|
// Callers call scan.reset() and then pass bytes in one at a time
|
||||||
|
// by calling scan.step(&scan, c) for each byte.
|
||||||
|
// The return value, referred to as an opcode, tells the
|
||||||
|
// caller about significant parsing events like beginning
|
||||||
|
// and ending literals, objects, and arrays, so that the
|
||||||
|
// caller can follow along if it wishes.
|
||||||
|
// The return value scanEnd indicates that a single top-level
|
||||||
|
// JSON value has been completed, *before* the byte that
|
||||||
|
// just got passed in. (The indication must be delayed in order
|
||||||
|
// to recognize the end of numbers: is 123 a whole value or
|
||||||
|
// the beginning of 12345e+6?).
|
||||||
|
type scanner struct {
|
||||||
|
// The step is a func to be called to execute the next transition.
|
||||||
|
// Also tried using an integer constant and a single func
|
||||||
|
// with a switch, but using the func directly was 10% faster
|
||||||
|
// on a 64-bit Mac Mini, and it's nicer to read.
|
||||||
|
step func(*scanner, byte) int
|
||||||
|
|
||||||
|
// Reached end of top-level value.
|
||||||
|
endTop bool
|
||||||
|
|
||||||
|
// Stack of what we're in the middle of - array values, object keys, object values.
|
||||||
|
parseState []int
|
||||||
|
|
||||||
|
// Error that happened, if any.
|
||||||
|
err error
|
||||||
|
|
||||||
|
// 1-byte redo (see undo method)
|
||||||
|
redo bool
|
||||||
|
redoCode int
|
||||||
|
redoState func(*scanner, byte) int
|
||||||
|
|
||||||
|
// total bytes consumed, updated by decoder.Decode
|
||||||
|
bytes int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// These values are returned by the state transition functions
|
||||||
|
// assigned to scanner.state and the method scanner.eof.
|
||||||
|
// They give details about the current state of the scan that
|
||||||
|
// callers might be interested to know about.
|
||||||
|
// It is okay to ignore the return value of any particular
|
||||||
|
// call to scanner.state: if one call returns scanError,
|
||||||
|
// every subsequent call will return scanError too.
|
||||||
|
const (
|
||||||
|
// Continue.
|
||||||
|
scanContinue = iota // uninteresting byte
|
||||||
|
scanBeginLiteral // end implied by next result != scanContinue
|
||||||
|
scanBeginObject // begin object
|
||||||
|
scanObjectKey // just finished object key (string)
|
||||||
|
scanObjectValue // just finished non-last object value
|
||||||
|
scanEndObject // end object (implies scanObjectValue if possible)
|
||||||
|
scanBeginArray // begin array
|
||||||
|
scanArrayValue // just finished array value
|
||||||
|
scanEndArray // end array (implies scanArrayValue if possible)
|
||||||
|
scanSkipSpace // space byte; can skip; known to be last "continue" result
|
||||||
|
|
||||||
|
// Stop.
|
||||||
|
scanEnd // top-level value ended *before* this byte; known to be first "stop" result
|
||||||
|
scanError // hit an error, scanner.err.
|
||||||
|
)
|
||||||
|
|
||||||
|
// These values are stored in the parseState stack.
|
||||||
|
// They give the current state of a composite value
|
||||||
|
// being scanned. If the parser is inside a nested value
|
||||||
|
// the parseState describes the nested state, outermost at entry 0.
|
||||||
|
const (
|
||||||
|
parseObjectKey = iota // parsing object key (before colon)
|
||||||
|
parseObjectValue // parsing object value (after colon)
|
||||||
|
parseArrayValue // parsing array value
|
||||||
|
)
|
||||||
|
|
||||||
|
// reset prepares the scanner for use.
|
||||||
|
// It must be called before calling s.step.
|
||||||
|
func (s *scanner) reset() {
|
||||||
|
s.step = stateBeginValue
|
||||||
|
s.parseState = s.parseState[0:0]
|
||||||
|
s.err = nil
|
||||||
|
s.redo = false
|
||||||
|
s.endTop = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// eof tells the scanner that the end of input has been reached.
|
||||||
|
// It returns a scan status just as s.step does.
|
||||||
|
func (s *scanner) eof() int {
|
||||||
|
if s.err != nil {
|
||||||
|
return scanError
|
||||||
|
}
|
||||||
|
if s.endTop {
|
||||||
|
return scanEnd
|
||||||
|
}
|
||||||
|
s.step(s, ' ')
|
||||||
|
if s.endTop {
|
||||||
|
return scanEnd
|
||||||
|
}
|
||||||
|
if s.err == nil {
|
||||||
|
s.err = &SyntaxError{"unexpected end of JSON input", s.bytes}
|
||||||
|
}
|
||||||
|
return scanError
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushParseState pushes a new parse state p onto the parse stack.
|
||||||
|
func (s *scanner) pushParseState(p int) {
|
||||||
|
s.parseState = append(s.parseState, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// popParseState pops a parse state (already obtained) off the stack
|
||||||
|
// and updates s.step accordingly.
|
||||||
|
func (s *scanner) popParseState() {
|
||||||
|
n := len(s.parseState) - 1
|
||||||
|
s.parseState = s.parseState[0:n]
|
||||||
|
s.redo = false
|
||||||
|
if n == 0 {
|
||||||
|
s.step = stateEndTop
|
||||||
|
s.endTop = true
|
||||||
|
} else {
|
||||||
|
s.step = stateEndValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSpace(c byte) bool {
|
||||||
|
return c == ' ' || c == '\t' || c == '\r' || c == '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateBeginValueOrEmpty is the state after reading `[`.
|
||||||
|
func stateBeginValueOrEmpty(s *scanner, c byte) int {
|
||||||
|
if c <= ' ' && isSpace(c) {
|
||||||
|
return scanSkipSpace
|
||||||
|
}
|
||||||
|
if c == ']' {
|
||||||
|
return stateEndValue(s, c)
|
||||||
|
}
|
||||||
|
return stateBeginValue(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateBeginValue is the state at the beginning of the input.
|
||||||
|
func stateBeginValue(s *scanner, c byte) int {
|
||||||
|
if c <= ' ' && isSpace(c) {
|
||||||
|
return scanSkipSpace
|
||||||
|
}
|
||||||
|
switch c {
|
||||||
|
case '{':
|
||||||
|
s.step = stateBeginStringOrEmpty
|
||||||
|
s.pushParseState(parseObjectKey)
|
||||||
|
return scanBeginObject
|
||||||
|
case '[':
|
||||||
|
s.step = stateBeginValueOrEmpty
|
||||||
|
s.pushParseState(parseArrayValue)
|
||||||
|
return scanBeginArray
|
||||||
|
case '"':
|
||||||
|
s.step = stateInString
|
||||||
|
return scanBeginLiteral
|
||||||
|
case '-':
|
||||||
|
s.step = stateNeg
|
||||||
|
return scanBeginLiteral
|
||||||
|
case '0': // beginning of 0.123
|
||||||
|
s.step = state0
|
||||||
|
return scanBeginLiteral
|
||||||
|
case 't': // beginning of true
|
||||||
|
s.step = stateT
|
||||||
|
return scanBeginLiteral
|
||||||
|
case 'f': // beginning of false
|
||||||
|
s.step = stateF
|
||||||
|
return scanBeginLiteral
|
||||||
|
case 'n': // beginning of null
|
||||||
|
s.step = stateN
|
||||||
|
return scanBeginLiteral
|
||||||
|
}
|
||||||
|
if '1' <= c && c <= '9' { // beginning of 1234.5
|
||||||
|
s.step = state1
|
||||||
|
return scanBeginLiteral
|
||||||
|
}
|
||||||
|
return s.error(c, "looking for beginning of value")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateBeginStringOrEmpty is the state after reading `{`.
|
||||||
|
func stateBeginStringOrEmpty(s *scanner, c byte) int {
|
||||||
|
if c <= ' ' && isSpace(c) {
|
||||||
|
return scanSkipSpace
|
||||||
|
}
|
||||||
|
if c == '}' {
|
||||||
|
n := len(s.parseState)
|
||||||
|
s.parseState[n-1] = parseObjectValue
|
||||||
|
return stateEndValue(s, c)
|
||||||
|
}
|
||||||
|
return stateBeginString(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateBeginString is the state after reading `{"key": value,`.
|
||||||
|
func stateBeginString(s *scanner, c byte) int {
|
||||||
|
if c <= ' ' && isSpace(c) {
|
||||||
|
return scanSkipSpace
|
||||||
|
}
|
||||||
|
if c == '"' {
|
||||||
|
s.step = stateInString
|
||||||
|
return scanBeginLiteral
|
||||||
|
}
|
||||||
|
return s.error(c, "looking for beginning of object key string")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateEndValue is the state after completing a value,
|
||||||
|
// such as after reading `{}` or `true` or `["x"`.
|
||||||
|
func stateEndValue(s *scanner, c byte) int {
|
||||||
|
n := len(s.parseState)
|
||||||
|
if n == 0 {
|
||||||
|
// Completed top-level before the current byte.
|
||||||
|
s.step = stateEndTop
|
||||||
|
s.endTop = true
|
||||||
|
return stateEndTop(s, c)
|
||||||
|
}
|
||||||
|
if c <= ' ' && isSpace(c) {
|
||||||
|
s.step = stateEndValue
|
||||||
|
return scanSkipSpace
|
||||||
|
}
|
||||||
|
ps := s.parseState[n-1]
|
||||||
|
switch ps {
|
||||||
|
case parseObjectKey:
|
||||||
|
if c == ':' {
|
||||||
|
s.parseState[n-1] = parseObjectValue
|
||||||
|
s.step = stateBeginValue
|
||||||
|
return scanObjectKey
|
||||||
|
}
|
||||||
|
return s.error(c, "after object key")
|
||||||
|
case parseObjectValue:
|
||||||
|
if c == ',' {
|
||||||
|
s.parseState[n-1] = parseObjectKey
|
||||||
|
s.step = stateBeginString
|
||||||
|
return scanObjectValue
|
||||||
|
}
|
||||||
|
if c == '}' {
|
||||||
|
s.popParseState()
|
||||||
|
return scanEndObject
|
||||||
|
}
|
||||||
|
return s.error(c, "after object key:value pair")
|
||||||
|
case parseArrayValue:
|
||||||
|
if c == ',' {
|
||||||
|
s.step = stateBeginValue
|
||||||
|
return scanArrayValue
|
||||||
|
}
|
||||||
|
if c == ']' {
|
||||||
|
s.popParseState()
|
||||||
|
return scanEndArray
|
||||||
|
}
|
||||||
|
return s.error(c, "after array element")
|
||||||
|
}
|
||||||
|
return s.error(c, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateEndTop is the state after finishing the top-level value,
|
||||||
|
// such as after reading `{}` or `[1,2,3]`.
|
||||||
|
// Only space characters should be seen now.
|
||||||
|
func stateEndTop(s *scanner, c byte) int {
|
||||||
|
if c != ' ' && c != '\t' && c != '\r' && c != '\n' {
|
||||||
|
// Complain about non-space byte on next call.
|
||||||
|
s.error(c, "after top-level value")
|
||||||
|
}
|
||||||
|
return scanEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateInString is the state after reading `"`.
|
||||||
|
func stateInString(s *scanner, c byte) int {
|
||||||
|
if c == '"' {
|
||||||
|
s.step = stateEndValue
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
if c == '\\' {
|
||||||
|
s.step = stateInStringEsc
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
if c < 0x20 {
|
||||||
|
return s.error(c, "in string literal")
|
||||||
|
}
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateInStringEsc is the state after reading `"\` during a quoted string.
|
||||||
|
func stateInStringEsc(s *scanner, c byte) int {
|
||||||
|
switch c {
|
||||||
|
case 'b', 'f', 'n', 'r', 't', '\\', '/', '"':
|
||||||
|
s.step = stateInString
|
||||||
|
return scanContinue
|
||||||
|
case 'u':
|
||||||
|
s.step = stateInStringEscU
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in string escape code")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateInStringEscU is the state after reading `"\u` during a quoted string.
|
||||||
|
func stateInStringEscU(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||||
|
s.step = stateInStringEscU1
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
// numbers
|
||||||
|
return s.error(c, "in \\u hexadecimal character escape")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateInStringEscU1 is the state after reading `"\u1` during a quoted string.
|
||||||
|
func stateInStringEscU1(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||||
|
s.step = stateInStringEscU12
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
// numbers
|
||||||
|
return s.error(c, "in \\u hexadecimal character escape")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateInStringEscU12 is the state after reading `"\u12` during a quoted string.
|
||||||
|
func stateInStringEscU12(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||||
|
s.step = stateInStringEscU123
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
// numbers
|
||||||
|
return s.error(c, "in \\u hexadecimal character escape")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateInStringEscU123 is the state after reading `"\u123` during a quoted string.
|
||||||
|
func stateInStringEscU123(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||||
|
s.step = stateInString
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
// numbers
|
||||||
|
return s.error(c, "in \\u hexadecimal character escape")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateNeg is the state after reading `-` during a number.
|
||||||
|
func stateNeg(s *scanner, c byte) int {
|
||||||
|
if c == '0' {
|
||||||
|
s.step = state0
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
if '1' <= c && c <= '9' {
|
||||||
|
s.step = state1
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in numeric literal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// state1 is the state after reading a non-zero integer during a number,
|
||||||
|
// such as after reading `1` or `100` but not `0`.
|
||||||
|
func state1(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' {
|
||||||
|
s.step = state1
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return state0(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// state0 is the state after reading `0` during a number.
|
||||||
|
func state0(s *scanner, c byte) int {
|
||||||
|
if c == '.' {
|
||||||
|
s.step = stateDot
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
if c == 'e' || c == 'E' {
|
||||||
|
s.step = stateE
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return stateEndValue(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateDot is the state after reading the integer and decimal point in a number,
|
||||||
|
// such as after reading `1.`.
|
||||||
|
func stateDot(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' {
|
||||||
|
s.step = stateDot0
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "after decimal point in numeric literal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateDot0 is the state after reading the integer, decimal point, and subsequent
|
||||||
|
// digits of a number, such as after reading `3.14`.
|
||||||
|
func stateDot0(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' {
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
if c == 'e' || c == 'E' {
|
||||||
|
s.step = stateE
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return stateEndValue(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateE is the state after reading the mantissa and e in a number,
|
||||||
|
// such as after reading `314e` or `0.314e`.
|
||||||
|
func stateE(s *scanner, c byte) int {
|
||||||
|
if c == '+' || c == '-' {
|
||||||
|
s.step = stateESign
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return stateESign(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateESign is the state after reading the mantissa, e, and sign in a number,
|
||||||
|
// such as after reading `314e-` or `0.314e+`.
|
||||||
|
func stateESign(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' {
|
||||||
|
s.step = stateE0
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in exponent of numeric literal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateE0 is the state after reading the mantissa, e, optional sign,
|
||||||
|
// and at least one digit of the exponent in a number,
|
||||||
|
// such as after reading `314e-2` or `0.314e+1` or `3.14e0`.
|
||||||
|
func stateE0(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' {
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return stateEndValue(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateT is the state after reading `t`.
|
||||||
|
func stateT(s *scanner, c byte) int {
|
||||||
|
if c == 'r' {
|
||||||
|
s.step = stateTr
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal true (expecting 'r')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateTr is the state after reading `tr`.
|
||||||
|
func stateTr(s *scanner, c byte) int {
|
||||||
|
if c == 'u' {
|
||||||
|
s.step = stateTru
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal true (expecting 'u')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateTru is the state after reading `tru`.
|
||||||
|
func stateTru(s *scanner, c byte) int {
|
||||||
|
if c == 'e' {
|
||||||
|
s.step = stateEndValue
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal true (expecting 'e')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateF is the state after reading `f`.
|
||||||
|
func stateF(s *scanner, c byte) int {
|
||||||
|
if c == 'a' {
|
||||||
|
s.step = stateFa
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal false (expecting 'a')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateFa is the state after reading `fa`.
|
||||||
|
func stateFa(s *scanner, c byte) int {
|
||||||
|
if c == 'l' {
|
||||||
|
s.step = stateFal
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal false (expecting 'l')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateFal is the state after reading `fal`.
|
||||||
|
func stateFal(s *scanner, c byte) int {
|
||||||
|
if c == 's' {
|
||||||
|
s.step = stateFals
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal false (expecting 's')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateFals is the state after reading `fals`.
|
||||||
|
func stateFals(s *scanner, c byte) int {
|
||||||
|
if c == 'e' {
|
||||||
|
s.step = stateEndValue
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal false (expecting 'e')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateN is the state after reading `n`.
|
||||||
|
func stateN(s *scanner, c byte) int {
|
||||||
|
if c == 'u' {
|
||||||
|
s.step = stateNu
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal null (expecting 'u')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateNu is the state after reading `nu`.
|
||||||
|
func stateNu(s *scanner, c byte) int {
|
||||||
|
if c == 'l' {
|
||||||
|
s.step = stateNul
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal null (expecting 'l')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateNul is the state after reading `nul`.
|
||||||
|
func stateNul(s *scanner, c byte) int {
|
||||||
|
if c == 'l' {
|
||||||
|
s.step = stateEndValue
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal null (expecting 'l')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateError is the state after reaching a syntax error,
|
||||||
|
// such as after reading `[1}` or `5.1.2`.
|
||||||
|
func stateError(s *scanner, c byte) int {
|
||||||
|
return scanError
|
||||||
|
}
|
||||||
|
|
||||||
|
// error records an error and switches to the error state.
|
||||||
|
func (s *scanner) error(c byte, context string) int {
|
||||||
|
s.step = stateError
|
||||||
|
s.err = &SyntaxError{"invalid character " + quoteChar(c) + " " + context, s.bytes}
|
||||||
|
return scanError
|
||||||
|
}
|
||||||
|
|
||||||
|
// quoteChar formats c as a quoted character literal.
|
||||||
|
func quoteChar(c byte) string {
|
||||||
|
// special cases - different from quoted strings
|
||||||
|
if c == '\'' {
|
||||||
|
return `'\''`
|
||||||
|
}
|
||||||
|
if c == '"' {
|
||||||
|
return `'"'`
|
||||||
|
}
|
||||||
|
|
||||||
|
// use quoted string with different quotation marks
|
||||||
|
s := strconv.Quote(string(c))
|
||||||
|
return "'" + s[1:len(s)-1] + "'"
|
||||||
|
}
|
||||||
|
|
||||||
|
// undo causes the scanner to return scanCode from the next state transition.
|
||||||
|
// This gives callers a simple 1-byte undo mechanism.
|
||||||
|
func (s *scanner) undo(scanCode int) {
|
||||||
|
if s.redo {
|
||||||
|
panic("json: invalid use of scanner")
|
||||||
|
}
|
||||||
|
s.redoCode = scanCode
|
||||||
|
s.redoState = s.step
|
||||||
|
s.step = stateRedo
|
||||||
|
s.redo = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateRedo helps implement the scanner's 1-byte undo.
|
||||||
|
func stateRedo(s *scanner, c byte) int {
|
||||||
|
s.redo = false
|
||||||
|
s.step = s.redoState
|
||||||
|
return s.redoCode
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,336 @@
|
||||||
|
// Copyright 2010 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.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math"
|
||||||
|
"math/rand/v2"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var validTests = []struct {
|
||||||
|
data string
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{`foo`, false},
|
||||||
|
{`}{`, false},
|
||||||
|
{`{]`, false},
|
||||||
|
{`{}`, true},
|
||||||
|
{`{"foo":"bar"}`, true},
|
||||||
|
{`{"foo":"bar","bar":{"baz":["qux"]}}`, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValid(t *testing.T) {
|
||||||
|
for _, tt := range validTests {
|
||||||
|
if ok := Valid([]byte(tt.data)); ok != tt.ok {
|
||||||
|
t.Errorf("Valid(%#q) = %v, want %v", tt.data, ok, tt.ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests of simple examples.
|
||||||
|
|
||||||
|
type example struct {
|
||||||
|
compact string
|
||||||
|
indent string
|
||||||
|
}
|
||||||
|
|
||||||
|
var examples = []example{
|
||||||
|
{`1`, `1`},
|
||||||
|
{`{}`, `{}`},
|
||||||
|
{`[]`, `[]`},
|
||||||
|
{`{"":2}`, "{\n\t\"\": 2\n}"},
|
||||||
|
{`[3]`, "[\n\t3\n]"},
|
||||||
|
{`[1,2,3]`, "[\n\t1,\n\t2,\n\t3\n]"},
|
||||||
|
{`{"x":1}`, "{\n\t\"x\": 1\n}"},
|
||||||
|
{ex1, ex1i},
|
||||||
|
}
|
||||||
|
|
||||||
|
var ex1 = `[true,false,null,"x",1,1.5,0,-5e+2]`
|
||||||
|
|
||||||
|
var ex1i = `[
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
"x",
|
||||||
|
1,
|
||||||
|
1.5,
|
||||||
|
0,
|
||||||
|
-5e+2
|
||||||
|
]`
|
||||||
|
|
||||||
|
func TestCompact(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for _, tt := range examples {
|
||||||
|
buf.Reset()
|
||||||
|
if err := Compact(&buf, []byte(tt.compact)); err != nil {
|
||||||
|
t.Errorf("Compact(%#q): %v", tt.compact, err)
|
||||||
|
} else if s := buf.String(); s != tt.compact {
|
||||||
|
t.Errorf("Compact(%#q) = %#q, want original", tt.compact, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
if err := Compact(&buf, []byte(tt.indent)); err != nil {
|
||||||
|
t.Errorf("Compact(%#q): %v", tt.indent, err)
|
||||||
|
continue
|
||||||
|
} else if s := buf.String(); s != tt.compact {
|
||||||
|
t.Errorf("Compact(%#q) = %#q, want %#q", tt.indent, s, tt.compact)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompactSeparators(t *testing.T) {
|
||||||
|
// U+2028 and U+2029 should be escaped inside strings.
|
||||||
|
// They should not appear outside strings.
|
||||||
|
tests := []struct {
|
||||||
|
in, compact string
|
||||||
|
}{
|
||||||
|
{"{\"\u2028\": 1}", `{"\u2028":1}`},
|
||||||
|
{"{\"\u2029\" :2}", `{"\u2029":2}`},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := Compact(&buf, []byte(tt.in)); err != nil {
|
||||||
|
t.Errorf("Compact(%q): %v", tt.in, err)
|
||||||
|
} else if s := buf.String(); s != tt.compact {
|
||||||
|
t.Errorf("Compact(%q) = %q, want %q", tt.in, s, tt.compact)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIndent(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for _, tt := range examples {
|
||||||
|
buf.Reset()
|
||||||
|
if err := Indent(&buf, []byte(tt.indent), "", "\t"); err != nil {
|
||||||
|
t.Errorf("Indent(%#q): %v", tt.indent, err)
|
||||||
|
} else if s := buf.String(); s != tt.indent {
|
||||||
|
t.Errorf("Indent(%#q) = %#q, want original", tt.indent, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
if err := Indent(&buf, []byte(tt.compact), "", "\t"); err != nil {
|
||||||
|
t.Errorf("Indent(%#q): %v", tt.compact, err)
|
||||||
|
continue
|
||||||
|
} else if s := buf.String(); s != tt.indent {
|
||||||
|
t.Errorf("Indent(%#q) = %#q, want %#q", tt.compact, s, tt.indent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests of a large random structure.
|
||||||
|
|
||||||
|
func TestCompactBig(t *testing.T) {
|
||||||
|
initBig()
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := Compact(&buf, jsonBig); err != nil {
|
||||||
|
t.Fatalf("Compact: %v", err)
|
||||||
|
}
|
||||||
|
b := buf.Bytes()
|
||||||
|
if !bytes.Equal(b, jsonBig) {
|
||||||
|
t.Error("Compact(jsonBig) != jsonBig")
|
||||||
|
diff(t, b, jsonBig)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIndentBig(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
initBig()
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := Indent(&buf, jsonBig, "", "\t"); err != nil {
|
||||||
|
t.Fatalf("Indent1: %v", err)
|
||||||
|
}
|
||||||
|
b := buf.Bytes()
|
||||||
|
if len(b) == len(jsonBig) {
|
||||||
|
// jsonBig is compact (no unnecessary spaces);
|
||||||
|
// indenting should make it bigger
|
||||||
|
t.Fatalf("Indent(jsonBig) did not get bigger")
|
||||||
|
}
|
||||||
|
|
||||||
|
// should be idempotent
|
||||||
|
var buf1 bytes.Buffer
|
||||||
|
if err := Indent(&buf1, b, "", "\t"); err != nil {
|
||||||
|
t.Fatalf("Indent2: %v", err)
|
||||||
|
}
|
||||||
|
b1 := buf1.Bytes()
|
||||||
|
if !bytes.Equal(b1, b) {
|
||||||
|
t.Error("Indent(Indent(jsonBig)) != Indent(jsonBig)")
|
||||||
|
diff(t, b1, b)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// should get back to original
|
||||||
|
buf1.Reset()
|
||||||
|
if err := Compact(&buf1, b); err != nil {
|
||||||
|
t.Fatalf("Compact: %v", err)
|
||||||
|
}
|
||||||
|
b1 = buf1.Bytes()
|
||||||
|
if !bytes.Equal(b1, jsonBig) {
|
||||||
|
t.Error("Compact(Indent(jsonBig)) != jsonBig")
|
||||||
|
diff(t, b1, jsonBig)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type indentErrorTest struct {
|
||||||
|
in string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
var indentErrorTests = []indentErrorTest{
|
||||||
|
{`{"X": "foo", "Y"}`, &SyntaxError{"invalid character '}' after object key", 17}},
|
||||||
|
{`{"X": "foo" "Y": "bar"}`, &SyntaxError{"invalid character '\"' after object key:value pair", 13}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIndentErrors(t *testing.T) {
|
||||||
|
for i, tt := range indentErrorTests {
|
||||||
|
slice := make([]uint8, 0)
|
||||||
|
buf := bytes.NewBuffer(slice)
|
||||||
|
if err := Indent(buf, []uint8(tt.in), "", ""); err != nil {
|
||||||
|
if !reflect.DeepEqual(err, tt.err) {
|
||||||
|
t.Errorf("#%d: Indent: %#v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextValueBig(t *testing.T) {
|
||||||
|
initBig()
|
||||||
|
var scan scanner
|
||||||
|
item, rest, err := nextValue(jsonBig, &scan)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("nextValue: %s", err)
|
||||||
|
}
|
||||||
|
if len(item) != len(jsonBig) || &item[0] != &jsonBig[0] {
|
||||||
|
t.Errorf("invalid item: %d %d", len(item), len(jsonBig))
|
||||||
|
}
|
||||||
|
if len(rest) != 0 {
|
||||||
|
t.Errorf("invalid rest: %d", len(rest))
|
||||||
|
}
|
||||||
|
|
||||||
|
item, rest, err = nextValue(append(jsonBig, "HELLO WORLD"...), &scan)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("nextValue extra: %s", err)
|
||||||
|
}
|
||||||
|
if len(item) != len(jsonBig) {
|
||||||
|
t.Errorf("invalid item: %d %d", len(item), len(jsonBig))
|
||||||
|
}
|
||||||
|
if string(rest) != "HELLO WORLD" {
|
||||||
|
t.Errorf("invalid rest: %d", len(rest))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var benchScan scanner
|
||||||
|
|
||||||
|
func BenchmarkSkipValue(b *testing.B) {
|
||||||
|
initBig()
|
||||||
|
for b.Loop() {
|
||||||
|
_, _, _ = nextValue(jsonBig, &benchScan)
|
||||||
|
}
|
||||||
|
b.SetBytes(int64(len(jsonBig)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func diff(t *testing.T, a, b []byte) {
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
if i >= len(a) || i >= len(b) || a[i] != b[i] {
|
||||||
|
j := i - 10
|
||||||
|
if j < 0 {
|
||||||
|
j = 0
|
||||||
|
}
|
||||||
|
t.Errorf("diverge at %d: «%s» vs «%s»", i, trim(a[j:]), trim(b[j:]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func trim(b []byte) []byte {
|
||||||
|
if len(b) > 20 {
|
||||||
|
return b[0:20]
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a random JSON object.
|
||||||
|
|
||||||
|
var jsonBig []byte
|
||||||
|
|
||||||
|
func initBig() {
|
||||||
|
n := 10000
|
||||||
|
if testing.Short() {
|
||||||
|
n = 100
|
||||||
|
}
|
||||||
|
b, err := Marshal(genValue(n))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
jsonBig = b
|
||||||
|
}
|
||||||
|
|
||||||
|
func genValue(n int) any {
|
||||||
|
if n > 1 {
|
||||||
|
switch rand.IntN(2) {
|
||||||
|
case 0:
|
||||||
|
return genArray(n)
|
||||||
|
case 1:
|
||||||
|
return genMap(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch rand.IntN(3) {
|
||||||
|
case 0:
|
||||||
|
return rand.IntN(2) == 0
|
||||||
|
case 1:
|
||||||
|
return rand.NormFloat64()
|
||||||
|
case 2:
|
||||||
|
return genString(30)
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func genString(stddev float64) string {
|
||||||
|
n := int(math.Abs(rand.NormFloat64()*stddev + stddev/2))
|
||||||
|
c := make([]rune, n)
|
||||||
|
for i := range c {
|
||||||
|
f := math.Abs(rand.NormFloat64()*64 + 32)
|
||||||
|
if f > 0x10ffff {
|
||||||
|
f = 0x10ffff
|
||||||
|
}
|
||||||
|
c[i] = rune(f)
|
||||||
|
}
|
||||||
|
return string(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func genArray(n int) []any {
|
||||||
|
f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2)))
|
||||||
|
if f > n {
|
||||||
|
f = n
|
||||||
|
}
|
||||||
|
if f < 1 {
|
||||||
|
f = 1
|
||||||
|
}
|
||||||
|
x := make([]any, f)
|
||||||
|
for i := range x {
|
||||||
|
x[i] = genValue(((i+1)*n)/f - (i*n)/f)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
func genMap(n int) map[string]any {
|
||||||
|
f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2)))
|
||||||
|
if f > n {
|
||||||
|
f = n
|
||||||
|
}
|
||||||
|
if n > 0 && f == 0 {
|
||||||
|
f = 1
|
||||||
|
}
|
||||||
|
x := make(map[string]any)
|
||||||
|
for i := range f {
|
||||||
|
x[genString(10)] = genValue(((i+1)*n)/f - (i*n)/f)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,572 @@
|
||||||
|
// Copyright 2010 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.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Decoder reads and decodes JSON values from an input stream.
|
||||||
|
type Decoder struct {
|
||||||
|
r io.Reader
|
||||||
|
buf []byte
|
||||||
|
d decodeState
|
||||||
|
scanp int // start of unread data in buf
|
||||||
|
scan scanner
|
||||||
|
err error
|
||||||
|
|
||||||
|
tokenState int
|
||||||
|
tokenStack []int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDecoder returns a new decoder that reads from r.
|
||||||
|
//
|
||||||
|
// The decoder introduces its own buffering and may
|
||||||
|
// read data from r beyond the JSON values requested.
|
||||||
|
func NewDecoder(r io.Reader) *Decoder {
|
||||||
|
return &Decoder{r: r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseNumber causes the Decoder to unmarshal a number into an any as a
|
||||||
|
// Number instead of as a float64.
|
||||||
|
func (dec *Decoder) UseNumber() { dec.d.useNumber = true }
|
||||||
|
|
||||||
|
// UseOrderedObject causes the Decoder to unmarshal an object into an any
|
||||||
|
// as a OrderedObject instead of as a map[string]any.
|
||||||
|
func (dec *Decoder) UseOrderedObject() { dec.d.useOrderedObject = true }
|
||||||
|
|
||||||
|
// Decode reads the next JSON-encoded value from its
|
||||||
|
// input and stores it in the value pointed to by v.
|
||||||
|
//
|
||||||
|
// See the documentation for Unmarshal for details about
|
||||||
|
// the conversion of JSON into a Go value.
|
||||||
|
func (dec *Decoder) Decode(v any) error {
|
||||||
|
if dec.err != nil {
|
||||||
|
return dec.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dec.tokenPrepareForDecode(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dec.tokenValueAllowed() {
|
||||||
|
return &SyntaxError{msg: "not at beginning of value"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read whole value into buffer.
|
||||||
|
n, err := dec.readValue()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dec.d.init(dec.buf[dec.scanp : dec.scanp+n])
|
||||||
|
dec.scanp += n
|
||||||
|
|
||||||
|
// Don't save err from unmarshal into dec.err:
|
||||||
|
// the connection is still usable since we read a complete JSON
|
||||||
|
// object from it before the error happened.
|
||||||
|
err = dec.d.unmarshal(v)
|
||||||
|
|
||||||
|
// fixup token streaming state
|
||||||
|
dec.tokenValueEnd()
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffered returns a reader of the data remaining in the Decoder's
|
||||||
|
// buffer. The reader is valid until the next call to Decode.
|
||||||
|
func (dec *Decoder) Buffered() io.Reader {
|
||||||
|
return bytes.NewReader(dec.buf[dec.scanp:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// readValue reads a JSON value into dec.buf.
|
||||||
|
// It returns the length of the encoding.
|
||||||
|
func (dec *Decoder) readValue() (int, error) {
|
||||||
|
dec.scan.reset()
|
||||||
|
|
||||||
|
scanp := dec.scanp
|
||||||
|
var err error
|
||||||
|
Input:
|
||||||
|
for {
|
||||||
|
// Look in the buffer for a new value.
|
||||||
|
for i, c := range dec.buf[scanp:] {
|
||||||
|
dec.scan.bytes++
|
||||||
|
v := dec.scan.step(&dec.scan, c)
|
||||||
|
if v == scanEnd {
|
||||||
|
scanp += i
|
||||||
|
break Input
|
||||||
|
}
|
||||||
|
// scanEnd is delayed one byte.
|
||||||
|
// We might block trying to get that byte from src,
|
||||||
|
// so instead invent a space byte.
|
||||||
|
if (v == scanEndObject || v == scanEndArray) && dec.scan.step(&dec.scan, ' ') == scanEnd {
|
||||||
|
scanp += i + 1
|
||||||
|
break Input
|
||||||
|
}
|
||||||
|
if v == scanError {
|
||||||
|
dec.err = dec.scan.err
|
||||||
|
return 0, dec.scan.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scanp = len(dec.buf)
|
||||||
|
|
||||||
|
// Did the last read have an error?
|
||||||
|
// Delayed until now to allow buffer scan.
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
if dec.scan.step(&dec.scan, ' ') == scanEnd {
|
||||||
|
break Input
|
||||||
|
}
|
||||||
|
if nonSpace(dec.buf) {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dec.err = err
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
n := scanp - dec.scanp
|
||||||
|
err = dec.refill()
|
||||||
|
scanp = dec.scanp + n
|
||||||
|
}
|
||||||
|
return scanp - dec.scanp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *Decoder) refill() error {
|
||||||
|
// Make room to read more into the buffer.
|
||||||
|
// First slide down data already consumed.
|
||||||
|
if dec.scanp > 0 {
|
||||||
|
n := copy(dec.buf, dec.buf[dec.scanp:])
|
||||||
|
dec.buf = dec.buf[:n]
|
||||||
|
dec.scanp = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grow buffer if not large enough.
|
||||||
|
const minRead = 512
|
||||||
|
if cap(dec.buf)-len(dec.buf) < minRead {
|
||||||
|
newBuf := make([]byte, len(dec.buf), 2*cap(dec.buf)+minRead)
|
||||||
|
copy(newBuf, dec.buf)
|
||||||
|
dec.buf = newBuf
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read. Delay error for next iteration (after scan).
|
||||||
|
n, err := dec.r.Read(dec.buf[len(dec.buf):cap(dec.buf)])
|
||||||
|
dec.buf = dec.buf[0 : len(dec.buf)+n]
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func nonSpace(b []byte) bool {
|
||||||
|
for _, c := range b {
|
||||||
|
if !isSpace(c) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// An Encoder writes JSON values to an output stream.
|
||||||
|
type Encoder struct {
|
||||||
|
w io.Writer
|
||||||
|
err error
|
||||||
|
escapeHTML bool
|
||||||
|
|
||||||
|
indentBuf *bytes.Buffer
|
||||||
|
indentPrefix string
|
||||||
|
indentValue string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEncoder returns a new encoder that writes to w.
|
||||||
|
func NewEncoder(w io.Writer) *Encoder {
|
||||||
|
return &Encoder{w: w, escapeHTML: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode writes the JSON encoding of v to the stream,
|
||||||
|
// followed by a newline character.
|
||||||
|
//
|
||||||
|
// See the documentation for Marshal for details about the
|
||||||
|
// conversion of Go values to JSON.
|
||||||
|
func (enc *Encoder) Encode(v any) error {
|
||||||
|
if enc.err != nil {
|
||||||
|
return enc.err
|
||||||
|
}
|
||||||
|
e := newEncodeState()
|
||||||
|
err := e.marshal(v, encOpts{escapeHTML: enc.escapeHTML})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminate each value with a newline.
|
||||||
|
// This makes the output look a little nicer
|
||||||
|
// when debugging, and some kind of space
|
||||||
|
// is required if the encoded value was a number,
|
||||||
|
// so that the reader knows there aren't more
|
||||||
|
// digits coming.
|
||||||
|
e.WriteByte('\n')
|
||||||
|
|
||||||
|
b := e.Bytes()
|
||||||
|
if enc.indentPrefix != "" || enc.indentValue != "" {
|
||||||
|
if enc.indentBuf == nil {
|
||||||
|
enc.indentBuf = new(bytes.Buffer)
|
||||||
|
}
|
||||||
|
enc.indentBuf.Reset()
|
||||||
|
err = Indent(enc.indentBuf, b, enc.indentPrefix, enc.indentValue)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b = enc.indentBuf.Bytes()
|
||||||
|
}
|
||||||
|
if _, err = enc.w.Write(b); err != nil {
|
||||||
|
enc.err = err
|
||||||
|
}
|
||||||
|
encodeStatePool.Put(e)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetIndent instructs the encoder to format each subsequent encoded
|
||||||
|
// value as if indented by the package-level function Indent(dst, src, prefix, indent).
|
||||||
|
// Calling SetIndent("", "") disables indentation.
|
||||||
|
func (enc *Encoder) SetIndent(prefix, indent string) {
|
||||||
|
enc.indentPrefix = prefix
|
||||||
|
enc.indentValue = indent
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEscapeHTML specifies whether problematic HTML characters
|
||||||
|
// should be escaped inside JSON quoted strings.
|
||||||
|
// The default behavior is to escape &, <, and > to \u0026, \u003c, and \u003e
|
||||||
|
// to avoid certain safety problems that can arise when embedding JSON in HTML.
|
||||||
|
//
|
||||||
|
// In non-HTML settings where the escaping interferes with the readability
|
||||||
|
// of the output, SetEscapeHTML(false) disables this behavior.
|
||||||
|
func (enc *Encoder) SetEscapeHTML(on bool) {
|
||||||
|
enc.escapeHTML = on
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawMessage is a raw encoded JSON value.
|
||||||
|
// It implements Marshaler and Unmarshaler and can
|
||||||
|
// be used to delay JSON decoding or precompute a JSON encoding.
|
||||||
|
type RawMessage []byte
|
||||||
|
|
||||||
|
// MarshalJSON returns m as the JSON encoding of m.
|
||||||
|
func (m RawMessage) MarshalJSON() ([]byte, error) {
|
||||||
|
if m == nil {
|
||||||
|
return []byte("null"), nil
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON sets *m to a copy of data.
|
||||||
|
func (m *RawMessage) UnmarshalJSON(data []byte) error {
|
||||||
|
if m == nil {
|
||||||
|
return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
|
||||||
|
}
|
||||||
|
*m = append((*m)[0:0], data...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Marshaler = (*RawMessage)(nil)
|
||||||
|
var _ Unmarshaler = (*RawMessage)(nil)
|
||||||
|
|
||||||
|
// Member is used to store key/value pairs in an OrderedObject.
|
||||||
|
type Member struct {
|
||||||
|
Key string
|
||||||
|
Value any
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrderedObject stores the key/value pairs of a JSON object in the order
|
||||||
|
// in which they appeared in the original document. See the documentation for UseOrderedObject on the Decoder.
|
||||||
|
//
|
||||||
|
// OrderedObject is used to enable decoding of arbitrary JSON objects while preserving
|
||||||
|
// the order of the keys. Unmarshal and Decoder.Decode are supported.
|
||||||
|
//
|
||||||
|
// var o OrderedObject
|
||||||
|
// Unmarshal(json, &o) // decode JSON object, while preserving key order
|
||||||
|
//
|
||||||
|
// var oa []OrderedObject
|
||||||
|
// Unmarshal(json, &oa) // decode an array of JSON objects, while preserving key order
|
||||||
|
//
|
||||||
|
// var v any
|
||||||
|
// d := new Decoder(json)
|
||||||
|
// d.UseOrderedObject() // decode all JSON objects as OrderedObject rather than map[string]any
|
||||||
|
// d.Decode(&v)
|
||||||
|
//
|
||||||
|
// type A struct {
|
||||||
|
// B bool
|
||||||
|
// Inner OrderedObject
|
||||||
|
// I int
|
||||||
|
// }
|
||||||
|
// var a A
|
||||||
|
// Unmarshal(&a) // decode A as a JSON object with Inner as a nested object, preserving key order
|
||||||
|
//
|
||||||
|
// OrderedObject can also be used to encode a JSON object in
|
||||||
|
// a specified order. Marshal and Encoder.Encode are supported.
|
||||||
|
//
|
||||||
|
// var o OrderedObject
|
||||||
|
// Marshal(o) // encode JSON object, each with keys in OrderedObject order
|
||||||
|
//
|
||||||
|
// var oa []OrderedObject
|
||||||
|
// Marshal(oa) // encode an array of JSON objects, with keys in OrderedObject order
|
||||||
|
//
|
||||||
|
// type A struct {
|
||||||
|
// B bool
|
||||||
|
// Inner OrderedObject
|
||||||
|
// I int
|
||||||
|
// }
|
||||||
|
// var a A = createA()
|
||||||
|
// Marshal(a) // encode A as a JSON object with Inner as a nested object
|
||||||
|
type OrderedObject []Member
|
||||||
|
|
||||||
|
// A Token holds a value of one of these types:
|
||||||
|
//
|
||||||
|
// Delim, for the four JSON delimiters [ ] { }
|
||||||
|
// bool, for JSON booleans
|
||||||
|
// float64, for JSON numbers
|
||||||
|
// Number, for JSON numbers
|
||||||
|
// string, for JSON string literals
|
||||||
|
// nil, for JSON null
|
||||||
|
type Token any
|
||||||
|
|
||||||
|
const (
|
||||||
|
tokenTopValue = iota
|
||||||
|
tokenArrayStart
|
||||||
|
tokenArrayValue
|
||||||
|
tokenArrayComma
|
||||||
|
tokenObjectStart
|
||||||
|
tokenObjectKey
|
||||||
|
tokenObjectColon
|
||||||
|
tokenObjectValue
|
||||||
|
tokenObjectComma
|
||||||
|
)
|
||||||
|
|
||||||
|
// advance tokenstate from a separator state to a value state.
|
||||||
|
func (dec *Decoder) tokenPrepareForDecode() error {
|
||||||
|
// Note: Not calling peek before switch, to avoid
|
||||||
|
// putting peek into the standard Decode path.
|
||||||
|
// peek is only called when using the Token API.
|
||||||
|
switch dec.tokenState {
|
||||||
|
case tokenArrayComma:
|
||||||
|
c, err := dec.peek()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c != ',' {
|
||||||
|
return &SyntaxError{"expected comma after array element", 0}
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = tokenArrayValue
|
||||||
|
case tokenObjectColon:
|
||||||
|
c, err := dec.peek()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c != ':' {
|
||||||
|
return &SyntaxError{"expected colon after object key", 0}
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = tokenObjectValue
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *Decoder) tokenValueAllowed() bool {
|
||||||
|
switch dec.tokenState {
|
||||||
|
case tokenTopValue, tokenArrayStart, tokenArrayValue, tokenObjectValue:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *Decoder) tokenValueEnd() {
|
||||||
|
switch dec.tokenState {
|
||||||
|
case tokenArrayStart, tokenArrayValue:
|
||||||
|
dec.tokenState = tokenArrayComma
|
||||||
|
case tokenObjectValue:
|
||||||
|
dec.tokenState = tokenObjectComma
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Delim is a JSON array or object delimiter, one of [ ] { or }.
|
||||||
|
type Delim rune
|
||||||
|
|
||||||
|
func (d Delim) String() string {
|
||||||
|
return string(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token returns the next JSON token in the input stream.
|
||||||
|
// At the end of the input stream, Token returns nil, io.EOF.
|
||||||
|
//
|
||||||
|
// Token guarantees that the delimiters [ ] { } it returns are
|
||||||
|
// properly nested and matched: if Token encounters an unexpected
|
||||||
|
// delimiter in the input, it will return an error.
|
||||||
|
//
|
||||||
|
// The input stream consists of basic JSON values—bool, string,
|
||||||
|
// number, and null—along with delimiters [ ] { } of type Delim
|
||||||
|
// to mark the start and end of arrays and objects.
|
||||||
|
// Commas and colons are elided.
|
||||||
|
func (dec *Decoder) Token() (Token, error) {
|
||||||
|
for {
|
||||||
|
c, err := dec.peek()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch c {
|
||||||
|
case '[':
|
||||||
|
if !dec.tokenValueAllowed() {
|
||||||
|
return dec.tokenError(c)
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenStack = append(dec.tokenStack, dec.tokenState)
|
||||||
|
dec.tokenState = tokenArrayStart
|
||||||
|
return Delim('['), nil
|
||||||
|
|
||||||
|
case ']':
|
||||||
|
if dec.tokenState != tokenArrayStart && dec.tokenState != tokenArrayComma {
|
||||||
|
return dec.tokenError(c)
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = dec.tokenStack[len(dec.tokenStack)-1]
|
||||||
|
dec.tokenStack = dec.tokenStack[:len(dec.tokenStack)-1]
|
||||||
|
dec.tokenValueEnd()
|
||||||
|
return Delim(']'), nil
|
||||||
|
|
||||||
|
case '{':
|
||||||
|
if !dec.tokenValueAllowed() {
|
||||||
|
return dec.tokenError(c)
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenStack = append(dec.tokenStack, dec.tokenState)
|
||||||
|
dec.tokenState = tokenObjectStart
|
||||||
|
return Delim('{'), nil
|
||||||
|
|
||||||
|
case '}':
|
||||||
|
if dec.tokenState != tokenObjectStart && dec.tokenState != tokenObjectComma {
|
||||||
|
return dec.tokenError(c)
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = dec.tokenStack[len(dec.tokenStack)-1]
|
||||||
|
dec.tokenStack = dec.tokenStack[:len(dec.tokenStack)-1]
|
||||||
|
dec.tokenValueEnd()
|
||||||
|
return Delim('}'), nil
|
||||||
|
|
||||||
|
case ':':
|
||||||
|
if dec.tokenState != tokenObjectColon {
|
||||||
|
return dec.tokenError(c)
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = tokenObjectValue
|
||||||
|
continue
|
||||||
|
|
||||||
|
case ',':
|
||||||
|
if dec.tokenState == tokenArrayComma {
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = tokenArrayValue
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if dec.tokenState == tokenObjectComma {
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = tokenObjectKey
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return dec.tokenError(c)
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
if dec.tokenState == tokenObjectStart || dec.tokenState == tokenObjectKey {
|
||||||
|
var x string
|
||||||
|
old := dec.tokenState
|
||||||
|
dec.tokenState = tokenTopValue
|
||||||
|
err := dec.Decode(&x)
|
||||||
|
dec.tokenState = old
|
||||||
|
if err != nil {
|
||||||
|
clearOffset(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dec.tokenState = tokenObjectColon
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
default:
|
||||||
|
if !dec.tokenValueAllowed() {
|
||||||
|
return dec.tokenError(c)
|
||||||
|
}
|
||||||
|
var x any
|
||||||
|
if err := dec.Decode(&x); err != nil {
|
||||||
|
clearOffset(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearOffset(err error) {
|
||||||
|
var s *SyntaxError
|
||||||
|
if errors.As(err, &s) {
|
||||||
|
s.Offset = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *Decoder) tokenError(c byte) (Token, error) {
|
||||||
|
var context string
|
||||||
|
switch dec.tokenState {
|
||||||
|
case tokenTopValue:
|
||||||
|
context = " looking for beginning of value"
|
||||||
|
case tokenArrayStart, tokenArrayValue, tokenObjectValue:
|
||||||
|
context = " looking for beginning of value"
|
||||||
|
case tokenArrayComma:
|
||||||
|
context = " after array element"
|
||||||
|
case tokenObjectKey:
|
||||||
|
context = " looking for beginning of object key string"
|
||||||
|
case tokenObjectColon:
|
||||||
|
context = " after object key"
|
||||||
|
case tokenObjectComma:
|
||||||
|
context = " after object key:value pair"
|
||||||
|
}
|
||||||
|
return nil, &SyntaxError{"invalid character " + quoteChar(c) + " " + context, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// More reports whether there is another element in the
|
||||||
|
// current array or object being parsed.
|
||||||
|
func (dec *Decoder) More() bool {
|
||||||
|
c, err := dec.peek()
|
||||||
|
return err == nil && c != ']' && c != '}'
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *Decoder) peek() (byte, error) {
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
for i := dec.scanp; i < len(dec.buf); i++ {
|
||||||
|
c := dec.buf[i]
|
||||||
|
if isSpace(c) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dec.scanp = i
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
// buffer has been scanned, now report any error
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
err = dec.refill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO
|
||||||
|
|
||||||
|
// EncodeToken writes the given JSON token to the stream.
|
||||||
|
// It returns an error if the delimiters [ ] { } are not properly used.
|
||||||
|
//
|
||||||
|
// EncodeToken does not call Flush, because usually it is part of
|
||||||
|
// a larger operation such as Encode, and those will call Flush when finished.
|
||||||
|
// Callers that create an Encoder and then invoke EncodeToken directly,
|
||||||
|
// without using Encode, need to call Flush when finished to ensure that
|
||||||
|
// the JSON is written to the underlying writer.
|
||||||
|
func (e *Encoder) EncodeToken(t Token) error {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
@ -0,0 +1,429 @@
|
||||||
|
// Copyright 2010 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.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test values for the stream test.
|
||||||
|
// One of each JSON kind.
|
||||||
|
var streamTest = []any{
|
||||||
|
0.1,
|
||||||
|
"hello",
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
[]any{"a", "b", "c"},
|
||||||
|
map[string]any{"K": "Kelvin", "ß": "long s"},
|
||||||
|
3.14, // another value to make sure something can follow map
|
||||||
|
}
|
||||||
|
|
||||||
|
var streamEncoded = `0.1
|
||||||
|
"hello"
|
||||||
|
null
|
||||||
|
true
|
||||||
|
false
|
||||||
|
["a","b","c"]
|
||||||
|
{"\u00DF":"long s","\u212A":"Kelvin"}
|
||||||
|
3.14
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestEncoder(t *testing.T) {
|
||||||
|
for i := 0; i <= len(streamTest); i++ {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
enc := NewEncoder(&buf)
|
||||||
|
// Check that enc.SetIndent("", "") turns off indentation.
|
||||||
|
enc.SetIndent(">", ".")
|
||||||
|
enc.SetIndent("", "")
|
||||||
|
for j, v := range streamTest[0:i] {
|
||||||
|
if err := enc.Encode(v); err != nil {
|
||||||
|
t.Fatalf("encode #%d: %v", j, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if have, want := buf.String(), nlines(streamEncoded, i); have != want {
|
||||||
|
t.Errorf("encoding %d items: mismatch", i)
|
||||||
|
diff(t, []byte(have), []byte(want))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var streamEncodedIndent = `0.1
|
||||||
|
"hello"
|
||||||
|
null
|
||||||
|
true
|
||||||
|
false
|
||||||
|
[
|
||||||
|
>."a",
|
||||||
|
>."b",
|
||||||
|
>."c"
|
||||||
|
>]
|
||||||
|
{
|
||||||
|
>."\u00DF": "long s",
|
||||||
|
>."\u212A": "Kelvin"
|
||||||
|
>}
|
||||||
|
3.14
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestEncoderIndent(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
enc := NewEncoder(&buf)
|
||||||
|
enc.SetIndent(">", ".")
|
||||||
|
for _, v := range streamTest {
|
||||||
|
err := enc.Encode(v)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if have, want := buf.String(), streamEncodedIndent; have != want {
|
||||||
|
t.Error("indented encoding mismatch")
|
||||||
|
diff(t, []byte(have), []byte(want))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncoderSetEscapeHTML(t *testing.T) {
|
||||||
|
var c C
|
||||||
|
var ct CText
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
v any
|
||||||
|
wantEscape string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"c", c, `"\u003C\u0026\u003E"`, `"<&>"`},
|
||||||
|
{"ct", ct, `"\u0022\u003C\u0026\u003E\u0022"`, `"\u0022<\u0026>\u0022"`},
|
||||||
|
{`"<&>"`, "<&>", `"\u003C\u0026\u003E"`, `"<\u0026>"`},
|
||||||
|
} {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
enc := NewEncoder(&buf)
|
||||||
|
if err := enc.Encode(tt.v); err != nil {
|
||||||
|
t.Fatalf("Encode(%s): %s", tt.name, err)
|
||||||
|
}
|
||||||
|
if got := strings.TrimSpace(buf.String()); got != tt.wantEscape {
|
||||||
|
t.Errorf("Encode(%s) = %#q, want %#q", tt.name, got, tt.wantEscape)
|
||||||
|
}
|
||||||
|
buf.Reset()
|
||||||
|
enc.SetEscapeHTML(false)
|
||||||
|
if err := enc.Encode(tt.v); err != nil {
|
||||||
|
t.Fatalf("SetEscapeHTML(false) Encode(%s): %s", tt.name, err)
|
||||||
|
}
|
||||||
|
if got := strings.TrimSpace(buf.String()); got != tt.want {
|
||||||
|
t.Errorf("SetEscapeHTML(false) Encode(%s) = %#q, want %#q",
|
||||||
|
tt.name, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecoder(t *testing.T) {
|
||||||
|
for i := 0; i <= len(streamTest); i++ {
|
||||||
|
// Use stream without newlines as input,
|
||||||
|
// just to stress the decoder even more.
|
||||||
|
// Our test input does not include back-to-back numbers.
|
||||||
|
// Otherwise stripping the newlines would
|
||||||
|
// merge two adjacent JSON values.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for _, c := range nlines(streamEncoded, i) {
|
||||||
|
if c != '\n' {
|
||||||
|
buf.WriteRune(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out := make([]any, i)
|
||||||
|
dec := NewDecoder(&buf)
|
||||||
|
for j := range out {
|
||||||
|
if err := dec.Decode(&out[j]); err != nil {
|
||||||
|
t.Fatalf("decode #%d/%d: %v", j, i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(out, streamTest[0:i]) {
|
||||||
|
t.Errorf("decoding %d items: mismatch", i)
|
||||||
|
for j := range out {
|
||||||
|
if !reflect.DeepEqual(out[j], streamTest[j]) {
|
||||||
|
t.Errorf("#%d: have %v want %v", j, out[j], streamTest[j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecoderBuffered(t *testing.T) {
|
||||||
|
r := strings.NewReader(`{"Name": "Gopher"} extra `)
|
||||||
|
var m struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
d := NewDecoder(r)
|
||||||
|
err := d.Decode(&m)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if m.Name != "Gopher" {
|
||||||
|
t.Errorf("Name = %q; want Gopher", m.Name)
|
||||||
|
}
|
||||||
|
rest, err := io.ReadAll(d.Buffered())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if g, w := string(rest), " extra "; g != w {
|
||||||
|
t.Errorf("Remaining = %q; want %q", g, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nlines(s string, n int) string {
|
||||||
|
if n <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
for i, c := range s {
|
||||||
|
if c == '\n' {
|
||||||
|
if n--; n == 0 {
|
||||||
|
return s[0 : i+1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRawMessage(t *testing.T) {
|
||||||
|
// TODO(rsc): Should not need the * in *RawMessage
|
||||||
|
var data struct {
|
||||||
|
X float64
|
||||||
|
Id *RawMessage //nolint:revive // It's "Id" in JSON and it's a test.
|
||||||
|
Y float32
|
||||||
|
}
|
||||||
|
const raw = `["\u0056",null]`
|
||||||
|
const msg = `{"X":0.1,"Id":["\u0056",null],"Y":0.2}`
|
||||||
|
err := Unmarshal([]byte(msg), &data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
if string([]byte(*data.Id)) != raw {
|
||||||
|
t.Fatalf("Raw mismatch: have %#q want %#q", []byte(*data.Id), raw)
|
||||||
|
}
|
||||||
|
b, err := Marshal(&data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Marshal: %v", err)
|
||||||
|
}
|
||||||
|
if string(b) != msg {
|
||||||
|
t.Fatalf("Marshal: have %#q want %#q", b, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNullRawMessage(t *testing.T) {
|
||||||
|
// TODO(rsc): Should not need the * in *RawMessage
|
||||||
|
var data struct {
|
||||||
|
X float64
|
||||||
|
Id *RawMessage //nolint:revive // It's "Id" in JSON and it's a test.
|
||||||
|
Y float32
|
||||||
|
}
|
||||||
|
data.Id = new(RawMessage)
|
||||||
|
const msg = `{"X":0.1,"Id":null,"Y":0.2}`
|
||||||
|
err := Unmarshal([]byte(msg), &data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
if data.Id != nil {
|
||||||
|
t.Fatalf("Raw mismatch: have non-nil, want nil")
|
||||||
|
}
|
||||||
|
b, err := Marshal(&data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Marshal: %v", err)
|
||||||
|
}
|
||||||
|
if string(b) != msg {
|
||||||
|
t.Fatalf("Marshal: have %#q want %#q", b, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var blockingTests = []string{
|
||||||
|
`{"x": 1}`,
|
||||||
|
`[1, 2, 3]`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBlocking(t *testing.T) {
|
||||||
|
for _, enc := range blockingTests {
|
||||||
|
r, w := net.Pipe()
|
||||||
|
go func() {
|
||||||
|
_, err := w.Write([]byte(enc))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var val any
|
||||||
|
|
||||||
|
// If Decode reads beyond what w.Write writes above,
|
||||||
|
// it will block, and the test will deadlock.
|
||||||
|
if err := NewDecoder(r).Decode(&val); err != nil {
|
||||||
|
t.Errorf("decoding %s: %v", enc, err)
|
||||||
|
}
|
||||||
|
r.Close()
|
||||||
|
w.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncoderEncode(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
type T struct {
|
||||||
|
X, Y string
|
||||||
|
}
|
||||||
|
v := &T{"foo", "bar"}
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
if err := NewEncoder(io.Discard).Encode(v); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type tokenStreamCase struct {
|
||||||
|
json string
|
||||||
|
expTokens []any
|
||||||
|
}
|
||||||
|
|
||||||
|
type decodeThis struct {
|
||||||
|
v any
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenStreamCases = []tokenStreamCase{
|
||||||
|
// streaming token cases
|
||||||
|
{json: `10`, expTokens: []any{float64(10)}},
|
||||||
|
{json: ` [10] `, expTokens: []any{
|
||||||
|
Delim('['), float64(10), Delim(']')}},
|
||||||
|
{json: ` [false,10,"b"] `, expTokens: []any{
|
||||||
|
Delim('['), false, float64(10), "b", Delim(']')}},
|
||||||
|
{json: `{ "a": 1 }`, expTokens: []any{
|
||||||
|
Delim('{'), "a", float64(1), Delim('}')}},
|
||||||
|
{json: `{"a": 1, "b":"3"}`, expTokens: []any{
|
||||||
|
Delim('{'), "a", float64(1), "b", "3", Delim('}')}},
|
||||||
|
{json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{
|
||||||
|
Delim('['),
|
||||||
|
Delim('{'), "a", float64(1), Delim('}'),
|
||||||
|
Delim('{'), "a", float64(2), Delim('}'),
|
||||||
|
Delim(']')}},
|
||||||
|
{json: `{"obj": {"a": 1}}`, expTokens: []any{
|
||||||
|
Delim('{'), "obj", Delim('{'), "a", float64(1), Delim('}'),
|
||||||
|
Delim('}')}},
|
||||||
|
{json: `{"obj": [{"a": 1}]}`, expTokens: []any{
|
||||||
|
Delim('{'), "obj", Delim('['),
|
||||||
|
Delim('{'), "a", float64(1), Delim('}'),
|
||||||
|
Delim(']'), Delim('}')}},
|
||||||
|
|
||||||
|
// streaming tokens with intermittent Decode()
|
||||||
|
{json: `{ "a": 1 }`, expTokens: []any{
|
||||||
|
Delim('{'), "a",
|
||||||
|
decodeThis{float64(1)},
|
||||||
|
Delim('}')}},
|
||||||
|
{json: ` [ { "a" : 1 } ] `, expTokens: []any{
|
||||||
|
Delim('['),
|
||||||
|
decodeThis{map[string]any{"a": float64(1)}},
|
||||||
|
Delim(']')}},
|
||||||
|
{json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{
|
||||||
|
Delim('['),
|
||||||
|
decodeThis{map[string]any{"a": float64(1)}},
|
||||||
|
decodeThis{map[string]any{"a": float64(2)}},
|
||||||
|
Delim(']')}},
|
||||||
|
{json: `{ "obj" : [ { "a" : 1 } ] }`, expTokens: []any{
|
||||||
|
Delim('{'), "obj", Delim('['),
|
||||||
|
decodeThis{map[string]any{"a": float64(1)}},
|
||||||
|
Delim(']'), Delim('}')}},
|
||||||
|
|
||||||
|
{json: `{"obj": {"a": 1}}`, expTokens: []any{
|
||||||
|
Delim('{'), "obj",
|
||||||
|
decodeThis{map[string]any{"a": float64(1)}},
|
||||||
|
Delim('}')}},
|
||||||
|
{json: `{"obj": [{"a": 1}]}`, expTokens: []any{
|
||||||
|
Delim('{'), "obj",
|
||||||
|
decodeThis{[]any{
|
||||||
|
map[string]any{"a": float64(1)},
|
||||||
|
}},
|
||||||
|
Delim('}')}},
|
||||||
|
{json: ` [{"a": 1} {"a": 2}] `, expTokens: []any{
|
||||||
|
Delim('['),
|
||||||
|
decodeThis{map[string]any{"a": float64(1)}},
|
||||||
|
decodeThis{&SyntaxError{"expected comma after array element", 0}},
|
||||||
|
}},
|
||||||
|
{json: `{ "a" 1 }`, expTokens: []any{
|
||||||
|
Delim('{'), "a",
|
||||||
|
decodeThis{&SyntaxError{"expected colon after object key", 0}},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeInStream(t *testing.T) {
|
||||||
|
for ci, tcase := range tokenStreamCases {
|
||||||
|
dec := NewDecoder(strings.NewReader(tcase.json))
|
||||||
|
for i, etk := range tcase.expTokens {
|
||||||
|
var tk any
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if dt, ok := etk.(decodeThis); ok {
|
||||||
|
etk = dt.v
|
||||||
|
err = dec.Decode(&tk)
|
||||||
|
} else {
|
||||||
|
tk, err = dec.Token()
|
||||||
|
}
|
||||||
|
if experr, ok := etk.(error); ok {
|
||||||
|
if err == nil || err.Error() != experr.Error() {
|
||||||
|
t.Errorf("case %v: Expected error %v in %q, but was %v", ci, experr, tcase.json, err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
} else if errors.Is(err, io.EOF) {
|
||||||
|
t.Errorf("case %v: Unexpected EOF in %q", ci, tcase.json)
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
t.Errorf("case %v: Unexpected error '%v' in %q", ci, err, tcase.json)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tk, etk) {
|
||||||
|
t.Errorf(`case %v: %q @ %v expected %T(%v) was %T(%v)`, ci, tcase.json, i, etk, etk, tk, tk)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test from golang.org/issue/11893.
|
||||||
|
func TestHTTPDecoding(t *testing.T) {
|
||||||
|
const raw = `{ "foo": "bar" }`
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, err := w.Write([]byte(raw))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
res, err := http.Get(ts.URL)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("GET failed: %v", err)
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
foo := struct {
|
||||||
|
Foo string
|
||||||
|
}{}
|
||||||
|
|
||||||
|
d := NewDecoder(res.Body)
|
||||||
|
err = d.Decode(&foo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Decode: %v", err)
|
||||||
|
}
|
||||||
|
if foo.Foo != "bar" {
|
||||||
|
t.Errorf("decoded %q; want \"bar\"", foo.Foo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we get the EOF the second time
|
||||||
|
err = d.Decode(&foo)
|
||||||
|
if !errors.Is(err, io.EOF) {
|
||||||
|
t.Errorf("err = %v; want io.EOF", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,218 @@
|
||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import "unicode/utf8"
|
||||||
|
|
||||||
|
// safeSet holds the value true if the ASCII character with the given array
|
||||||
|
// position can be represented inside a JSON string without any further
|
||||||
|
// escaping.
|
||||||
|
//
|
||||||
|
// All values are true except for the ASCII control characters (0-31), the
|
||||||
|
// double quote ("), and the backslash character ("\").
|
||||||
|
var safeSet = [utf8.RuneSelf]bool{
|
||||||
|
' ': true,
|
||||||
|
'!': true,
|
||||||
|
'"': false,
|
||||||
|
'#': true,
|
||||||
|
'$': true,
|
||||||
|
'%': true,
|
||||||
|
'&': false,
|
||||||
|
'\'': false,
|
||||||
|
'(': true,
|
||||||
|
')': true,
|
||||||
|
'*': true,
|
||||||
|
'+': false,
|
||||||
|
',': true,
|
||||||
|
'-': true,
|
||||||
|
'.': true,
|
||||||
|
'/': true,
|
||||||
|
'0': true,
|
||||||
|
'1': true,
|
||||||
|
'2': true,
|
||||||
|
'3': true,
|
||||||
|
'4': true,
|
||||||
|
'5': true,
|
||||||
|
'6': true,
|
||||||
|
'7': true,
|
||||||
|
'8': true,
|
||||||
|
'9': true,
|
||||||
|
':': true,
|
||||||
|
';': true,
|
||||||
|
'<': true,
|
||||||
|
'=': true,
|
||||||
|
'>': true,
|
||||||
|
'?': true,
|
||||||
|
'@': true,
|
||||||
|
'A': true,
|
||||||
|
'B': true,
|
||||||
|
'C': true,
|
||||||
|
'D': true,
|
||||||
|
'E': true,
|
||||||
|
'F': true,
|
||||||
|
'G': true,
|
||||||
|
'H': true,
|
||||||
|
'I': true,
|
||||||
|
'J': true,
|
||||||
|
'K': true,
|
||||||
|
'L': true,
|
||||||
|
'M': true,
|
||||||
|
'N': true,
|
||||||
|
'O': true,
|
||||||
|
'P': true,
|
||||||
|
'Q': true,
|
||||||
|
'R': true,
|
||||||
|
'S': true,
|
||||||
|
'T': true,
|
||||||
|
'U': true,
|
||||||
|
'V': true,
|
||||||
|
'W': true,
|
||||||
|
'X': true,
|
||||||
|
'Y': true,
|
||||||
|
'Z': true,
|
||||||
|
'[': true,
|
||||||
|
'\\': false,
|
||||||
|
']': true,
|
||||||
|
'^': true,
|
||||||
|
'_': true,
|
||||||
|
'`': false,
|
||||||
|
'a': true,
|
||||||
|
'b': true,
|
||||||
|
'c': true,
|
||||||
|
'd': true,
|
||||||
|
'e': true,
|
||||||
|
'f': true,
|
||||||
|
'g': true,
|
||||||
|
'h': true,
|
||||||
|
'i': true,
|
||||||
|
'j': true,
|
||||||
|
'k': true,
|
||||||
|
'l': true,
|
||||||
|
'm': true,
|
||||||
|
'n': true,
|
||||||
|
'o': true,
|
||||||
|
'p': true,
|
||||||
|
'q': true,
|
||||||
|
'r': true,
|
||||||
|
's': true,
|
||||||
|
't': true,
|
||||||
|
'u': true,
|
||||||
|
'v': true,
|
||||||
|
'w': true,
|
||||||
|
'x': true,
|
||||||
|
'y': true,
|
||||||
|
'z': true,
|
||||||
|
'{': true,
|
||||||
|
'|': true,
|
||||||
|
'}': true,
|
||||||
|
'~': true,
|
||||||
|
'\u007f': false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// htmlSafeSet holds the value true if the ASCII character with the given
|
||||||
|
// array position can be safely represented inside a JSON string, embedded
|
||||||
|
// inside of HTML <script> tags, without any additional escaping.
|
||||||
|
//
|
||||||
|
// All values are true except for the ASCII control characters (0-31), the
|
||||||
|
// double quote ("), the backslash character ("\"), HTML opening and closing
|
||||||
|
// tags ("<" and ">"), and the ampersand ("&").
|
||||||
|
var htmlSafeSet = [utf8.RuneSelf]bool{
|
||||||
|
' ': true,
|
||||||
|
'!': true,
|
||||||
|
'"': false,
|
||||||
|
'#': true,
|
||||||
|
'$': true,
|
||||||
|
'%': true,
|
||||||
|
'&': false,
|
||||||
|
'\'': false,
|
||||||
|
'(': true,
|
||||||
|
')': true,
|
||||||
|
'*': true,
|
||||||
|
'+': false,
|
||||||
|
',': true,
|
||||||
|
'-': true,
|
||||||
|
'.': true,
|
||||||
|
'/': true,
|
||||||
|
'0': true,
|
||||||
|
'1': true,
|
||||||
|
'2': true,
|
||||||
|
'3': true,
|
||||||
|
'4': true,
|
||||||
|
'5': true,
|
||||||
|
'6': true,
|
||||||
|
'7': true,
|
||||||
|
'8': true,
|
||||||
|
'9': true,
|
||||||
|
':': true,
|
||||||
|
';': true,
|
||||||
|
'<': false,
|
||||||
|
'=': true,
|
||||||
|
'>': false,
|
||||||
|
'?': true,
|
||||||
|
'@': true,
|
||||||
|
'A': true,
|
||||||
|
'B': true,
|
||||||
|
'C': true,
|
||||||
|
'D': true,
|
||||||
|
'E': true,
|
||||||
|
'F': true,
|
||||||
|
'G': true,
|
||||||
|
'H': true,
|
||||||
|
'I': true,
|
||||||
|
'J': true,
|
||||||
|
'K': true,
|
||||||
|
'L': true,
|
||||||
|
'M': true,
|
||||||
|
'N': true,
|
||||||
|
'O': true,
|
||||||
|
'P': true,
|
||||||
|
'Q': true,
|
||||||
|
'R': true,
|
||||||
|
'S': true,
|
||||||
|
'T': true,
|
||||||
|
'U': true,
|
||||||
|
'V': true,
|
||||||
|
'W': true,
|
||||||
|
'X': true,
|
||||||
|
'Y': true,
|
||||||
|
'Z': true,
|
||||||
|
'[': true,
|
||||||
|
'\\': false,
|
||||||
|
']': true,
|
||||||
|
'^': true,
|
||||||
|
'_': true,
|
||||||
|
'`': false,
|
||||||
|
'a': true,
|
||||||
|
'b': true,
|
||||||
|
'c': true,
|
||||||
|
'd': true,
|
||||||
|
'e': true,
|
||||||
|
'f': true,
|
||||||
|
'g': true,
|
||||||
|
'h': true,
|
||||||
|
'i': true,
|
||||||
|
'j': true,
|
||||||
|
'k': true,
|
||||||
|
'l': true,
|
||||||
|
'm': true,
|
||||||
|
'n': true,
|
||||||
|
'o': true,
|
||||||
|
'p': true,
|
||||||
|
'q': true,
|
||||||
|
'r': true,
|
||||||
|
's': true,
|
||||||
|
't': true,
|
||||||
|
'u': true,
|
||||||
|
'v': true,
|
||||||
|
'w': true,
|
||||||
|
'x': true,
|
||||||
|
'y': true,
|
||||||
|
'z': true,
|
||||||
|
'{': true,
|
||||||
|
'|': true,
|
||||||
|
'}': true,
|
||||||
|
'~': true,
|
||||||
|
'\u007f': false,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
// Copyright 2011 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.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type basicLatin2xTag struct {
|
||||||
|
V string `json:"$%-/"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type basicLatin3xTag struct {
|
||||||
|
V string `json:"0123456789"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type basicLatin4xTag struct {
|
||||||
|
V string `json:"ABCDEFGHIJKLMO"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type basicLatin5xTag struct {
|
||||||
|
V string `json:"PQRSTUVWXYZ_"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type basicLatin6xTag struct {
|
||||||
|
V string `json:"abcdefghijklmno"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type basicLatin7xTag struct {
|
||||||
|
V string `json:"pqrstuvwxyz"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type miscPlaneTag struct {
|
||||||
|
V string `json:"色は匂へど"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type percentSlashTag struct {
|
||||||
|
V string `json:"text/html%"` // https://golang.org/issue/2718
|
||||||
|
}
|
||||||
|
|
||||||
|
type punctuationTag struct {
|
||||||
|
V string `json:"!#$%&()*+-./:<=>?@[]^_{|}~"` // https://golang.org/issue/3546
|
||||||
|
}
|
||||||
|
|
||||||
|
type dashTag struct {
|
||||||
|
V string `json:"-,"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type emptyTag struct {
|
||||||
|
W string
|
||||||
|
}
|
||||||
|
|
||||||
|
type misnamedTag struct {
|
||||||
|
X string `jsom:"Misnamed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type badFormatTag struct {
|
||||||
|
Y string `:"BadFormat"` //nolint:govet // It's intentionally wrong.
|
||||||
|
}
|
||||||
|
|
||||||
|
type badCodeTag struct {
|
||||||
|
Z string `json:" !\"#&'()*+,."` //nolint:staticcheck // It's intentionally wrong.
|
||||||
|
}
|
||||||
|
|
||||||
|
type spaceTag struct {
|
||||||
|
Q string `json:"With space"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type unicodeTag struct {
|
||||||
|
W string `json:"Ελλάδα"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var structTagObjectKeyTests = []struct {
|
||||||
|
raw any
|
||||||
|
value string
|
||||||
|
key string
|
||||||
|
}{
|
||||||
|
{basicLatin2xTag{"2x"}, "2x", "$%-/"},
|
||||||
|
{basicLatin3xTag{"3x"}, "3x", "0123456789"},
|
||||||
|
{basicLatin4xTag{"4x"}, "4x", "ABCDEFGHIJKLMO"},
|
||||||
|
{basicLatin5xTag{"5x"}, "5x", "PQRSTUVWXYZ_"},
|
||||||
|
{basicLatin6xTag{"6x"}, "6x", "abcdefghijklmno"},
|
||||||
|
{basicLatin7xTag{"7x"}, "7x", "pqrstuvwxyz"},
|
||||||
|
{miscPlaneTag{"いろはにほへと"}, "いろはにほへと", "色は匂へど"},
|
||||||
|
{dashTag{"foo"}, "foo", "-"},
|
||||||
|
{emptyTag{"Pour Moi"}, "Pour Moi", "W"},
|
||||||
|
{misnamedTag{"Animal Kingdom"}, "Animal Kingdom", "X"},
|
||||||
|
{badFormatTag{"Orfevre"}, "Orfevre", "Y"},
|
||||||
|
{badCodeTag{"Reliable Man"}, "Reliable Man", "Z"},
|
||||||
|
{percentSlashTag{"brut"}, "brut", "text/html%"},
|
||||||
|
{punctuationTag{"Union Rags"}, "Union Rags", "!#$%&()*+-./:<=>?@[]^_{|}~"},
|
||||||
|
{spaceTag{"Perreddu"}, "Perreddu", "With space"},
|
||||||
|
{unicodeTag{"Loukanikos"}, "Loukanikos", "Ελλάδα"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructTagObjectKey(t *testing.T) {
|
||||||
|
for _, tt := range structTagObjectKeyTests {
|
||||||
|
b, err := Marshal(tt.raw)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Marshal(%#q) failed: %v", tt.raw, err)
|
||||||
|
}
|
||||||
|
var f any
|
||||||
|
err = Unmarshal(b, &f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unmarshal(%#q) failed: %v", b, err)
|
||||||
|
}
|
||||||
|
for i, v := range f.(map[string]any) {
|
||||||
|
switch i {
|
||||||
|
case tt.key:
|
||||||
|
if s, ok := v.(string); !ok || s != tt.value {
|
||||||
|
t.Fatalf("Unexpected value: %#q, want %v", s, tt.value)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Fatalf("Unexpected key: %#q, from %#q", i, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2011 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.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tagOptions is the string following a comma in a struct field's "json"
|
||||||
|
// tag, or the empty string. It does not include the leading comma.
|
||||||
|
type tagOptions string
|
||||||
|
|
||||||
|
// parseTag splits a struct field's json tag into its name and
|
||||||
|
// comma-separated options.
|
||||||
|
func parseTag(tag string) (string, tagOptions) {
|
||||||
|
if idx := strings.Index(tag, ","); idx != -1 {
|
||||||
|
return tag[:idx], tagOptions(tag[idx+1:])
|
||||||
|
}
|
||||||
|
return tag, tagOptions("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains reports whether a comma-separated list of options
|
||||||
|
// contains a particular substr flag. substr must be surrounded by a
|
||||||
|
// string boundary or commas.
|
||||||
|
func (o tagOptions) Contains(optionName string) bool {
|
||||||
|
if len(o) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for s := range strings.FieldsFuncSeq(string(o), func(c rune) bool { return c == ',' }) {
|
||||||
|
if s == optionName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright 2011 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.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTagParsing(t *testing.T) {
|
||||||
|
name, opts := parseTag("field,foobar,foo")
|
||||||
|
if name != "field" {
|
||||||
|
t.Fatalf("name = %q, want field", name)
|
||||||
|
}
|
||||||
|
for _, tt := range []struct {
|
||||||
|
opt string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"foobar", true},
|
||||||
|
{"foo", true},
|
||||||
|
{"bar", false},
|
||||||
|
} {
|
||||||
|
if opts.Contains(tt.opt) != tt.want {
|
||||||
|
t.Errorf("Contains(%q) = %v", tt.opt, !tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Loading…
Reference in New Issue