Rebrand to Tutus - update license, workflows, and dependencies

This commit is contained in:
Tutus Development 2025-12-27 15:45:24 +00:00
parent aaa5646a4d
commit 210cc2b24d
20 changed files with 8979 additions and 8979 deletions

View File

@ -1,119 +1,119 @@
name: Tests name: Tests
on: on:
pull_request: pull_request:
branches: branches:
- master - master
types: [opened, synchronize] types: [opened, synchronize]
paths-ignore: paths-ignore:
- '*.md' - '*.md'
workflow_dispatch: workflow_dispatch:
env: env:
GO111MODULE: "on" GO111MODULE: "on"
jobs: jobs:
test_cover: test_cover:
name: Coverage name: Coverage
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
CGO_ENABLED: 0 CGO_ENABLED: 0
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: 1.25 go-version: 1.25
- name: Write coverage profile - name: Write coverage profile
run: go test -v ./... -coverprofile=./coverage.txt -covermode=atomic run: go test -v ./... -coverprofile=./coverage.txt -covermode=atomic
- name: Upload coverage results to Codecov - name: Upload coverage results to Codecov
uses: codecov/codecov-action@v2 uses: codecov/codecov-action@v2
with: with:
fail_ci_if_error: false fail_ci_if_error: false
path_to_write_report: ./coverage.txt path_to_write_report: ./coverage.txt
verbose: true verbose: true
tests: tests:
name: Go name: Go
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
go_versions: [ '1.24', '1.25' ] go_versions: [ '1.24', '1.25' ]
os: [ubuntu-latest, windows-latest, macos-latest] os: [ubuntu-latest, windows-latest, macos-latest]
exclude: exclude:
# Only latest Go version for Windows and MacOS. # Only latest Go version for Windows and MacOS.
- os: windows-latest - os: windows-latest
go_versions: '1.24' go_versions: '1.24'
- os: macos-latest - os: macos-latest
go_versions: '1.24' go_versions: '1.24'
# Exclude latest Go version for Ubuntu as Coverage uses it. # Exclude latest Go version for Ubuntu as Coverage uses it.
- os: ubuntu-latest - os: ubuntu-latest
go_versions: '1.25' go_versions: '1.25'
fail-fast: false fail-fast: false
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: '${{ matrix.go_versions }}' go-version: '${{ matrix.go_versions }}'
- name: Run tests - name: Run tests
run: go test -v -race ./... run: go test -v -race ./...
lint: lint:
name: Linter name: Linter
uses: nspcc-dev/.github/.github/workflows/go-linter.yml@master uses: nspcc-dev/.github/.github/workflows/go-linter.yml@master
codeql: codeql:
name: CodeQL name: CodeQL
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
language: [ 'go' ] language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more: # 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 # 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: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v3 uses: github/codeql-action/init@v3
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # 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. # 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. # 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 # queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # 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) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v3 uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # ✏️ 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 # and modify them (or add more) to build your code if your project
# uses a compiled language # uses a compiled language
#- run: | #- run: |
# make bootstrap # make bootstrap
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3 uses: github/codeql-action/analyze@v3

54
LICENSE
View File

@ -1,27 +1,27 @@
Copyright (c) 2009 The Go Authors. All rights reserved. Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
met: met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above * Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the in the documentation and/or other materials provided with the
distribution. distribution.
* Neither the name of Google Inc. nor the names of its * Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from contributors may be used to endorse or promote products derived from
this software without specific prior written permission. this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,252 +1,252 @@
// Copyright 2011 The Go Authors. All rights reserved. // Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Large data benchmark. // Large data benchmark.
// The JSON data is a summary of agl's changes in the // The JSON data is a summary of agl's changes in the
// go, webkit, and chromium open source projects. // go, webkit, and chromium open source projects.
// We benchmark converting between the JSON form // We benchmark converting between the JSON form
// and in-memory data structures. // and in-memory data structures.
package json package json
import ( import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"io" "io"
"os" "os"
"strings" "strings"
"testing" "testing"
) )
type codeResponse struct { type codeResponse struct {
Tree *codeNode `json:"tree"` Tree *codeNode `json:"tree"`
Username string `json:"username"` Username string `json:"username"`
} }
type codeNode struct { type codeNode struct {
Name string `json:"name"` Name string `json:"name"`
Kids []*codeNode `json:"kids"` Kids []*codeNode `json:"kids"`
CLWeight float64 `json:"cl_weight"` CLWeight float64 `json:"cl_weight"`
Touches int `json:"touches"` Touches int `json:"touches"`
MinT int64 `json:"min_t"` MinT int64 `json:"min_t"`
MaxT int64 `json:"max_t"` MaxT int64 `json:"max_t"`
MeanT int64 `json:"mean_t"` MeanT int64 `json:"mean_t"`
} }
var codeJSON []byte var codeJSON []byte
var codeStruct codeResponse var codeStruct codeResponse
func codeInit() { func codeInit() {
f, err := os.Open("testdata/code.json.gz") f, err := os.Open("testdata/code.json.gz")
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer f.Close() defer f.Close()
gz, err := gzip.NewReader(f) gz, err := gzip.NewReader(f)
if err != nil { if err != nil {
panic(err) panic(err)
} }
data, err := io.ReadAll(gz) data, err := io.ReadAll(gz)
if err != nil { if err != nil {
panic(err) panic(err)
} }
codeJSON = data codeJSON = data
if err := Unmarshal(codeJSON, &codeStruct); err != nil { if err := Unmarshal(codeJSON, &codeStruct); err != nil {
panic("unmarshal code.json: " + err.Error()) panic("unmarshal code.json: " + err.Error())
} }
if data, err = Marshal(&codeStruct); err != nil { if data, err = Marshal(&codeStruct); err != nil {
panic("marshal code.json: " + err.Error()) panic("marshal code.json: " + err.Error())
} }
if !bytes.Equal(data, codeJSON) { if !bytes.Equal(data, codeJSON) {
println("different lengths", len(data), len(codeJSON)) println("different lengths", len(data), len(codeJSON))
for i := 0; i < len(data) && i < len(codeJSON); i++ { for i := 0; i < len(data) && i < len(codeJSON); i++ {
if data[i] != codeJSON[i] { if data[i] != codeJSON[i] {
println("re-marshal: changed at byte", i) println("re-marshal: changed at byte", i)
println("orig: ", string(codeJSON[i-10:i+10])) println("orig: ", string(codeJSON[i-10:i+10]))
println("new: ", string(data[i-10:i+10])) println("new: ", string(data[i-10:i+10]))
break break
} }
} }
panic("re-marshal code.json: different result") panic("re-marshal code.json: different result")
} }
} }
func BenchmarkCodeEncoder(b *testing.B) { func BenchmarkCodeEncoder(b *testing.B) {
if codeJSON == nil { if codeJSON == nil {
b.StopTimer() b.StopTimer()
codeInit() codeInit()
b.StartTimer() b.StartTimer()
} }
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
enc := NewEncoder(io.Discard) enc := NewEncoder(io.Discard)
for pb.Next() { for pb.Next() {
if err := enc.Encode(&codeStruct); err != nil { if err := enc.Encode(&codeStruct); err != nil {
b.Fatal("Encode:", err) b.Fatal("Encode:", err)
} }
} }
}) })
b.SetBytes(int64(len(codeJSON))) b.SetBytes(int64(len(codeJSON)))
} }
func BenchmarkCodeMarshal(b *testing.B) { func BenchmarkCodeMarshal(b *testing.B) {
if codeJSON == nil { if codeJSON == nil {
b.StopTimer() b.StopTimer()
codeInit() codeInit()
b.StartTimer() b.StartTimer()
} }
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
if _, err := Marshal(&codeStruct); err != nil { if _, err := Marshal(&codeStruct); err != nil {
b.Fatal("Marshal:", err) b.Fatal("Marshal:", err)
} }
} }
}) })
b.SetBytes(int64(len(codeJSON))) b.SetBytes(int64(len(codeJSON)))
} }
func BenchmarkCodeDecoder(b *testing.B) { func BenchmarkCodeDecoder(b *testing.B) {
if codeJSON == nil { if codeJSON == nil {
b.StopTimer() b.StopTimer()
codeInit() codeInit()
b.StartTimer() b.StartTimer()
} }
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
var buf bytes.Buffer var buf bytes.Buffer
dec := NewDecoder(&buf) dec := NewDecoder(&buf)
var r codeResponse var r codeResponse
for pb.Next() { for pb.Next() {
buf.Write(codeJSON) buf.Write(codeJSON)
// hide EOF // hide EOF
buf.WriteByte('\n') buf.WriteByte('\n')
buf.WriteByte('\n') buf.WriteByte('\n')
buf.WriteByte('\n') buf.WriteByte('\n')
if err := dec.Decode(&r); err != nil { if err := dec.Decode(&r); err != nil {
b.Fatal("Decode:", err) b.Fatal("Decode:", err)
} }
} }
}) })
b.SetBytes(int64(len(codeJSON))) b.SetBytes(int64(len(codeJSON)))
} }
func BenchmarkDecoderStream(b *testing.B) { func BenchmarkDecoderStream(b *testing.B) {
b.StopTimer() b.StopTimer()
var buf bytes.Buffer var buf bytes.Buffer
dec := NewDecoder(&buf) dec := NewDecoder(&buf)
buf.WriteString(`"` + strings.Repeat("x", 1000000) + `"` + "\n\n\n") buf.WriteString(`"` + strings.Repeat("x", 1000000) + `"` + "\n\n\n")
var x any var x any
if err := dec.Decode(&x); err != nil { if err := dec.Decode(&x); err != nil {
b.Fatal("Decode:", err) b.Fatal("Decode:", err)
} }
ones := strings.Repeat(" 1\n", 300000) + "\n\n\n" ones := strings.Repeat(" 1\n", 300000) + "\n\n\n"
b.StartTimer() b.StartTimer()
for i := range b.N { for i := range b.N {
if i%300000 == 0 { if i%300000 == 0 {
buf.WriteString(ones) buf.WriteString(ones)
} }
x = nil x = nil
if err := dec.Decode(&x); err != nil || x != 1.0 { if err := dec.Decode(&x); err != nil || x != 1.0 {
b.Fatalf("Decode: %v after %d", err, i) b.Fatalf("Decode: %v after %d", err, i)
} }
} }
} }
func BenchmarkCodeUnmarshal(b *testing.B) { func BenchmarkCodeUnmarshal(b *testing.B) {
if codeJSON == nil { if codeJSON == nil {
b.StopTimer() b.StopTimer()
codeInit() codeInit()
b.StartTimer() b.StartTimer()
} }
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
var r codeResponse var r codeResponse
if err := Unmarshal(codeJSON, &r); err != nil { if err := Unmarshal(codeJSON, &r); err != nil {
b.Fatal("Unmarshal:", err) b.Fatal("Unmarshal:", err)
} }
} }
}) })
b.SetBytes(int64(len(codeJSON))) b.SetBytes(int64(len(codeJSON)))
} }
func BenchmarkCodeUnmarshalReuse(b *testing.B) { func BenchmarkCodeUnmarshalReuse(b *testing.B) {
if codeJSON == nil { if codeJSON == nil {
b.StopTimer() b.StopTimer()
codeInit() codeInit()
b.StartTimer() b.StartTimer()
} }
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
var r codeResponse var r codeResponse
for pb.Next() { for pb.Next() {
if err := Unmarshal(codeJSON, &r); err != nil { if err := Unmarshal(codeJSON, &r); err != nil {
b.Fatal("Unmarshal:", err) b.Fatal("Unmarshal:", err)
} }
} }
}) })
// TODO(bcmills): Is there a missing b.SetBytes here? // TODO(bcmills): Is there a missing b.SetBytes here?
} }
func BenchmarkUnmarshalString(b *testing.B) { func BenchmarkUnmarshalString(b *testing.B) {
data := []byte(`"hello, world"`) data := []byte(`"hello, world"`)
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
var s string var s string
for pb.Next() { for pb.Next() {
if err := Unmarshal(data, &s); err != nil { if err := Unmarshal(data, &s); err != nil {
b.Fatal("Unmarshal:", err) b.Fatal("Unmarshal:", err)
} }
} }
}) })
} }
func BenchmarkUnmarshalFloat64(b *testing.B) { func BenchmarkUnmarshalFloat64(b *testing.B) {
data := []byte(`3.14`) data := []byte(`3.14`)
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
var f float64 var f float64
for pb.Next() { for pb.Next() {
if err := Unmarshal(data, &f); err != nil { if err := Unmarshal(data, &f); err != nil {
b.Fatal("Unmarshal:", err) b.Fatal("Unmarshal:", err)
} }
} }
}) })
} }
func BenchmarkUnmarshalInt64(b *testing.B) { func BenchmarkUnmarshalInt64(b *testing.B) {
data := []byte(`3`) data := []byte(`3`)
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
var x int64 var x int64
for pb.Next() { for pb.Next() {
if err := Unmarshal(data, &x); err != nil { if err := Unmarshal(data, &x); err != nil {
b.Fatal("Unmarshal:", err) b.Fatal("Unmarshal:", err)
} }
} }
}) })
} }
func BenchmarkIssue10335(b *testing.B) { func BenchmarkIssue10335(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
j := []byte(`{"a":{ }}`) j := []byte(`{"a":{ }}`)
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
var s struct{} var s struct{}
for pb.Next() { for pb.Next() {
if err := Unmarshal(j, &s); err != nil { if err := Unmarshal(j, &s); err != nil {
b.Fatal(err) b.Fatal(err)
} }
} }
}) })
} }
func BenchmarkUnmapped(b *testing.B) { func BenchmarkUnmapped(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
j := []byte(`{"s": "hello", "y": 2, "o": {"x": 0}, "a": [1, 99, {"x": 1}]}`) j := []byte(`{"s": "hello", "y": 2, "o": {"x": 0}, "a": [1, 99, {"x": 1}]}`)
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
var s struct{} var s struct{}
for pb.Next() { for pb.Next() {
if err := Unmarshal(j, &s); err != nil { if err := Unmarshal(j, &s); err != nil {
b.Fatal(err) b.Fatal(err)
} }
} }
}) })
} }

2562
decode.go

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2688
encode.go

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,73 +1,73 @@
// Copyright 2016 The Go Authors. All rights reserved. // Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package json_test package json_test
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "log"
"strings" "strings"
) )
type Animal int type Animal int
const ( const (
Unknown Animal = iota Unknown Animal = iota
Gopher Gopher
Zebra Zebra
) )
func (a *Animal) UnmarshalJSON(b []byte) error { func (a *Animal) UnmarshalJSON(b []byte) error {
var s string var s string
if err := json.Unmarshal(b, &s); err != nil { if err := json.Unmarshal(b, &s); err != nil {
return err return err
} }
switch strings.ToLower(s) { switch strings.ToLower(s) {
default: default:
*a = Unknown *a = Unknown
case "gopher": case "gopher":
*a = Gopher *a = Gopher
case "zebra": case "zebra":
*a = Zebra *a = Zebra
} }
return nil return nil
} }
func (a Animal) MarshalJSON() ([]byte, error) { func (a Animal) MarshalJSON() ([]byte, error) {
var s string var s string
switch a { switch a {
default: default:
s = "unknown" s = "unknown"
case Gopher: case Gopher:
s = "gopher" s = "gopher"
case Zebra: case Zebra:
s = "zebra" s = "zebra"
} }
return json.Marshal(s) return json.Marshal(s)
} }
func Example_customMarshalJSON() { func Example_customMarshalJSON() {
blob := `["gopher","armadillo","zebra","unknown","gopher","bee","gopher","zebra"]` blob := `["gopher","armadillo","zebra","unknown","gopher","bee","gopher","zebra"]`
var zoo []Animal var zoo []Animal
if err := json.Unmarshal([]byte(blob), &zoo); err != nil { if err := json.Unmarshal([]byte(blob), &zoo); err != nil {
log.Fatal(err) log.Fatal(err)
} }
census := make(map[Animal]int) census := make(map[Animal]int)
for _, animal := range zoo { for _, animal := range zoo {
census[animal]++ census[animal]++
} }
fmt.Printf("Zoo Census:\n* Gophers: %d\n* Zebras: %d\n* Unknown: %d\n", fmt.Printf("Zoo Census:\n* Gophers: %d\n* Zebras: %d\n* Unknown: %d\n",
census[Gopher], census[Zebra], census[Unknown]) census[Gopher], census[Zebra], census[Unknown])
// Output: // Output:
// Zoo Census: // Zoo Census:
// * Gophers: 3 // * Gophers: 3
// * Zebras: 2 // * Zebras: 2
// * Unknown: 3 // * Unknown: 3
} }

280
fold.go
View File

@ -1,140 +1,140 @@
// Copyright 2013 The Go Authors. All rights reserved. // Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package json package json
import ( import (
"bytes" "bytes"
"unicode/utf8" "unicode/utf8"
) )
const ( const (
caseMask = ^byte(0x20) // Mask to ignore case in ASCII. caseMask = ^byte(0x20) // Mask to ignore case in ASCII.
kelvin = '\u212a' kelvin = '\u212a'
smallLongEss = '\u017f' smallLongEss = '\u017f'
) )
// foldFunc returns one of four different case folding equivalence // foldFunc returns one of four different case folding equivalence
// functions, from most general (and slow) to fastest: // functions, from most general (and slow) to fastest:
// //
// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8 // 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') // 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S')
// 3) asciiEqualFold, no special, but includes non-letters (including _) // 3) asciiEqualFold, no special, but includes non-letters (including _)
// 4) simpleLetterEqualFold, no specials, no non-letters. // 4) simpleLetterEqualFold, no specials, no non-letters.
// //
// The letters S and K are special because they map to 3 runes, not just 2: // 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 // - S maps to s and to U+017F 'ſ' Latin small letter long s
// - k maps to K and to U+212A '' Kelvin sign // - k maps to K and to U+212A '' Kelvin sign
// //
// See https://play.golang.org/p/tTxjOc0OGo // See https://play.golang.org/p/tTxjOc0OGo
// //
// The returned function is specialized for matching against s and // The returned function is specialized for matching against s and
// should only be given s. It's not curried for performance reasons. // should only be given s. It's not curried for performance reasons.
func foldFunc(s []byte) func(s, t []byte) bool { func foldFunc(s []byte) func(s, t []byte) bool {
nonLetter := false nonLetter := false
special := false // special letter special := false // special letter
for _, b := range s { for _, b := range s {
if b >= utf8.RuneSelf { if b >= utf8.RuneSelf {
return bytes.EqualFold return bytes.EqualFold
} }
upper := b & caseMask upper := b & caseMask
if upper < 'A' || upper > 'Z' { if upper < 'A' || upper > 'Z' {
nonLetter = true nonLetter = true
} else if upper == 'K' || upper == 'S' { } else if upper == 'K' || upper == 'S' {
// See above for why these letters are special. // See above for why these letters are special.
special = true special = true
} }
} }
if special { if special {
return equalFoldRight return equalFoldRight
} }
if nonLetter { if nonLetter {
return asciiEqualFold return asciiEqualFold
} }
return simpleLetterEqualFold return simpleLetterEqualFold
} }
// equalFoldRight is a specialization of bytes.EqualFold when s is // equalFoldRight is a specialization of bytes.EqualFold when s is
// known to be all ASCII (including punctuation), but contains an 's', // known to be all ASCII (including punctuation), but contains an 's',
// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t. // 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t.
// See comments on foldFunc. // See comments on foldFunc.
func equalFoldRight(s, t []byte) bool { func equalFoldRight(s, t []byte) bool {
for _, sb := range s { for _, sb := range s {
if len(t) == 0 { if len(t) == 0 {
return false return false
} }
tb := t[0] tb := t[0]
if tb < utf8.RuneSelf { if tb < utf8.RuneSelf {
if sb != tb { if sb != tb {
sbUpper := sb & caseMask sbUpper := sb & caseMask
if 'A' <= sbUpper && sbUpper <= 'Z' { if 'A' <= sbUpper && sbUpper <= 'Z' {
if sbUpper != tb&caseMask { if sbUpper != tb&caseMask {
return false return false
} }
} else { } else {
return false return false
} }
} }
t = t[1:] t = t[1:]
continue continue
} }
// sb is ASCII and t is not. t must be either kelvin // sb is ASCII and t is not. t must be either kelvin
// sign or long s; sb must be s, S, k, or K. // sign or long s; sb must be s, S, k, or K.
tr, size := utf8.DecodeRune(t) tr, size := utf8.DecodeRune(t)
switch sb { switch sb {
case 's', 'S': case 's', 'S':
if tr != smallLongEss { if tr != smallLongEss {
return false return false
} }
case 'k', 'K': case 'k', 'K':
if tr != kelvin { if tr != kelvin {
return false return false
} }
default: default:
return false return false
} }
t = t[size:] t = t[size:]
} }
return len(t) <= 0 return len(t) <= 0
} }
// asciiEqualFold is a specialization of bytes.EqualFold for use when // asciiEqualFold is a specialization of bytes.EqualFold for use when
// s is all ASCII (but may contain non-letters) and contains no // s is all ASCII (but may contain non-letters) and contains no
// special-folding letters. // special-folding letters.
// See comments on foldFunc. // See comments on foldFunc.
func asciiEqualFold(s, t []byte) bool { func asciiEqualFold(s, t []byte) bool {
if len(s) != len(t) { if len(s) != len(t) {
return false return false
} }
for i, sb := range s { for i, sb := range s {
tb := t[i] tb := t[i]
if sb == tb { if sb == tb {
continue continue
} }
if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') { if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') {
if sb&caseMask != tb&caseMask { if sb&caseMask != tb&caseMask {
return false return false
} }
} else { } else {
return false return false
} }
} }
return true return true
} }
// simpleLetterEqualFold is a specialization of bytes.EqualFold for // simpleLetterEqualFold is a specialization of bytes.EqualFold for
// use when s is all ASCII letters (no underscores, etc) and also // use when s is all ASCII letters (no underscores, etc) and also
// doesn't contain 'k', 'K', 's', or 'S'. // doesn't contain 'k', 'K', 's', or 'S'.
// See comments on foldFunc. // See comments on foldFunc.
func simpleLetterEqualFold(s, t []byte) bool { func simpleLetterEqualFold(s, t []byte) bool {
if len(s) != len(t) { if len(s) != len(t) {
return false return false
} }
for i, b := range s { for i, b := range s {
if b&caseMask != t[i]&caseMask { if b&caseMask != t[i]&caseMask {
return false return false
} }
} }
return true return true
} }

View File

@ -1,116 +1,116 @@
// Copyright 2013 The Go Authors. All rights reserved. // Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package json package json
import ( import (
"bytes" "bytes"
"strings" "strings"
"testing" "testing"
"unicode/utf8" "unicode/utf8"
) )
var foldTests = []struct { var foldTests = []struct {
fn func(s, t []byte) bool fn func(s, t []byte) bool
s, t string s, t string
want bool want bool
}{ }{
{equalFoldRight, "", "", true}, {equalFoldRight, "", "", true},
{equalFoldRight, "a", "a", true}, {equalFoldRight, "a", "a", true},
{equalFoldRight, "", "a", false}, {equalFoldRight, "", "a", false},
{equalFoldRight, "a", "", false}, {equalFoldRight, "a", "", false},
{equalFoldRight, "a", "A", true}, {equalFoldRight, "a", "A", true},
{equalFoldRight, "AB", "ab", true}, {equalFoldRight, "AB", "ab", true},
{equalFoldRight, "AB", "ac", false}, {equalFoldRight, "AB", "ac", false},
{equalFoldRight, "sbkKc", "ſbKc", true}, {equalFoldRight, "sbkKc", "ſbKc", true},
{equalFoldRight, "SbKkc", "ſbKc", true}, {equalFoldRight, "SbKkc", "ſbKc", true},
{equalFoldRight, "SbKkc", "ſbKK", false}, {equalFoldRight, "SbKkc", "ſbKK", false},
{equalFoldRight, "e", "é", false}, {equalFoldRight, "e", "é", false},
{equalFoldRight, "s", "S", true}, {equalFoldRight, "s", "S", true},
{simpleLetterEqualFold, "", "", true}, {simpleLetterEqualFold, "", "", true},
{simpleLetterEqualFold, "abc", "abc", true}, {simpleLetterEqualFold, "abc", "abc", true},
{simpleLetterEqualFold, "abc", "ABC", true}, {simpleLetterEqualFold, "abc", "ABC", true},
{simpleLetterEqualFold, "abc", "ABCD", false}, {simpleLetterEqualFold, "abc", "ABCD", false},
{simpleLetterEqualFold, "abc", "xxx", false}, {simpleLetterEqualFold, "abc", "xxx", false},
{asciiEqualFold, "a_B", "A_b", true}, {asciiEqualFold, "a_B", "A_b", true},
{asciiEqualFold, "aa@", "aa`", false}, // verify 0x40 and 0x60 aren't case-equivalent {asciiEqualFold, "aa@", "aa`", false}, // verify 0x40 and 0x60 aren't case-equivalent
} }
func TestFold(t *testing.T) { func TestFold(t *testing.T) {
for i, tt := range foldTests { for i, tt := range foldTests {
if got := tt.fn([]byte(tt.s), []byte(tt.t)); got != tt.want { 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) t.Errorf("%d. %q, %q = %v; want %v", i, tt.s, tt.t, got, tt.want)
} }
truth := strings.EqualFold(tt.s, tt.t) truth := strings.EqualFold(tt.s, tt.t)
if truth != tt.want { if truth != tt.want {
t.Errorf("strings.EqualFold doesn't agree with case %d", i) t.Errorf("strings.EqualFold doesn't agree with case %d", i)
} }
} }
} }
func TestFoldAgainstUnicode(t *testing.T) { func TestFoldAgainstUnicode(t *testing.T) {
const bufSize = 5 const bufSize = 5
buf1 := make([]byte, 0, bufSize) buf1 := make([]byte, 0, bufSize)
buf2 := make([]byte, 0, bufSize) buf2 := make([]byte, 0, bufSize)
var runes []rune var runes []rune
for i := 0x20; i <= 0x7f; i++ { for i := 0x20; i <= 0x7f; i++ {
runes = append(runes, rune(i)) runes = append(runes, rune(i))
} }
runes = append(runes, kelvin, smallLongEss) runes = append(runes, kelvin, smallLongEss)
funcs := []struct { funcs := []struct {
name string name string
fold func(s, t []byte) bool fold func(s, t []byte) bool
letter bool // must be ASCII letter letter bool // must be ASCII letter
simple bool // must be simple ASCII letter (not 'S' or 'K') simple bool // must be simple ASCII letter (not 'S' or 'K')
}{ }{
{ {
name: "equalFoldRight", name: "equalFoldRight",
fold: equalFoldRight, fold: equalFoldRight,
}, },
{ {
name: "asciiEqualFold", name: "asciiEqualFold",
fold: asciiEqualFold, fold: asciiEqualFold,
simple: true, simple: true,
}, },
{ {
name: "simpleLetterEqualFold", name: "simpleLetterEqualFold",
fold: simpleLetterEqualFold, fold: simpleLetterEqualFold,
simple: true, simple: true,
letter: true, letter: true,
}, },
} }
for _, ff := range funcs { for _, ff := range funcs {
for _, r := range runes { for _, r := range runes {
if r >= utf8.RuneSelf { if r >= utf8.RuneSelf {
continue continue
} }
if ff.letter && !isASCIILetter(byte(r)) { if ff.letter && !isASCIILetter(byte(r)) {
continue continue
} }
if ff.simple && (r == 's' || r == 'S' || r == 'k' || r == 'K') { if ff.simple && (r == 's' || r == 'S' || r == 'k' || r == 'K') {
continue continue
} }
for _, r2 := range runes { for _, r2 := range runes {
buf1 := append(buf1[:0], 'x') buf1 := append(buf1[:0], 'x')
buf2 := append(buf2[:0], 'x') buf2 := append(buf2[:0], 'x')
buf1 = buf1[:1+utf8.EncodeRune(buf1[1:bufSize], r)] buf1 = buf1[:1+utf8.EncodeRune(buf1[1:bufSize], r)]
buf2 = buf2[:1+utf8.EncodeRune(buf2[1:bufSize], r2)] buf2 = buf2[:1+utf8.EncodeRune(buf2[1:bufSize], r2)]
buf1 = append(buf1, 'x') buf1 = append(buf1, 'x')
buf2 = append(buf2, 'x') buf2 = append(buf2, 'x')
want := bytes.EqualFold(buf1, buf2) want := bytes.EqualFold(buf1, buf2)
if got := ff.fold(buf1, buf2); got != want { if got := ff.fold(buf1, buf2); got != want {
t.Errorf("%s(%q, %q) = %v; want %v", ff.name, buf1, buf2, got, want) t.Errorf("%s(%q, %q) = %v; want %v", ff.name, buf1, buf2, got, want)
} }
} }
} }
} }
} }
func isASCIILetter(b byte) bool { func isASCIILetter(b byte) bool {
return ('A' <= b && b <= 'Z') || ('a' <= b && b <= 'z') return ('A' <= b && b <= 'Z') || ('a' <= b && b <= 'z')
} }

282
indent.go
View File

@ -1,141 +1,141 @@
// Copyright 2010 The Go Authors. All rights reserved. // Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package json package json
import "bytes" import "bytes"
// Compact appends to dst the JSON-encoded src with // Compact appends to dst the JSON-encoded src with
// insignificant space characters elided. // insignificant space characters elided.
func Compact(dst *bytes.Buffer, src []byte) error { func Compact(dst *bytes.Buffer, src []byte) error {
return compact(dst, src, false) return compact(dst, src, false)
} }
func compact(dst *bytes.Buffer, src []byte, escape bool) error { func compact(dst *bytes.Buffer, src []byte, escape bool) error {
origLen := dst.Len() origLen := dst.Len()
var scan scanner var scan scanner
scan.reset() scan.reset()
start := 0 start := 0
for i, c := range src { for i, c := range src {
if escape && (c == '<' || c == '>' || c == '&') { if escape && (c == '<' || c == '>' || c == '&') {
if start < i { if start < i {
dst.Write(src[start:i]) dst.Write(src[start:i])
} }
dst.WriteString(`\u00`) dst.WriteString(`\u00`)
dst.WriteByte(hex[c>>4]) dst.WriteByte(hex[c>>4])
dst.WriteByte(hex[c&0xF]) dst.WriteByte(hex[c&0xF])
start = i + 1 start = i + 1
} }
// Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9). // 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 c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
if start < i { if start < i {
dst.Write(src[start:i]) dst.Write(src[start:i])
} }
dst.WriteString(`\u202`) dst.WriteString(`\u202`)
dst.WriteByte(hex[src[i+2]&0xF]) dst.WriteByte(hex[src[i+2]&0xF])
start = i + 3 start = i + 3
} }
v := scan.step(&scan, c) v := scan.step(&scan, c)
if v >= scanSkipSpace { if v >= scanSkipSpace {
if v == scanError { if v == scanError {
break break
} }
if start < i { if start < i {
dst.Write(src[start:i]) dst.Write(src[start:i])
} }
start = i + 1 start = i + 1
} }
} }
if scan.eof() == scanError { if scan.eof() == scanError {
dst.Truncate(origLen) dst.Truncate(origLen)
return scan.err return scan.err
} }
if start < len(src) { if start < len(src) {
dst.Write(src[start:]) dst.Write(src[start:])
} }
return nil return nil
} }
func newline(dst *bytes.Buffer, prefix, indent string, depth int) { func newline(dst *bytes.Buffer, prefix, indent string, depth int) {
dst.WriteByte('\n') dst.WriteByte('\n')
dst.WriteString(prefix) dst.WriteString(prefix)
for range depth { for range depth {
dst.WriteString(indent) dst.WriteString(indent)
} }
} }
// Indent appends to dst an indented form of the JSON-encoded src. // Indent appends to dst an indented form of the JSON-encoded src.
// Each element in a JSON object or array begins on a new, // Each element in a JSON object or array begins on a new,
// indented line beginning with prefix followed by one or more // indented line beginning with prefix followed by one or more
// copies of indent according to the indentation nesting. // copies of indent according to the indentation nesting.
// The data appended to dst does not begin with the prefix nor // 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. // any indentation, to make it easier to embed inside other formatted JSON data.
// Although leading space characters (space, tab, carriage return, newline) // Although leading space characters (space, tab, carriage return, newline)
// at the beginning of src are dropped, trailing space characters // at the beginning of src are dropped, trailing space characters
// at the end of src are preserved and copied to dst. // at the end of src are preserved and copied to dst.
// For example, if src has no trailing spaces, neither will dst; // For example, if src has no trailing spaces, neither will dst;
// if src ends in a trailing newline, so will dst. // if src ends in a trailing newline, so will dst.
func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error { func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
origLen := dst.Len() origLen := dst.Len()
var scan scanner var scan scanner
scan.reset() scan.reset()
needIndent := false needIndent := false
depth := 0 depth := 0
for _, c := range src { for _, c := range src {
scan.bytes++ scan.bytes++
v := scan.step(&scan, c) v := scan.step(&scan, c)
if v == scanSkipSpace { if v == scanSkipSpace {
continue continue
} }
if v == scanError { if v == scanError {
break break
} }
if needIndent && v != scanEndObject && v != scanEndArray { if needIndent && v != scanEndObject && v != scanEndArray {
needIndent = false needIndent = false
depth++ depth++
newline(dst, prefix, indent, depth) newline(dst, prefix, indent, depth)
} }
// Emit semantically uninteresting bytes // Emit semantically uninteresting bytes
// (in particular, punctuation in strings) unmodified. // (in particular, punctuation in strings) unmodified.
if v == scanContinue { if v == scanContinue {
dst.WriteByte(c) dst.WriteByte(c)
continue continue
} }
// Add spacing around real punctuation. // Add spacing around real punctuation.
switch c { switch c {
case '{', '[': case '{', '[':
// delay indent so that empty object and array are formatted as {} and []. // delay indent so that empty object and array are formatted as {} and [].
needIndent = true needIndent = true
dst.WriteByte(c) dst.WriteByte(c)
case ',': case ',':
dst.WriteByte(c) dst.WriteByte(c)
newline(dst, prefix, indent, depth) newline(dst, prefix, indent, depth)
case ':': case ':':
dst.WriteByte(c) dst.WriteByte(c)
dst.WriteByte(' ') dst.WriteByte(' ')
case '}', ']': case '}', ']':
if needIndent { if needIndent {
// suppress indent in empty object/array // suppress indent in empty object/array
needIndent = false needIndent = false
} else { } else {
depth-- depth--
newline(dst, prefix, indent, depth) newline(dst, prefix, indent, depth)
} }
dst.WriteByte(c) dst.WriteByte(c)
default: default:
dst.WriteByte(c) dst.WriteByte(c)
} }
} }
if scan.eof() == scanError { if scan.eof() == scanError {
dst.Truncate(origLen) dst.Truncate(origLen)
return scan.err return scan.err
} }
return nil return nil
} }

View File

@ -1,133 +1,133 @@
// Copyright 2011 The Go Authors. All rights reserved. // Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package json package json
import ( import (
"regexp" "regexp"
"testing" "testing"
) )
func TestNumberIsValid(t *testing.T) { func TestNumberIsValid(t *testing.T) {
// From: http://stackoverflow.com/a/13340826 // From: http://stackoverflow.com/a/13340826
var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`) var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`)
validTests := []string{ validTests := []string{
"0", "0",
"-0", "-0",
"1", "1",
"-1", "-1",
"0.1", "0.1",
"-0.1", "-0.1",
"1234", "1234",
"-1234", "-1234",
"12.34", "12.34",
"-12.34", "-12.34",
"12E0", "12E0",
"12E1", "12E1",
"12e34", "12e34",
"12E-0", "12E-0",
"12e+1", "12e+1",
"12e-34", "12e-34",
"-12E0", "-12E0",
"-12E1", "-12E1",
"-12e34", "-12e34",
"-12E-0", "-12E-0",
"-12e+1", "-12e+1",
"-12e-34", "-12e-34",
"1.2E0", "1.2E0",
"1.2E1", "1.2E1",
"1.2e34", "1.2e34",
"1.2E-0", "1.2E-0",
"1.2e+1", "1.2e+1",
"1.2e-34", "1.2e-34",
"-1.2E0", "-1.2E0",
"-1.2E1", "-1.2E1",
"-1.2e34", "-1.2e34",
"-1.2E-0", "-1.2E-0",
"-1.2e+1", "-1.2e+1",
"-1.2e-34", "-1.2e-34",
"0E0", "0E0",
"0E1", "0E1",
"0e34", "0e34",
"0E-0", "0E-0",
"0e+1", "0e+1",
"0e-34", "0e-34",
"-0E0", "-0E0",
"-0E1", "-0E1",
"-0e34", "-0e34",
"-0E-0", "-0E-0",
"-0e+1", "-0e+1",
"-0e-34", "-0e-34",
} }
for _, test := range validTests { for _, test := range validTests {
if !isValidNumber(test) { if !isValidNumber(test) {
t.Errorf("%s should be valid", test) t.Errorf("%s should be valid", test)
} }
var f float64 var f float64
if err := Unmarshal([]byte(test), &f); err != nil { if err := Unmarshal([]byte(test), &f); err != nil {
t.Errorf("%s should be valid but Unmarshal failed: %v", test, err) t.Errorf("%s should be valid but Unmarshal failed: %v", test, err)
} }
if !jsonNumberRegexp.MatchString(test) { if !jsonNumberRegexp.MatchString(test) {
t.Errorf("%s should be valid but regexp does not match", test) t.Errorf("%s should be valid but regexp does not match", test)
} }
} }
invalidTests := []string{ invalidTests := []string{
"", "",
"invalid", "invalid",
"1.0.1", "1.0.1",
"1..1", "1..1",
"-1-2", "-1-2",
"012a42", "012a42",
"01.2", "01.2",
"012", "012",
"12E12.12", "12E12.12",
"1e2e3", "1e2e3",
"1e+-2", "1e+-2",
"1e--23", "1e--23",
"1e", "1e",
"e1", "e1",
"1e+", "1e+",
"1ea", "1ea",
"1a", "1a",
"1.a", "1.a",
"1.", "1.",
"01", "01",
"1.e1", "1.e1",
} }
for _, test := range invalidTests { for _, test := range invalidTests {
if isValidNumber(test) { if isValidNumber(test) {
t.Errorf("%s should be invalid", test) t.Errorf("%s should be invalid", test)
} }
var f float64 var f float64
if err := Unmarshal([]byte(test), &f); err == nil { if err := Unmarshal([]byte(test), &f); err == nil {
t.Errorf("%s should be invalid but unmarshal wrote %v", test, f) t.Errorf("%s should be invalid but unmarshal wrote %v", test, f)
} }
if jsonNumberRegexp.MatchString(test) { if jsonNumberRegexp.MatchString(test) {
t.Errorf("%s should be invalid but matches regexp", test) t.Errorf("%s should be invalid but matches regexp", test)
} }
} }
} }
func BenchmarkNumberIsValid(b *testing.B) { func BenchmarkNumberIsValid(b *testing.B) {
s := "-61657.61667E+61673" s := "-61657.61667E+61673"
for b.Loop() { for b.Loop() {
isValidNumber(s) isValidNumber(s)
} }
} }
func BenchmarkNumberIsValidRegexp(b *testing.B) { func BenchmarkNumberIsValidRegexp(b *testing.B) {
var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`) var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`)
s := "-61657.61667E+61673" s := "-61657.61667E+61673"
for b.Loop() { for b.Loop() {
jsonNumberRegexp.MatchString(s) jsonNumberRegexp.MatchString(s)
} }
} }

1256
scanner.go

File diff suppressed because it is too large Load Diff

View File

@ -1,336 +1,336 @@
// Copyright 2010 The Go Authors. All rights reserved. // Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package json package json
import ( import (
"bytes" "bytes"
"math" "math"
"math/rand/v2" "math/rand/v2"
"reflect" "reflect"
"testing" "testing"
) )
var validTests = []struct { var validTests = []struct {
data string data string
ok bool ok bool
}{ }{
{`foo`, false}, {`foo`, false},
{`}{`, false}, {`}{`, false},
{`{]`, false}, {`{]`, false},
{`{}`, true}, {`{}`, true},
{`{"foo":"bar"}`, true}, {`{"foo":"bar"}`, true},
{`{"foo":"bar","bar":{"baz":["qux"]}}`, true}, {`{"foo":"bar","bar":{"baz":["qux"]}}`, true},
} }
func TestValid(t *testing.T) { func TestValid(t *testing.T) {
for _, tt := range validTests { for _, tt := range validTests {
if ok := Valid([]byte(tt.data)); ok != tt.ok { if ok := Valid([]byte(tt.data)); ok != tt.ok {
t.Errorf("Valid(%#q) = %v, want %v", tt.data, ok, tt.ok) t.Errorf("Valid(%#q) = %v, want %v", tt.data, ok, tt.ok)
} }
} }
} }
// Tests of simple examples. // Tests of simple examples.
type example struct { type example struct {
compact string compact string
indent string indent string
} }
var examples = []example{ var examples = []example{
{`1`, `1`}, {`1`, `1`},
{`{}`, `{}`}, {`{}`, `{}`},
{`[]`, `[]`}, {`[]`, `[]`},
{`{"":2}`, "{\n\t\"\": 2\n}"}, {`{"":2}`, "{\n\t\"\": 2\n}"},
{`[3]`, "[\n\t3\n]"}, {`[3]`, "[\n\t3\n]"},
{`[1,2,3]`, "[\n\t1,\n\t2,\n\t3\n]"}, {`[1,2,3]`, "[\n\t1,\n\t2,\n\t3\n]"},
{`{"x":1}`, "{\n\t\"x\": 1\n}"}, {`{"x":1}`, "{\n\t\"x\": 1\n}"},
{ex1, ex1i}, {ex1, ex1i},
} }
var ex1 = `[true,false,null,"x",1,1.5,0,-5e+2]` var ex1 = `[true,false,null,"x",1,1.5,0,-5e+2]`
var ex1i = `[ var ex1i = `[
true, true,
false, false,
null, null,
"x", "x",
1, 1,
1.5, 1.5,
0, 0,
-5e+2 -5e+2
]` ]`
func TestCompact(t *testing.T) { func TestCompact(t *testing.T) {
var buf bytes.Buffer var buf bytes.Buffer
for _, tt := range examples { for _, tt := range examples {
buf.Reset() buf.Reset()
if err := Compact(&buf, []byte(tt.compact)); err != nil { if err := Compact(&buf, []byte(tt.compact)); err != nil {
t.Errorf("Compact(%#q): %v", tt.compact, err) t.Errorf("Compact(%#q): %v", tt.compact, err)
} else if s := buf.String(); s != tt.compact { } else if s := buf.String(); s != tt.compact {
t.Errorf("Compact(%#q) = %#q, want original", tt.compact, s) t.Errorf("Compact(%#q) = %#q, want original", tt.compact, s)
} }
buf.Reset() buf.Reset()
if err := Compact(&buf, []byte(tt.indent)); err != nil { if err := Compact(&buf, []byte(tt.indent)); err != nil {
t.Errorf("Compact(%#q): %v", tt.indent, err) t.Errorf("Compact(%#q): %v", tt.indent, err)
continue continue
} else if s := buf.String(); s != tt.compact { } else if s := buf.String(); s != tt.compact {
t.Errorf("Compact(%#q) = %#q, want %#q", tt.indent, s, tt.compact) t.Errorf("Compact(%#q) = %#q, want %#q", tt.indent, s, tt.compact)
} }
} }
} }
func TestCompactSeparators(t *testing.T) { func TestCompactSeparators(t *testing.T) {
// U+2028 and U+2029 should be escaped inside strings. // U+2028 and U+2029 should be escaped inside strings.
// They should not appear outside strings. // They should not appear outside strings.
tests := []struct { tests := []struct {
in, compact string in, compact string
}{ }{
{"{\"\u2028\": 1}", `{"\u2028":1}`}, {"{\"\u2028\": 1}", `{"\u2028":1}`},
{"{\"\u2029\" :2}", `{"\u2029":2}`}, {"{\"\u2029\" :2}", `{"\u2029":2}`},
} }
for _, tt := range tests { for _, tt := range tests {
var buf bytes.Buffer var buf bytes.Buffer
if err := Compact(&buf, []byte(tt.in)); err != nil { if err := Compact(&buf, []byte(tt.in)); err != nil {
t.Errorf("Compact(%q): %v", tt.in, err) t.Errorf("Compact(%q): %v", tt.in, err)
} else if s := buf.String(); s != tt.compact { } else if s := buf.String(); s != tt.compact {
t.Errorf("Compact(%q) = %q, want %q", tt.in, s, tt.compact) t.Errorf("Compact(%q) = %q, want %q", tt.in, s, tt.compact)
} }
} }
} }
func TestIndent(t *testing.T) { func TestIndent(t *testing.T) {
var buf bytes.Buffer var buf bytes.Buffer
for _, tt := range examples { for _, tt := range examples {
buf.Reset() buf.Reset()
if err := Indent(&buf, []byte(tt.indent), "", "\t"); err != nil { if err := Indent(&buf, []byte(tt.indent), "", "\t"); err != nil {
t.Errorf("Indent(%#q): %v", tt.indent, err) t.Errorf("Indent(%#q): %v", tt.indent, err)
} else if s := buf.String(); s != tt.indent { } else if s := buf.String(); s != tt.indent {
t.Errorf("Indent(%#q) = %#q, want original", tt.indent, s) t.Errorf("Indent(%#q) = %#q, want original", tt.indent, s)
} }
buf.Reset() buf.Reset()
if err := Indent(&buf, []byte(tt.compact), "", "\t"); err != nil { if err := Indent(&buf, []byte(tt.compact), "", "\t"); err != nil {
t.Errorf("Indent(%#q): %v", tt.compact, err) t.Errorf("Indent(%#q): %v", tt.compact, err)
continue continue
} else if s := buf.String(); s != tt.indent { } else if s := buf.String(); s != tt.indent {
t.Errorf("Indent(%#q) = %#q, want %#q", tt.compact, s, tt.indent) t.Errorf("Indent(%#q) = %#q, want %#q", tt.compact, s, tt.indent)
} }
} }
} }
// Tests of a large random structure. // Tests of a large random structure.
func TestCompactBig(t *testing.T) { func TestCompactBig(t *testing.T) {
initBig() initBig()
var buf bytes.Buffer var buf bytes.Buffer
if err := Compact(&buf, jsonBig); err != nil { if err := Compact(&buf, jsonBig); err != nil {
t.Fatalf("Compact: %v", err) t.Fatalf("Compact: %v", err)
} }
b := buf.Bytes() b := buf.Bytes()
if !bytes.Equal(b, jsonBig) { if !bytes.Equal(b, jsonBig) {
t.Error("Compact(jsonBig) != jsonBig") t.Error("Compact(jsonBig) != jsonBig")
diff(t, b, jsonBig) diff(t, b, jsonBig)
return return
} }
} }
func TestIndentBig(t *testing.T) { func TestIndentBig(t *testing.T) {
t.Parallel() t.Parallel()
initBig() initBig()
var buf bytes.Buffer var buf bytes.Buffer
if err := Indent(&buf, jsonBig, "", "\t"); err != nil { if err := Indent(&buf, jsonBig, "", "\t"); err != nil {
t.Fatalf("Indent1: %v", err) t.Fatalf("Indent1: %v", err)
} }
b := buf.Bytes() b := buf.Bytes()
if len(b) == len(jsonBig) { if len(b) == len(jsonBig) {
// jsonBig is compact (no unnecessary spaces); // jsonBig is compact (no unnecessary spaces);
// indenting should make it bigger // indenting should make it bigger
t.Fatalf("Indent(jsonBig) did not get bigger") t.Fatalf("Indent(jsonBig) did not get bigger")
} }
// should be idempotent // should be idempotent
var buf1 bytes.Buffer var buf1 bytes.Buffer
if err := Indent(&buf1, b, "", "\t"); err != nil { if err := Indent(&buf1, b, "", "\t"); err != nil {
t.Fatalf("Indent2: %v", err) t.Fatalf("Indent2: %v", err)
} }
b1 := buf1.Bytes() b1 := buf1.Bytes()
if !bytes.Equal(b1, b) { if !bytes.Equal(b1, b) {
t.Error("Indent(Indent(jsonBig)) != Indent(jsonBig)") t.Error("Indent(Indent(jsonBig)) != Indent(jsonBig)")
diff(t, b1, b) diff(t, b1, b)
return return
} }
// should get back to original // should get back to original
buf1.Reset() buf1.Reset()
if err := Compact(&buf1, b); err != nil { if err := Compact(&buf1, b); err != nil {
t.Fatalf("Compact: %v", err) t.Fatalf("Compact: %v", err)
} }
b1 = buf1.Bytes() b1 = buf1.Bytes()
if !bytes.Equal(b1, jsonBig) { if !bytes.Equal(b1, jsonBig) {
t.Error("Compact(Indent(jsonBig)) != jsonBig") t.Error("Compact(Indent(jsonBig)) != jsonBig")
diff(t, b1, jsonBig) diff(t, b1, jsonBig)
return return
} }
} }
type indentErrorTest struct { type indentErrorTest struct {
in string in string
err error err error
} }
var indentErrorTests = []indentErrorTest{ var indentErrorTests = []indentErrorTest{
{`{"X": "foo", "Y"}`, &SyntaxError{"invalid character '}' after object key", 17}}, {`{"X": "foo", "Y"}`, &SyntaxError{"invalid character '}' after object key", 17}},
{`{"X": "foo" "Y": "bar"}`, &SyntaxError{"invalid character '\"' after object key:value pair", 13}}, {`{"X": "foo" "Y": "bar"}`, &SyntaxError{"invalid character '\"' after object key:value pair", 13}},
} }
func TestIndentErrors(t *testing.T) { func TestIndentErrors(t *testing.T) {
for i, tt := range indentErrorTests { for i, tt := range indentErrorTests {
slice := make([]uint8, 0) slice := make([]uint8, 0)
buf := bytes.NewBuffer(slice) buf := bytes.NewBuffer(slice)
if err := Indent(buf, []uint8(tt.in), "", ""); err != nil { if err := Indent(buf, []uint8(tt.in), "", ""); err != nil {
if !reflect.DeepEqual(err, tt.err) { if !reflect.DeepEqual(err, tt.err) {
t.Errorf("#%d: Indent: %#v", i, err) t.Errorf("#%d: Indent: %#v", i, err)
continue continue
} }
} }
} }
} }
func TestNextValueBig(t *testing.T) { func TestNextValueBig(t *testing.T) {
initBig() initBig()
var scan scanner var scan scanner
item, rest, err := nextValue(jsonBig, &scan) item, rest, err := nextValue(jsonBig, &scan)
if err != nil { if err != nil {
t.Fatalf("nextValue: %s", err) t.Fatalf("nextValue: %s", err)
} }
if len(item) != len(jsonBig) || &item[0] != &jsonBig[0] { if len(item) != len(jsonBig) || &item[0] != &jsonBig[0] {
t.Errorf("invalid item: %d %d", len(item), len(jsonBig)) t.Errorf("invalid item: %d %d", len(item), len(jsonBig))
} }
if len(rest) != 0 { if len(rest) != 0 {
t.Errorf("invalid rest: %d", len(rest)) t.Errorf("invalid rest: %d", len(rest))
} }
item, rest, err = nextValue(append(jsonBig, "HELLO WORLD"...), &scan) item, rest, err = nextValue(append(jsonBig, "HELLO WORLD"...), &scan)
if err != nil { if err != nil {
t.Fatalf("nextValue extra: %s", err) t.Fatalf("nextValue extra: %s", err)
} }
if len(item) != len(jsonBig) { if len(item) != len(jsonBig) {
t.Errorf("invalid item: %d %d", len(item), len(jsonBig)) t.Errorf("invalid item: %d %d", len(item), len(jsonBig))
} }
if string(rest) != "HELLO WORLD" { if string(rest) != "HELLO WORLD" {
t.Errorf("invalid rest: %d", len(rest)) t.Errorf("invalid rest: %d", len(rest))
} }
} }
var benchScan scanner var benchScan scanner
func BenchmarkSkipValue(b *testing.B) { func BenchmarkSkipValue(b *testing.B) {
initBig() initBig()
for b.Loop() { for b.Loop() {
_, _, _ = nextValue(jsonBig, &benchScan) _, _, _ = nextValue(jsonBig, &benchScan)
} }
b.SetBytes(int64(len(jsonBig))) b.SetBytes(int64(len(jsonBig)))
} }
func diff(t *testing.T, a, b []byte) { func diff(t *testing.T, a, b []byte) {
for i := 0; ; i++ { for i := 0; ; i++ {
if i >= len(a) || i >= len(b) || a[i] != b[i] { if i >= len(a) || i >= len(b) || a[i] != b[i] {
j := i - 10 j := i - 10
if j < 0 { if j < 0 {
j = 0 j = 0
} }
t.Errorf("diverge at %d: «%s» vs «%s»", i, trim(a[j:]), trim(b[j:])) t.Errorf("diverge at %d: «%s» vs «%s»", i, trim(a[j:]), trim(b[j:]))
return return
} }
} }
} }
func trim(b []byte) []byte { func trim(b []byte) []byte {
if len(b) > 20 { if len(b) > 20 {
return b[0:20] return b[0:20]
} }
return b return b
} }
// Generate a random JSON object. // Generate a random JSON object.
var jsonBig []byte var jsonBig []byte
func initBig() { func initBig() {
n := 10000 n := 10000
if testing.Short() { if testing.Short() {
n = 100 n = 100
} }
b, err := Marshal(genValue(n)) b, err := Marshal(genValue(n))
if err != nil { if err != nil {
panic(err) panic(err)
} }
jsonBig = b jsonBig = b
} }
func genValue(n int) any { func genValue(n int) any {
if n > 1 { if n > 1 {
switch rand.IntN(2) { switch rand.IntN(2) {
case 0: case 0:
return genArray(n) return genArray(n)
case 1: case 1:
return genMap(n) return genMap(n)
} }
} }
switch rand.IntN(3) { switch rand.IntN(3) {
case 0: case 0:
return rand.IntN(2) == 0 return rand.IntN(2) == 0
case 1: case 1:
return rand.NormFloat64() return rand.NormFloat64()
case 2: case 2:
return genString(30) return genString(30)
} }
panic("unreachable") panic("unreachable")
} }
func genString(stddev float64) string { func genString(stddev float64) string {
n := int(math.Abs(rand.NormFloat64()*stddev + stddev/2)) n := int(math.Abs(rand.NormFloat64()*stddev + stddev/2))
c := make([]rune, n) c := make([]rune, n)
for i := range c { for i := range c {
f := math.Abs(rand.NormFloat64()*64 + 32) f := math.Abs(rand.NormFloat64()*64 + 32)
if f > 0x10ffff { if f > 0x10ffff {
f = 0x10ffff f = 0x10ffff
} }
c[i] = rune(f) c[i] = rune(f)
} }
return string(c) return string(c)
} }
func genArray(n int) []any { func genArray(n int) []any {
f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2)))
if f > n { if f > n {
f = n f = n
} }
if f < 1 { if f < 1 {
f = 1 f = 1
} }
x := make([]any, f) x := make([]any, f)
for i := range x { for i := range x {
x[i] = genValue(((i+1)*n)/f - (i*n)/f) x[i] = genValue(((i+1)*n)/f - (i*n)/f)
} }
return x return x
} }
func genMap(n int) map[string]any { func genMap(n int) map[string]any {
f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2)))
if f > n { if f > n {
f = n f = n
} }
if n > 0 && f == 0 { if n > 0 && f == 0 {
f = 1 f = 1
} }
x := make(map[string]any) x := make(map[string]any)
for i := range f { for i := range f {
x[genString(10)] = genValue(((i+1)*n)/f - (i*n)/f) x[genString(10)] = genValue(((i+1)*n)/f - (i*n)/f)
} }
return x return x
} }

1144
stream.go

File diff suppressed because it is too large Load Diff

View File

@ -1,429 +1,429 @@
// Copyright 2010 The Go Authors. All rights reserved. // Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package json package json
import ( import (
"bytes" "bytes"
"errors" "errors"
"io" "io"
"log" "log"
"net" "net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
) )
// Test values for the stream test. // Test values for the stream test.
// One of each JSON kind. // One of each JSON kind.
var streamTest = []any{ var streamTest = []any{
0.1, 0.1,
"hello", "hello",
nil, nil,
true, true,
false, false,
[]any{"a", "b", "c"}, []any{"a", "b", "c"},
map[string]any{"": "Kelvin", "ß": "long s"}, map[string]any{"": "Kelvin", "ß": "long s"},
3.14, // another value to make sure something can follow map 3.14, // another value to make sure something can follow map
} }
var streamEncoded = `0.1 var streamEncoded = `0.1
"hello" "hello"
null null
true true
false false
["a","b","c"] ["a","b","c"]
{"\u00DF":"long s","\u212A":"Kelvin"} {"\u00DF":"long s","\u212A":"Kelvin"}
3.14 3.14
` `
func TestEncoder(t *testing.T) { func TestEncoder(t *testing.T) {
for i := 0; i <= len(streamTest); i++ { for i := 0; i <= len(streamTest); i++ {
var buf bytes.Buffer var buf bytes.Buffer
enc := NewEncoder(&buf) enc := NewEncoder(&buf)
// Check that enc.SetIndent("", "") turns off indentation. // Check that enc.SetIndent("", "") turns off indentation.
enc.SetIndent(">", ".") enc.SetIndent(">", ".")
enc.SetIndent("", "") enc.SetIndent("", "")
for j, v := range streamTest[0:i] { for j, v := range streamTest[0:i] {
if err := enc.Encode(v); err != nil { if err := enc.Encode(v); err != nil {
t.Fatalf("encode #%d: %v", j, err) t.Fatalf("encode #%d: %v", j, err)
} }
} }
if have, want := buf.String(), nlines(streamEncoded, i); have != want { if have, want := buf.String(), nlines(streamEncoded, i); have != want {
t.Errorf("encoding %d items: mismatch", i) t.Errorf("encoding %d items: mismatch", i)
diff(t, []byte(have), []byte(want)) diff(t, []byte(have), []byte(want))
break break
} }
} }
} }
var streamEncodedIndent = `0.1 var streamEncodedIndent = `0.1
"hello" "hello"
null null
true true
false false
[ [
>."a", >."a",
>."b", >."b",
>."c" >."c"
>] >]
{ {
>."\u00DF": "long s", >."\u00DF": "long s",
>."\u212A": "Kelvin" >."\u212A": "Kelvin"
>} >}
3.14 3.14
` `
func TestEncoderIndent(t *testing.T) { func TestEncoderIndent(t *testing.T) {
var buf bytes.Buffer var buf bytes.Buffer
enc := NewEncoder(&buf) enc := NewEncoder(&buf)
enc.SetIndent(">", ".") enc.SetIndent(">", ".")
for _, v := range streamTest { for _, v := range streamTest {
err := enc.Encode(v) err := enc.Encode(v)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
} }
if have, want := buf.String(), streamEncodedIndent; have != want { if have, want := buf.String(), streamEncodedIndent; have != want {
t.Error("indented encoding mismatch") t.Error("indented encoding mismatch")
diff(t, []byte(have), []byte(want)) diff(t, []byte(have), []byte(want))
} }
} }
func TestEncoderSetEscapeHTML(t *testing.T) { func TestEncoderSetEscapeHTML(t *testing.T) {
var c C var c C
var ct CText var ct CText
for _, tt := range []struct { for _, tt := range []struct {
name string name string
v any v any
wantEscape string wantEscape string
want string want string
}{ }{
{"c", c, `"\u003C\u0026\u003E"`, `"<&>"`}, {"c", c, `"\u003C\u0026\u003E"`, `"<&>"`},
{"ct", ct, `"\u0022\u003C\u0026\u003E\u0022"`, `"\u0022<\u0026>\u0022"`}, {"ct", ct, `"\u0022\u003C\u0026\u003E\u0022"`, `"\u0022<\u0026>\u0022"`},
{`"<&>"`, "<&>", `"\u003C\u0026\u003E"`, `"<\u0026>"`}, {`"<&>"`, "<&>", `"\u003C\u0026\u003E"`, `"<\u0026>"`},
} { } {
var buf bytes.Buffer var buf bytes.Buffer
enc := NewEncoder(&buf) enc := NewEncoder(&buf)
if err := enc.Encode(tt.v); err != nil { if err := enc.Encode(tt.v); err != nil {
t.Fatalf("Encode(%s): %s", tt.name, err) t.Fatalf("Encode(%s): %s", tt.name, err)
} }
if got := strings.TrimSpace(buf.String()); got != tt.wantEscape { if got := strings.TrimSpace(buf.String()); got != tt.wantEscape {
t.Errorf("Encode(%s) = %#q, want %#q", tt.name, got, tt.wantEscape) t.Errorf("Encode(%s) = %#q, want %#q", tt.name, got, tt.wantEscape)
} }
buf.Reset() buf.Reset()
enc.SetEscapeHTML(false) enc.SetEscapeHTML(false)
if err := enc.Encode(tt.v); err != nil { if err := enc.Encode(tt.v); err != nil {
t.Fatalf("SetEscapeHTML(false) Encode(%s): %s", tt.name, err) t.Fatalf("SetEscapeHTML(false) Encode(%s): %s", tt.name, err)
} }
if got := strings.TrimSpace(buf.String()); got != tt.want { if got := strings.TrimSpace(buf.String()); got != tt.want {
t.Errorf("SetEscapeHTML(false) Encode(%s) = %#q, want %#q", t.Errorf("SetEscapeHTML(false) Encode(%s) = %#q, want %#q",
tt.name, got, tt.want) tt.name, got, tt.want)
} }
} }
} }
func TestDecoder(t *testing.T) { func TestDecoder(t *testing.T) {
for i := 0; i <= len(streamTest); i++ { for i := 0; i <= len(streamTest); i++ {
// Use stream without newlines as input, // Use stream without newlines as input,
// just to stress the decoder even more. // just to stress the decoder even more.
// Our test input does not include back-to-back numbers. // Our test input does not include back-to-back numbers.
// Otherwise stripping the newlines would // Otherwise stripping the newlines would
// merge two adjacent JSON values. // merge two adjacent JSON values.
var buf bytes.Buffer var buf bytes.Buffer
for _, c := range nlines(streamEncoded, i) { for _, c := range nlines(streamEncoded, i) {
if c != '\n' { if c != '\n' {
buf.WriteRune(c) buf.WriteRune(c)
} }
} }
out := make([]any, i) out := make([]any, i)
dec := NewDecoder(&buf) dec := NewDecoder(&buf)
for j := range out { for j := range out {
if err := dec.Decode(&out[j]); err != nil { if err := dec.Decode(&out[j]); err != nil {
t.Fatalf("decode #%d/%d: %v", j, i, err) t.Fatalf("decode #%d/%d: %v", j, i, err)
} }
} }
if !reflect.DeepEqual(out, streamTest[0:i]) { if !reflect.DeepEqual(out, streamTest[0:i]) {
t.Errorf("decoding %d items: mismatch", i) t.Errorf("decoding %d items: mismatch", i)
for j := range out { for j := range out {
if !reflect.DeepEqual(out[j], streamTest[j]) { if !reflect.DeepEqual(out[j], streamTest[j]) {
t.Errorf("#%d: have %v want %v", j, out[j], streamTest[j]) t.Errorf("#%d: have %v want %v", j, out[j], streamTest[j])
} }
} }
break break
} }
} }
} }
func TestDecoderBuffered(t *testing.T) { func TestDecoderBuffered(t *testing.T) {
r := strings.NewReader(`{"Name": "Gopher"} extra `) r := strings.NewReader(`{"Name": "Gopher"} extra `)
var m struct { var m struct {
Name string Name string
} }
d := NewDecoder(r) d := NewDecoder(r)
err := d.Decode(&m) err := d.Decode(&m)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if m.Name != "Gopher" { if m.Name != "Gopher" {
t.Errorf("Name = %q; want Gopher", m.Name) t.Errorf("Name = %q; want Gopher", m.Name)
} }
rest, err := io.ReadAll(d.Buffered()) rest, err := io.ReadAll(d.Buffered())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if g, w := string(rest), " extra "; g != w { if g, w := string(rest), " extra "; g != w {
t.Errorf("Remaining = %q; want %q", g, w) t.Errorf("Remaining = %q; want %q", g, w)
} }
} }
func nlines(s string, n int) string { func nlines(s string, n int) string {
if n <= 0 { if n <= 0 {
return "" return ""
} }
for i, c := range s { for i, c := range s {
if c == '\n' { if c == '\n' {
if n--; n == 0 { if n--; n == 0 {
return s[0 : i+1] return s[0 : i+1]
} }
} }
} }
return s return s
} }
func TestRawMessage(t *testing.T) { func TestRawMessage(t *testing.T) {
// TODO(rsc): Should not need the * in *RawMessage // TODO(rsc): Should not need the * in *RawMessage
var data struct { var data struct {
X float64 X float64
Id *RawMessage //nolint:revive // It's "Id" in JSON and it's a test. Id *RawMessage //nolint:revive // It's "Id" in JSON and it's a test.
Y float32 Y float32
} }
const raw = `["\u0056",null]` const raw = `["\u0056",null]`
const msg = `{"X":0.1,"Id":["\u0056",null],"Y":0.2}` const msg = `{"X":0.1,"Id":["\u0056",null],"Y":0.2}`
err := Unmarshal([]byte(msg), &data) err := Unmarshal([]byte(msg), &data)
if err != nil { if err != nil {
t.Fatalf("Unmarshal: %v", err) t.Fatalf("Unmarshal: %v", err)
} }
if string([]byte(*data.Id)) != raw { if string([]byte(*data.Id)) != raw {
t.Fatalf("Raw mismatch: have %#q want %#q", []byte(*data.Id), raw) t.Fatalf("Raw mismatch: have %#q want %#q", []byte(*data.Id), raw)
} }
b, err := Marshal(&data) b, err := Marshal(&data)
if err != nil { if err != nil {
t.Fatalf("Marshal: %v", err) t.Fatalf("Marshal: %v", err)
} }
if string(b) != msg { if string(b) != msg {
t.Fatalf("Marshal: have %#q want %#q", b, msg) t.Fatalf("Marshal: have %#q want %#q", b, msg)
} }
} }
func TestNullRawMessage(t *testing.T) { func TestNullRawMessage(t *testing.T) {
// TODO(rsc): Should not need the * in *RawMessage // TODO(rsc): Should not need the * in *RawMessage
var data struct { var data struct {
X float64 X float64
Id *RawMessage //nolint:revive // It's "Id" in JSON and it's a test. Id *RawMessage //nolint:revive // It's "Id" in JSON and it's a test.
Y float32 Y float32
} }
data.Id = new(RawMessage) data.Id = new(RawMessage)
const msg = `{"X":0.1,"Id":null,"Y":0.2}` const msg = `{"X":0.1,"Id":null,"Y":0.2}`
err := Unmarshal([]byte(msg), &data) err := Unmarshal([]byte(msg), &data)
if err != nil { if err != nil {
t.Fatalf("Unmarshal: %v", err) t.Fatalf("Unmarshal: %v", err)
} }
if data.Id != nil { if data.Id != nil {
t.Fatalf("Raw mismatch: have non-nil, want nil") t.Fatalf("Raw mismatch: have non-nil, want nil")
} }
b, err := Marshal(&data) b, err := Marshal(&data)
if err != nil { if err != nil {
t.Fatalf("Marshal: %v", err) t.Fatalf("Marshal: %v", err)
} }
if string(b) != msg { if string(b) != msg {
t.Fatalf("Marshal: have %#q want %#q", b, msg) t.Fatalf("Marshal: have %#q want %#q", b, msg)
} }
} }
var blockingTests = []string{ var blockingTests = []string{
`{"x": 1}`, `{"x": 1}`,
`[1, 2, 3]`, `[1, 2, 3]`,
} }
func TestBlocking(t *testing.T) { func TestBlocking(t *testing.T) {
for _, enc := range blockingTests { for _, enc := range blockingTests {
r, w := net.Pipe() r, w := net.Pipe()
go func() { go func() {
_, err := w.Write([]byte(enc)) _, err := w.Write([]byte(enc))
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
}() }()
var val any var val any
// If Decode reads beyond what w.Write writes above, // If Decode reads beyond what w.Write writes above,
// it will block, and the test will deadlock. // it will block, and the test will deadlock.
if err := NewDecoder(r).Decode(&val); err != nil { if err := NewDecoder(r).Decode(&val); err != nil {
t.Errorf("decoding %s: %v", enc, err) t.Errorf("decoding %s: %v", enc, err)
} }
r.Close() r.Close()
w.Close() w.Close()
} }
} }
func BenchmarkEncoderEncode(b *testing.B) { func BenchmarkEncoderEncode(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
type T struct { type T struct {
X, Y string X, Y string
} }
v := &T{"foo", "bar"} v := &T{"foo", "bar"}
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
if err := NewEncoder(io.Discard).Encode(v); err != nil { if err := NewEncoder(io.Discard).Encode(v); err != nil {
b.Fatal(err) b.Fatal(err)
} }
} }
}) })
} }
type tokenStreamCase struct { type tokenStreamCase struct {
json string json string
expTokens []any expTokens []any
} }
type decodeThis struct { type decodeThis struct {
v any v any
} }
var tokenStreamCases = []tokenStreamCase{ var tokenStreamCases = []tokenStreamCase{
// streaming token cases // streaming token cases
{json: `10`, expTokens: []any{float64(10)}}, {json: `10`, expTokens: []any{float64(10)}},
{json: ` [10] `, expTokens: []any{ {json: ` [10] `, expTokens: []any{
Delim('['), float64(10), Delim(']')}}, Delim('['), float64(10), Delim(']')}},
{json: ` [false,10,"b"] `, expTokens: []any{ {json: ` [false,10,"b"] `, expTokens: []any{
Delim('['), false, float64(10), "b", Delim(']')}}, Delim('['), false, float64(10), "b", Delim(']')}},
{json: `{ "a": 1 }`, expTokens: []any{ {json: `{ "a": 1 }`, expTokens: []any{
Delim('{'), "a", float64(1), Delim('}')}}, Delim('{'), "a", float64(1), Delim('}')}},
{json: `{"a": 1, "b":"3"}`, expTokens: []any{ {json: `{"a": 1, "b":"3"}`, expTokens: []any{
Delim('{'), "a", float64(1), "b", "3", Delim('}')}}, Delim('{'), "a", float64(1), "b", "3", Delim('}')}},
{json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{ {json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{
Delim('['), Delim('['),
Delim('{'), "a", float64(1), Delim('}'), Delim('{'), "a", float64(1), Delim('}'),
Delim('{'), "a", float64(2), Delim('}'), Delim('{'), "a", float64(2), Delim('}'),
Delim(']')}}, Delim(']')}},
{json: `{"obj": {"a": 1}}`, expTokens: []any{ {json: `{"obj": {"a": 1}}`, expTokens: []any{
Delim('{'), "obj", Delim('{'), "a", float64(1), Delim('}'), Delim('{'), "obj", Delim('{'), "a", float64(1), Delim('}'),
Delim('}')}}, Delim('}')}},
{json: `{"obj": [{"a": 1}]}`, expTokens: []any{ {json: `{"obj": [{"a": 1}]}`, expTokens: []any{
Delim('{'), "obj", Delim('['), Delim('{'), "obj", Delim('['),
Delim('{'), "a", float64(1), Delim('}'), Delim('{'), "a", float64(1), Delim('}'),
Delim(']'), Delim('}')}}, Delim(']'), Delim('}')}},
// streaming tokens with intermittent Decode() // streaming tokens with intermittent Decode()
{json: `{ "a": 1 }`, expTokens: []any{ {json: `{ "a": 1 }`, expTokens: []any{
Delim('{'), "a", Delim('{'), "a",
decodeThis{float64(1)}, decodeThis{float64(1)},
Delim('}')}}, Delim('}')}},
{json: ` [ { "a" : 1 } ] `, expTokens: []any{ {json: ` [ { "a" : 1 } ] `, expTokens: []any{
Delim('['), Delim('['),
decodeThis{map[string]any{"a": float64(1)}}, decodeThis{map[string]any{"a": float64(1)}},
Delim(']')}}, Delim(']')}},
{json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{ {json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{
Delim('['), Delim('['),
decodeThis{map[string]any{"a": float64(1)}}, decodeThis{map[string]any{"a": float64(1)}},
decodeThis{map[string]any{"a": float64(2)}}, decodeThis{map[string]any{"a": float64(2)}},
Delim(']')}}, Delim(']')}},
{json: `{ "obj" : [ { "a" : 1 } ] }`, expTokens: []any{ {json: `{ "obj" : [ { "a" : 1 } ] }`, expTokens: []any{
Delim('{'), "obj", Delim('['), Delim('{'), "obj", Delim('['),
decodeThis{map[string]any{"a": float64(1)}}, decodeThis{map[string]any{"a": float64(1)}},
Delim(']'), Delim('}')}}, Delim(']'), Delim('}')}},
{json: `{"obj": {"a": 1}}`, expTokens: []any{ {json: `{"obj": {"a": 1}}`, expTokens: []any{
Delim('{'), "obj", Delim('{'), "obj",
decodeThis{map[string]any{"a": float64(1)}}, decodeThis{map[string]any{"a": float64(1)}},
Delim('}')}}, Delim('}')}},
{json: `{"obj": [{"a": 1}]}`, expTokens: []any{ {json: `{"obj": [{"a": 1}]}`, expTokens: []any{
Delim('{'), "obj", Delim('{'), "obj",
decodeThis{[]any{ decodeThis{[]any{
map[string]any{"a": float64(1)}, map[string]any{"a": float64(1)},
}}, }},
Delim('}')}}, Delim('}')}},
{json: ` [{"a": 1} {"a": 2}] `, expTokens: []any{ {json: ` [{"a": 1} {"a": 2}] `, expTokens: []any{
Delim('['), Delim('['),
decodeThis{map[string]any{"a": float64(1)}}, decodeThis{map[string]any{"a": float64(1)}},
decodeThis{&SyntaxError{"expected comma after array element", 0}}, decodeThis{&SyntaxError{"expected comma after array element", 0}},
}}, }},
{json: `{ "a" 1 }`, expTokens: []any{ {json: `{ "a" 1 }`, expTokens: []any{
Delim('{'), "a", Delim('{'), "a",
decodeThis{&SyntaxError{"expected colon after object key", 0}}, decodeThis{&SyntaxError{"expected colon after object key", 0}},
}}, }},
} }
func TestDecodeInStream(t *testing.T) { func TestDecodeInStream(t *testing.T) {
for ci, tcase := range tokenStreamCases { for ci, tcase := range tokenStreamCases {
dec := NewDecoder(strings.NewReader(tcase.json)) dec := NewDecoder(strings.NewReader(tcase.json))
for i, etk := range tcase.expTokens { for i, etk := range tcase.expTokens {
var tk any var tk any
var err error var err error
if dt, ok := etk.(decodeThis); ok { if dt, ok := etk.(decodeThis); ok {
etk = dt.v etk = dt.v
err = dec.Decode(&tk) err = dec.Decode(&tk)
} else { } else {
tk, err = dec.Token() tk, err = dec.Token()
} }
if experr, ok := etk.(error); ok { if experr, ok := etk.(error); ok {
if err == nil || err.Error() != experr.Error() { if err == nil || err.Error() != experr.Error() {
t.Errorf("case %v: Expected error %v in %q, but was %v", ci, experr, tcase.json, err) t.Errorf("case %v: Expected error %v in %q, but was %v", ci, experr, tcase.json, err)
} }
break break
} else if errors.Is(err, io.EOF) { } else if errors.Is(err, io.EOF) {
t.Errorf("case %v: Unexpected EOF in %q", ci, tcase.json) t.Errorf("case %v: Unexpected EOF in %q", ci, tcase.json)
break break
} else if err != nil { } else if err != nil {
t.Errorf("case %v: Unexpected error '%v' in %q", ci, err, tcase.json) t.Errorf("case %v: Unexpected error '%v' in %q", ci, err, tcase.json)
break break
} }
if !reflect.DeepEqual(tk, etk) { 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) t.Errorf(`case %v: %q @ %v expected %T(%v) was %T(%v)`, ci, tcase.json, i, etk, etk, tk, tk)
break break
} }
} }
} }
} }
// Test from golang.org/issue/11893. // Test from golang.org/issue/11893.
func TestHTTPDecoding(t *testing.T) { func TestHTTPDecoding(t *testing.T) {
const raw = `{ "foo": "bar" }` const raw = `{ "foo": "bar" }`
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte(raw)) _, err := w.Write([]byte(raw))
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
})) }))
defer ts.Close() defer ts.Close()
res, err := http.Get(ts.URL) res, err := http.Get(ts.URL)
if err != nil { if err != nil {
log.Fatalf("GET failed: %v", err) log.Fatalf("GET failed: %v", err)
} }
defer res.Body.Close() defer res.Body.Close()
foo := struct { foo := struct {
Foo string Foo string
}{} }{}
d := NewDecoder(res.Body) d := NewDecoder(res.Body)
err = d.Decode(&foo) err = d.Decode(&foo)
if err != nil { if err != nil {
t.Fatalf("Decode: %v", err) t.Fatalf("Decode: %v", err)
} }
if foo.Foo != "bar" { if foo.Foo != "bar" {
t.Errorf("decoded %q; want \"bar\"", foo.Foo) t.Errorf("decoded %q; want \"bar\"", foo.Foo)
} }
// make sure we get the EOF the second time // make sure we get the EOF the second time
err = d.Decode(&foo) err = d.Decode(&foo)
if !errors.Is(err, io.EOF) { if !errors.Is(err, io.EOF) {
t.Errorf("err = %v; want io.EOF", err) t.Errorf("err = %v; want io.EOF", err)
} }
} }

436
tables.go
View File

@ -1,218 +1,218 @@
// Copyright 2016 The Go Authors. All rights reserved. // Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package json package json
import "unicode/utf8" import "unicode/utf8"
// safeSet holds the value true if the ASCII character with the given array // safeSet holds the value true if the ASCII character with the given array
// position can be represented inside a JSON string without any further // position can be represented inside a JSON string without any further
// escaping. // escaping.
// //
// All values are true except for the ASCII control characters (0-31), the // All values are true except for the ASCII control characters (0-31), the
// double quote ("), and the backslash character ("\"). // double quote ("), and the backslash character ("\").
var safeSet = [utf8.RuneSelf]bool{ var safeSet = [utf8.RuneSelf]bool{
' ': true, ' ': true,
'!': true, '!': true,
'"': false, '"': false,
'#': true, '#': true,
'$': true, '$': true,
'%': true, '%': true,
'&': false, '&': false,
'\'': false, '\'': false,
'(': true, '(': true,
')': true, ')': true,
'*': true, '*': true,
'+': false, '+': false,
',': true, ',': true,
'-': true, '-': true,
'.': true, '.': true,
'/': true, '/': true,
'0': true, '0': true,
'1': true, '1': true,
'2': true, '2': true,
'3': true, '3': true,
'4': true, '4': true,
'5': true, '5': true,
'6': true, '6': true,
'7': true, '7': true,
'8': true, '8': true,
'9': true, '9': true,
':': true, ':': true,
';': true, ';': true,
'<': true, '<': true,
'=': true, '=': true,
'>': true, '>': true,
'?': true, '?': true,
'@': true, '@': true,
'A': true, 'A': true,
'B': true, 'B': true,
'C': true, 'C': true,
'D': true, 'D': true,
'E': true, 'E': true,
'F': true, 'F': true,
'G': true, 'G': true,
'H': true, 'H': true,
'I': true, 'I': true,
'J': true, 'J': true,
'K': true, 'K': true,
'L': true, 'L': true,
'M': true, 'M': true,
'N': true, 'N': true,
'O': true, 'O': true,
'P': true, 'P': true,
'Q': true, 'Q': true,
'R': true, 'R': true,
'S': true, 'S': true,
'T': true, 'T': true,
'U': true, 'U': true,
'V': true, 'V': true,
'W': true, 'W': true,
'X': true, 'X': true,
'Y': true, 'Y': true,
'Z': true, 'Z': true,
'[': true, '[': true,
'\\': false, '\\': false,
']': true, ']': true,
'^': true, '^': true,
'_': true, '_': true,
'`': false, '`': false,
'a': true, 'a': true,
'b': true, 'b': true,
'c': true, 'c': true,
'd': true, 'd': true,
'e': true, 'e': true,
'f': true, 'f': true,
'g': true, 'g': true,
'h': true, 'h': true,
'i': true, 'i': true,
'j': true, 'j': true,
'k': true, 'k': true,
'l': true, 'l': true,
'm': true, 'm': true,
'n': true, 'n': true,
'o': true, 'o': true,
'p': true, 'p': true,
'q': true, 'q': true,
'r': true, 'r': true,
's': true, 's': true,
't': true, 't': true,
'u': true, 'u': true,
'v': true, 'v': true,
'w': true, 'w': true,
'x': true, 'x': true,
'y': true, 'y': true,
'z': true, 'z': true,
'{': true, '{': true,
'|': true, '|': true,
'}': true, '}': true,
'~': true, '~': true,
'\u007f': false, '\u007f': false,
} }
// htmlSafeSet holds the value true if the ASCII character with the given // htmlSafeSet holds the value true if the ASCII character with the given
// array position can be safely represented inside a JSON string, embedded // array position can be safely represented inside a JSON string, embedded
// inside of HTML <script> tags, without any additional escaping. // inside of HTML <script> tags, without any additional escaping.
// //
// All values are true except for the ASCII control characters (0-31), the // All values are true except for the ASCII control characters (0-31), the
// double quote ("), the backslash character ("\"), HTML opening and closing // double quote ("), the backslash character ("\"), HTML opening and closing
// tags ("<" and ">"), and the ampersand ("&"). // tags ("<" and ">"), and the ampersand ("&").
var htmlSafeSet = [utf8.RuneSelf]bool{ var htmlSafeSet = [utf8.RuneSelf]bool{
' ': true, ' ': true,
'!': true, '!': true,
'"': false, '"': false,
'#': true, '#': true,
'$': true, '$': true,
'%': true, '%': true,
'&': false, '&': false,
'\'': false, '\'': false,
'(': true, '(': true,
')': true, ')': true,
'*': true, '*': true,
'+': false, '+': false,
',': true, ',': true,
'-': true, '-': true,
'.': true, '.': true,
'/': true, '/': true,
'0': true, '0': true,
'1': true, '1': true,
'2': true, '2': true,
'3': true, '3': true,
'4': true, '4': true,
'5': true, '5': true,
'6': true, '6': true,
'7': true, '7': true,
'8': true, '8': true,
'9': true, '9': true,
':': true, ':': true,
';': true, ';': true,
'<': false, '<': false,
'=': true, '=': true,
'>': false, '>': false,
'?': true, '?': true,
'@': true, '@': true,
'A': true, 'A': true,
'B': true, 'B': true,
'C': true, 'C': true,
'D': true, 'D': true,
'E': true, 'E': true,
'F': true, 'F': true,
'G': true, 'G': true,
'H': true, 'H': true,
'I': true, 'I': true,
'J': true, 'J': true,
'K': true, 'K': true,
'L': true, 'L': true,
'M': true, 'M': true,
'N': true, 'N': true,
'O': true, 'O': true,
'P': true, 'P': true,
'Q': true, 'Q': true,
'R': true, 'R': true,
'S': true, 'S': true,
'T': true, 'T': true,
'U': true, 'U': true,
'V': true, 'V': true,
'W': true, 'W': true,
'X': true, 'X': true,
'Y': true, 'Y': true,
'Z': true, 'Z': true,
'[': true, '[': true,
'\\': false, '\\': false,
']': true, ']': true,
'^': true, '^': true,
'_': true, '_': true,
'`': false, '`': false,
'a': true, 'a': true,
'b': true, 'b': true,
'c': true, 'c': true,
'd': true, 'd': true,
'e': true, 'e': true,
'f': true, 'f': true,
'g': true, 'g': true,
'h': true, 'h': true,
'i': true, 'i': true,
'j': true, 'j': true,
'k': true, 'k': true,
'l': true, 'l': true,
'm': true, 'm': true,
'n': true, 'n': true,
'o': true, 'o': true,
'p': true, 'p': true,
'q': true, 'q': true,
'r': true, 'r': true,
's': true, 's': true,
't': true, 't': true,
'u': true, 'u': true,
'v': true, 'v': true,
'w': true, 'w': true,
'x': true, 'x': true,
'y': true, 'y': true,
'z': true, 'z': true,
'{': true, '{': true,
'|': true, '|': true,
'}': true, '}': true,
'~': true, '~': true,
'\u007f': false, '\u007f': false,
} }

View File

@ -1,120 +1,120 @@
// Copyright 2011 The Go Authors. All rights reserved. // Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package json package json
import ( import (
"testing" "testing"
) )
type basicLatin2xTag struct { type basicLatin2xTag struct {
V string `json:"$%-/"` V string `json:"$%-/"`
} }
type basicLatin3xTag struct { type basicLatin3xTag struct {
V string `json:"0123456789"` V string `json:"0123456789"`
} }
type basicLatin4xTag struct { type basicLatin4xTag struct {
V string `json:"ABCDEFGHIJKLMO"` V string `json:"ABCDEFGHIJKLMO"`
} }
type basicLatin5xTag struct { type basicLatin5xTag struct {
V string `json:"PQRSTUVWXYZ_"` V string `json:"PQRSTUVWXYZ_"`
} }
type basicLatin6xTag struct { type basicLatin6xTag struct {
V string `json:"abcdefghijklmno"` V string `json:"abcdefghijklmno"`
} }
type basicLatin7xTag struct { type basicLatin7xTag struct {
V string `json:"pqrstuvwxyz"` V string `json:"pqrstuvwxyz"`
} }
type miscPlaneTag struct { type miscPlaneTag struct {
V string `json:"色は匂へど"` V string `json:"色は匂へど"`
} }
type percentSlashTag struct { type percentSlashTag struct {
V string `json:"text/html%"` // https://golang.org/issue/2718 V string `json:"text/html%"` // https://golang.org/issue/2718
} }
type punctuationTag struct { type punctuationTag struct {
V string `json:"!#$%&()*+-./:<=>?@[]^_{|}~"` // https://golang.org/issue/3546 V string `json:"!#$%&()*+-./:<=>?@[]^_{|}~"` // https://golang.org/issue/3546
} }
type dashTag struct { type dashTag struct {
V string `json:"-,"` V string `json:"-,"`
} }
type emptyTag struct { type emptyTag struct {
W string W string
} }
type misnamedTag struct { type misnamedTag struct {
X string `jsom:"Misnamed"` X string `jsom:"Misnamed"`
} }
type badFormatTag struct { type badFormatTag struct {
Y string `:"BadFormat"` //nolint:govet // It's intentionally wrong. Y string `:"BadFormat"` //nolint:govet // It's intentionally wrong.
} }
type badCodeTag struct { type badCodeTag struct {
Z string `json:" !\"#&'()*+,."` //nolint:staticcheck // It's intentionally wrong. Z string `json:" !\"#&'()*+,."` //nolint:staticcheck // It's intentionally wrong.
} }
type spaceTag struct { type spaceTag struct {
Q string `json:"With space"` Q string `json:"With space"`
} }
type unicodeTag struct { type unicodeTag struct {
W string `json:"Ελλάδα"` W string `json:"Ελλάδα"`
} }
var structTagObjectKeyTests = []struct { var structTagObjectKeyTests = []struct {
raw any raw any
value string value string
key string key string
}{ }{
{basicLatin2xTag{"2x"}, "2x", "$%-/"}, {basicLatin2xTag{"2x"}, "2x", "$%-/"},
{basicLatin3xTag{"3x"}, "3x", "0123456789"}, {basicLatin3xTag{"3x"}, "3x", "0123456789"},
{basicLatin4xTag{"4x"}, "4x", "ABCDEFGHIJKLMO"}, {basicLatin4xTag{"4x"}, "4x", "ABCDEFGHIJKLMO"},
{basicLatin5xTag{"5x"}, "5x", "PQRSTUVWXYZ_"}, {basicLatin5xTag{"5x"}, "5x", "PQRSTUVWXYZ_"},
{basicLatin6xTag{"6x"}, "6x", "abcdefghijklmno"}, {basicLatin6xTag{"6x"}, "6x", "abcdefghijklmno"},
{basicLatin7xTag{"7x"}, "7x", "pqrstuvwxyz"}, {basicLatin7xTag{"7x"}, "7x", "pqrstuvwxyz"},
{miscPlaneTag{"いろはにほへと"}, "いろはにほへと", "色は匂へど"}, {miscPlaneTag{"いろはにほへと"}, "いろはにほへと", "色は匂へど"},
{dashTag{"foo"}, "foo", "-"}, {dashTag{"foo"}, "foo", "-"},
{emptyTag{"Pour Moi"}, "Pour Moi", "W"}, {emptyTag{"Pour Moi"}, "Pour Moi", "W"},
{misnamedTag{"Animal Kingdom"}, "Animal Kingdom", "X"}, {misnamedTag{"Animal Kingdom"}, "Animal Kingdom", "X"},
{badFormatTag{"Orfevre"}, "Orfevre", "Y"}, {badFormatTag{"Orfevre"}, "Orfevre", "Y"},
{badCodeTag{"Reliable Man"}, "Reliable Man", "Z"}, {badCodeTag{"Reliable Man"}, "Reliable Man", "Z"},
{percentSlashTag{"brut"}, "brut", "text/html%"}, {percentSlashTag{"brut"}, "brut", "text/html%"},
{punctuationTag{"Union Rags"}, "Union Rags", "!#$%&()*+-./:<=>?@[]^_{|}~"}, {punctuationTag{"Union Rags"}, "Union Rags", "!#$%&()*+-./:<=>?@[]^_{|}~"},
{spaceTag{"Perreddu"}, "Perreddu", "With space"}, {spaceTag{"Perreddu"}, "Perreddu", "With space"},
{unicodeTag{"Loukanikos"}, "Loukanikos", "Ελλάδα"}, {unicodeTag{"Loukanikos"}, "Loukanikos", "Ελλάδα"},
} }
func TestStructTagObjectKey(t *testing.T) { func TestStructTagObjectKey(t *testing.T) {
for _, tt := range structTagObjectKeyTests { for _, tt := range structTagObjectKeyTests {
b, err := Marshal(tt.raw) b, err := Marshal(tt.raw)
if err != nil { if err != nil {
t.Fatalf("Marshal(%#q) failed: %v", tt.raw, err) t.Fatalf("Marshal(%#q) failed: %v", tt.raw, err)
} }
var f any var f any
err = Unmarshal(b, &f) err = Unmarshal(b, &f)
if err != nil { if err != nil {
t.Fatalf("Unmarshal(%#q) failed: %v", b, err) t.Fatalf("Unmarshal(%#q) failed: %v", b, err)
} }
for i, v := range f.(map[string]any) { for i, v := range f.(map[string]any) {
switch i { switch i {
case tt.key: case tt.key:
if s, ok := v.(string); !ok || s != tt.value { if s, ok := v.(string); !ok || s != tt.value {
t.Fatalf("Unexpected value: %#q, want %v", s, tt.value) t.Fatalf("Unexpected value: %#q, want %v", s, tt.value)
} }
default: default:
t.Fatalf("Unexpected key: %#q, from %#q", i, b) t.Fatalf("Unexpected key: %#q, from %#q", i, b)
} }
} }
} }
} }

74
tags.go
View File

@ -1,37 +1,37 @@
// Copyright 2011 The Go Authors. All rights reserved. // Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package json package json
import ( import (
"strings" "strings"
) )
// tagOptions is the string following a comma in a struct field's "json" // 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. // tag, or the empty string. It does not include the leading comma.
type tagOptions string type tagOptions string
// parseTag splits a struct field's json tag into its name and // parseTag splits a struct field's json tag into its name and
// comma-separated options. // comma-separated options.
func parseTag(tag string) (string, tagOptions) { func parseTag(tag string) (string, tagOptions) {
if idx := strings.Index(tag, ","); idx != -1 { if idx := strings.Index(tag, ","); idx != -1 {
return tag[:idx], tagOptions(tag[idx+1:]) return tag[:idx], tagOptions(tag[idx+1:])
} }
return tag, tagOptions("") return tag, tagOptions("")
} }
// Contains reports whether a comma-separated list of options // Contains reports whether a comma-separated list of options
// contains a particular substr flag. substr must be surrounded by a // contains a particular substr flag. substr must be surrounded by a
// string boundary or commas. // string boundary or commas.
func (o tagOptions) Contains(optionName string) bool { func (o tagOptions) Contains(optionName string) bool {
if len(o) == 0 { if len(o) == 0 {
return false return false
} }
for s := range strings.FieldsFuncSeq(string(o), func(c rune) bool { return c == ',' }) { for s := range strings.FieldsFuncSeq(string(o), func(c rune) bool { return c == ',' }) {
if s == optionName { if s == optionName {
return true return true
} }
} }
return false return false
} }

View File

@ -1,28 +1,28 @@
// Copyright 2011 The Go Authors. All rights reserved. // Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package json package json
import ( import (
"testing" "testing"
) )
func TestTagParsing(t *testing.T) { func TestTagParsing(t *testing.T) {
name, opts := parseTag("field,foobar,foo") name, opts := parseTag("field,foobar,foo")
if name != "field" { if name != "field" {
t.Fatalf("name = %q, want field", name) t.Fatalf("name = %q, want field", name)
} }
for _, tt := range []struct { for _, tt := range []struct {
opt string opt string
want bool want bool
}{ }{
{"foobar", true}, {"foobar", true},
{"foo", true}, {"foo", true},
{"bar", false}, {"bar", false},
} { } {
if opts.Contains(tt.opt) != tt.want { if opts.Contains(tt.opt) != tt.want {
t.Errorf("Contains(%q) = %v", tt.opt, !tt.want) t.Errorf("Contains(%q) = %v", tt.opt, !tt.want)
} }
} }
} }