(experimental) upgraded to encoding/json/v2

This commit is contained in:
Gani Georgiev committed 2026-08-18 18:16:22 +03:00
1 parent 4d4275c9aa
commit 97dd775455
125 files changed
+433 -364

No files matched your search

+1 -1
View File
@@ -48,7 +48,7 @@ func TestCreateSuccess(t *testing.T) {
t.Fatalf("Expected zip with name %q, got %q", zipName, name)
}
expectedSize := int64(544)
expectedSize := int64(532)
if size := info.Size(); size != expectedSize {
t.Fatalf("Expected zip with size %d, got %d", expectedSize, size)
}
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"errors"
"fmt"
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"errors"
"net/http"
+3 -3
View File
@@ -3,7 +3,7 @@ package auth
import (
"bytes"
"context"
"encoding/json"
"encoding/json/v2"
"testing"
"golang.org/x/oauth2"
@@ -215,12 +215,12 @@ func TestExtra(t *testing.T) {
after := b.Extra()
rawExtra, err := json.Marshal(extra)
rawExtra, err := json.Marshal(extra, json.Deterministic(true))
if err != nil {
t.Fatal(err)
}
rawAfter, err := json.Marshal(after)
rawAfter, err := json.Marshal(after, json.Deterministic(true))
if err != nil {
t.Fatal(err)
}
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"errors"
"io"
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"fmt"
"github.com/pocketbase/pocketbase/tools/types"
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"fmt"
"github.com/pocketbase/pocketbase/tools/types"
+8 -6
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"github.com/pocketbase/pocketbase/tools/types"
"golang.org/x/oauth2"
@@ -53,12 +53,14 @@ func (p *Facebook) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
}
extracted := struct {
Id string
Name string
Email string
Id string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Picture struct {
Data struct{ Url string }
}
Data struct {
Url string `json:"url"`
} `json:"data"`
} `json:"picture"`
}{}
if err := json.Unmarshal(data, &extracted); err != nil {
return nil, err
+4 -4
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"errors"
"fmt"
"io"
@@ -119,9 +119,9 @@ func (p *Gitea) fetchVerifiedPrimaryEmail(token *oauth2.Token) (string, error) {
}
emails := []struct {
Email string
Verified bool
Primary bool
Email string `json:"email"`
Verified bool `json:"verified"`
Primary bool `json:"primary"`
}{}
if err := json.Unmarshal(content, &emails); err != nil {
return "", err
+4 -4
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"io"
"strconv"
@@ -120,9 +120,9 @@ func (p *Gitee) fetchPrimaryEmail(token *oauth2.Token) (string, error) {
}
emails := []struct {
Email string
State string
Scope []string
Email string `json:"email"`
State string `json:"state"`
Scope []string `json:"scope"`
}{}
if err := json.Unmarshal(content, &emails); err != nil {
// ignore unmarshal error in case "Keep my email address private"
+4 -4
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"io"
"strconv"
@@ -116,9 +116,9 @@ func (p *Github) fetchVerifiedPrimaryEmail(token *oauth2.Token) (string, error)
}
emails := []struct {
Email string
Verified bool
Primary bool
Email string `json:"email"`
Verified bool `json:"verified"`
Primary bool `json:"primary"`
}{}
if err := json.Unmarshal(content, &emails); err != nil {
return "", err
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"strconv"
"time"
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"github.com/pocketbase/pocketbase/tools/types"
"golang.org/x/oauth2"
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"github.com/pocketbase/pocketbase/tools/types"
"golang.org/x/oauth2"
+2 -2
View File
@@ -7,7 +7,7 @@ import (
"crypto/ed25519"
"crypto/rsa"
"encoding/base64"
"encoding/json"
"encoding/json/v2"
"errors"
"fmt"
"io"
@@ -105,7 +105,7 @@ func Fetch(ctx context.Context, jwksURL string, kid string) (*JWK, error) {
}
jwks := struct {
Keys []*JWK
Keys []*JWK `json:"keys"`
}{}
err = json.Unmarshal(rawBody, &jwks)
+2 -2
View File
@@ -7,7 +7,7 @@ import (
"crypto/rand"
"crypto/rsa"
"encoding/base64"
"encoding/json"
"encoding/json/v2"
"fmt"
"math/big"
"net/http"
@@ -252,7 +252,7 @@ func TestValidateTokenSignature(t *testing.T) {
}
server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
_ = json.NewEncoder(res).Encode(map[string]any{"keys": []*jwk.JWK{
_ = json.MarshalWrite(res, map[string]any{"keys": []*jwk.JWK{
{
Kid: "key1",
Kty: "OKP",
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"strconv"
"github.com/pocketbase/pocketbase/tools/types"
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"github.com/pocketbase/pocketbase/tools/types"
"golang.org/x/oauth2"
+1 -2
View File
@@ -3,7 +3,7 @@ package auth
import (
"bytes"
"context"
"encoding/json"
"encoding/json/v2"
"errors"
"net/http"
@@ -68,7 +68,6 @@ func (p *Linear) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
} `json:"viewer"`
} `json:"data"`
}{}
if err := json.Unmarshal(data, &extracted); err != nil {
return nil, err
}
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"github.com/pocketbase/pocketbase/tools/types"
"golang.org/x/oauth2"
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"errors"
"strings"
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"errors"
"slices"
+1 -1
View File
@@ -3,7 +3,7 @@ package auth
import (
"bytes"
"context"
"encoding/json"
"encoding/json/v2"
"errors"
"net/http"
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"net/http"
"github.com/pocketbase/pocketbase/tools/types"
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"errors"
"fmt"
"os"
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"github.com/pocketbase/pocketbase/tools/types"
"golang.org/x/oauth2"
+2 -2
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"errors"
"github.com/pocketbase/pocketbase/tools/types"
@@ -62,7 +62,7 @@ func (p *Planningcenter) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
// don't map the email because users can have multiple assigned
// and it's not clear if they are verified
}
}
} `json:"data"`
}{}
if err := json.Unmarshal(data, &extracted); err != nil {
return nil, err
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"github.com/pocketbase/pocketbase/tools/types"
"golang.org/x/oauth2"
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"strconv"
"github.com/pocketbase/pocketbase/tools/types"
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"net/http"
"github.com/pocketbase/pocketbase/tools/types"
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"errors"
"net/http"
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"github.com/pocketbase/pocketbase/tools/types"
"golang.org/x/oauth2"
+1 -2
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"errors"
"fmt"
"strconv"
@@ -69,7 +69,6 @@ func (p *VK) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
AvatarURL string `json:"photo_max"`
} `json:"response"`
}{}
if err := json.Unmarshal(data, &extracted); err != nil {
return nil, err
}
+1 -2
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"github.com/pocketbase/pocketbase/tools/types"
"golang.org/x/oauth2"
@@ -62,7 +62,6 @@ func (p *Wakatime) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
IsEmailConfirmed bool `json:"is_email_confirmed"`
} `json:"data"`
}{}
if err := json.Unmarshal(data, &extracted); err != nil {
return nil, err
}
+1 -1
View File
@@ -2,7 +2,7 @@ package auth
import (
"context"
"encoding/json"
"encoding/json/v2"
"github.com/pocketbase/pocketbase/tools/types"
"golang.org/x/oauth2"
+2 -2
View File
@@ -1,7 +1,7 @@
package cron
import (
"encoding/json"
"encoding/json/v2"
"slices"
"sync"
"testing"
@@ -128,7 +128,7 @@ func TestCronAddAndRemove(t *testing.T) {
"test5": `{"minutes":{"1":{}},"hours":{"2":{}},"days":{"3":{}},"months":{"4":{}},"daysOfWeek":{"5":{}}}`,
}
for k, v := range expectedSchedules {
raw, err := json.Marshal(indexedJobs[k].schedule)
raw, err := json.Marshal(indexedJobs[k].schedule, json.Deterministic(true))
if err != nil {
t.Fatal(err)
}
+1 -1
View File
@@ -1,6 +1,6 @@
package cron
import "encoding/json"
import "encoding/json/v2"
// Job defines a single registered cron job.
type Job struct {
+2 -2
View File
@@ -1,7 +1,7 @@
package cron
import (
"encoding/json"
"encoding/json/v2"
"testing"
)
@@ -59,7 +59,7 @@ func TestJobMarshalJSON(t *testing.T) {
j := Job{id: "test_id", schedule: s}
raw, err := json.Marshal(j)
raw, err := json.Marshal(j, json.Deterministic(true))
if err != nil {
t.Fatal(err)
}
+2 -2
View File
@@ -1,7 +1,7 @@
package cron_test
import (
"encoding/json"
"encoding/json/v2"
"fmt"
"testing"
"time"
@@ -265,7 +265,7 @@ func TestNewSchedule(t *testing.T) {
return
}
encoded, err := json.Marshal(schedule)
encoded, err := json.Marshal(schedule, json.Deterministic(true))
if err != nil {
t.Fatalf("Failed to marshalize the result schedule: %v", err)
}
+1 -1
View File
@@ -2,7 +2,7 @@ package dbutils_test
import (
"bytes"
"encoding/json"
"encoding/json/v2"
"fmt"
"strings"
"testing"
+2 -2
View File
@@ -64,8 +64,8 @@ func TestNewFileFromPath(t *testing.T) {
if match, err := regexp.Match(normalizedNamePattern, []byte(f.Name)); !match {
t.Fatalf("Expected Name to match %v, got %q (%v)", normalizedNamePattern, f.Name, err)
}
if f.Size != 73 {
t.Fatalf("Expected Size %v, got %v", 73, f.Size)
if f.Size != 77 {
t.Fatalf("Expected Size %v, got %v", 77, f.Size)
}
if _, ok := f.Reader.(*filesystem.PathReader); !ok {
t.Fatalf("Expected Reader to be PathReader, got %v", f.Reader)
+5 -5
View File
@@ -444,7 +444,7 @@ func TestFilesystemServe(t *testing.T) {
map[string]string{
"Content-Disposition": `inline; filename="test_name.png"`,
"Content-Type": "image/png",
"Content-Length": "73",
"Content-Length": "77",
"Content-Security-Policy": csp,
"Cache-Control": cacheControl,
},
@@ -459,7 +459,7 @@ func TestFilesystemServe(t *testing.T) {
map[string]string{
"Content-Disposition": `attachment; filename="test_name_download.png"`,
"Content-Type": "image/png",
"Content-Length": "73",
"Content-Length": "77",
"Content-Security-Policy": csp,
"Cache-Control": cacheControl,
},
@@ -792,8 +792,8 @@ func TestFilesystemCopy(t *testing.T) {
}
defer f.Close()
if f.Size() != 73 {
t.Fatalf("Expected file size %d, got %d", 73, f.Size())
if f.Size() != 77 {
t.Fatalf("Expected file size %d, got %d", 77, f.Size())
}
}
@@ -895,7 +895,7 @@ func TestFilesystemServeSingleRange(t *testing.T) {
t.Fatalf("Expected StatusCode %d, got %d", http.StatusPartialContent, result.StatusCode)
}
expectedRange := "bytes 0-20/73"
expectedRange := "bytes 0-20/77"
if cr := result.Header.Get("Content-Range"); cr != expectedRange {
t.Fatalf("Expected Content-Range %q, got %q", expectedRange, cr)
}
+6 -3
View File
@@ -1,7 +1,7 @@
package fileblob
import (
"encoding/json"
"encoding/json/v2"
"fmt"
"os"
)
@@ -50,7 +50,8 @@ func setAttrs(path string, xa xattrs) error {
return err
}
if err := json.NewEncoder(f).Encode(xa); err != nil {
err = json.MarshalWrite(f, xa)
if err != nil {
f.Close()
os.Remove(f.Name())
return err
@@ -75,7 +76,9 @@ func getAttrs(path string) (xattrs, error) {
}
xa := new(xattrs)
if err := json.NewDecoder(f).Decode(xa); err != nil {
err = json.UnmarshalRead(f, xa)
if err != nil {
f.Close()
return xattrs{}, err
}
@@ -1,7 +1,7 @@
package s3_test
import (
"encoding/json"
"encoding/json/v2"
"encoding/xml"
"testing"
@@ -29,7 +29,7 @@ func TestResponseErrorSerialization(t *testing.T) {
t.Fatal(err)
}
jsonRaw, err := json.Marshal(respErr)
jsonRaw, err := json.Marshal(respErr, json.Deterministic(true))
if err != nil {
t.Fatal(err)
}
@@ -2,7 +2,7 @@ package s3_test
import (
"context"
"encoding/json"
"encoding/json/v2"
"io"
"net/http"
"strings"
@@ -78,7 +78,7 @@ func TestS3GetObject(t *testing.T) {
}
// check serialized attributes
raw, err := json.Marshal(resp)
raw, err := json.Marshal(resp, json.Deterministic(true))
if err != nil {
t.Fatal(err)
}
@@ -2,7 +2,7 @@ package s3_test
import (
"context"
"encoding/json"
"encoding/json/v2"
"net/http"
"testing"
@@ -63,7 +63,7 @@ func TestS3HeadObject(t *testing.T) {
t.Fatal(err)
}
raw, err := json.Marshal(resp)
raw, err := json.Marshal(resp, json.Deterministic(true))
if err != nil {
t.Fatal(err)
}
@@ -2,7 +2,7 @@ package s3_test
import (
"context"
"encoding/json"
"encoding/json/v2"
"io"
"net/http"
"strings"
@@ -143,7 +143,7 @@ func TestS3ListObjects(t *testing.T) {
t.Fatal(err)
}
raw, err := json.Marshal(resp)
raw, err := json.Marshal(resp, json.Deterministic(true))
if err != nil {
t.Fatal(err)
}
@@ -2,7 +2,7 @@ package s3blob_test
import (
"context"
"encoding/json"
"encoding/json/v2"
"errors"
"fmt"
"io"
@@ -261,7 +261,11 @@ func TestDriverAttributes(t *testing.T) {
t.Fatal(err)
}
raw, err := json.Marshal(attrs)
raw, err := json.Marshal(
attrs,
json.Deterministic(true),
json.FormatNilSliceAsNull(true),
)
if err != nil {
t.Fatal(err)
}
@@ -362,7 +366,11 @@ func TestDriverListPaged(t *testing.T) {
t.Fatal(err)
}
raw, err := json.Marshal(page)
raw, err := json.Marshal(
page,
json.Deterministic(true),
json.FormatNilSliceAsNull(true),
)
if err != nil {
t.Fatal(err)
}
@@ -510,7 +518,11 @@ func TestDriverNewRangeReader(t *testing.T) {
}
}
rawAttrs, err := json.Marshal(r.Attributes())
rawAttrs, err := json.Marshal(
r.Attributes(),
json.Deterministic(true),
json.FormatNilSliceAsNull(true),
)
if err != nil {
t.Fatal(err)
}
+1 -1
View File
@@ -1,7 +1,7 @@
package list
import (
"encoding/json"
"encoding/json/v2"
"regexp"
"strings"
+1 -1
View File
@@ -1,7 +1,7 @@
package list_test
import (
"encoding/json"
"encoding/json/v2"
"fmt"
"testing"
+1 -1
View File
@@ -2,7 +2,7 @@ package logger
import (
"context"
"encoding/json"
"encoding/json/v2"
"errors"
"log/slog"
"sync"
+2 -1
View File
@@ -2,6 +2,7 @@ package logger
import (
"context"
"encoding/json/v2"
"errors"
"fmt"
"log/slog"
@@ -326,7 +327,7 @@ func TestBatchHandlerAttrsFormat(t *testing.T) {
for i, data := range expected {
t.Run(fmt.Sprintf("log handler %d", i), func(t *testing.T) {
log := beforeLogs[i]
raw, _ := log.Data.MarshalJSON()
raw, _ := json.Marshal(log.Data, json.Deterministic(true))
if string(raw) != data {
t.Fatalf("Expected \n%s \ngot \n%s", data, raw)
}
+1 -1
View File
@@ -1,7 +1,7 @@
package picker
import (
"encoding/json"
"encoding/json/v2"
"strings"
"github.com/pocketbase/pocketbase/tools/search"
+2 -2
View File
@@ -1,7 +1,7 @@
package picker_test
import (
"encoding/json"
"encoding/json/v2"
"testing"
"github.com/pocketbase/pocketbase/tools/picker"
@@ -263,7 +263,7 @@ func TestPickFields(t *testing.T) {
return
}
serialized, err := json.Marshal(result)
serialized, err := json.Marshal(result, json.Deterministic(true))
if err != nil {
t.Fatal(err)
}
+10 -10
View File
@@ -2,7 +2,7 @@ package router_test
import (
"database/sql"
"encoding/json"
"encoding/json/v2"
"errors"
"fmt"
"io/fs"
@@ -22,7 +22,7 @@ func TestNewApiErrorWithRawData(t *testing.T) {
"rawData_test",
)
result, _ := json.Marshal(e)
result, _ := json.Marshal(e, json.Deterministic(true))
expected := `{"data":{},"message":"Message_test.","status":300}`
if string(result) != expected {
@@ -61,7 +61,7 @@ func TestNewApiErrorWithValidationData(t *testing.T) {
},
)
result, _ := json.Marshal(e)
result, _ := json.Marshal(e, json.Deterministic(true))
expected := `{"data":{"err1":{"code":"validation_invalid_value","message":"Invalid value."},"err2":{"code":"validation_required","message":"Cannot be blank."},"err3":{"err3.1":{"code":"validation_invalid_value","message":"Invalid value."},"err3.2":{"code":"validation_required","message":"Cannot be blank."},"err3.3":{"err3.3.1":{"code":"validation_required","message":"Cannot be blank."}}},"err4":{"code":"mock_code","message":"Mock_error.","mock_resolve":123},"err5":{"err5.1":{"code":"validation_required","message":"Cannot be blank."}}},"message":"Message_test.","status":300}`
if string(result) != expected {
@@ -93,7 +93,7 @@ func TestNewNotFoundError(t *testing.T) {
for i, s := range scenarios {
t.Run(strconv.Itoa(i), func(t *testing.T) {
e := router.NewNotFoundError(s.message, s.data)
result, _ := json.Marshal(e)
result, _ := json.Marshal(e, json.Deterministic(true))
if str := string(result); str != s.expected {
t.Fatalf("Expected\n%v\ngot\n%v", s.expected, str)
@@ -118,7 +118,7 @@ func TestNewBadRequestError(t *testing.T) {
for i, s := range scenarios {
t.Run(strconv.Itoa(i), func(t *testing.T) {
e := router.NewBadRequestError(s.message, s.data)
result, _ := json.Marshal(e)
result, _ := json.Marshal(e, json.Deterministic(true))
if str := string(result); str != s.expected {
t.Fatalf("Expected\n%v\ngot\n%v", s.expected, str)
@@ -143,7 +143,7 @@ func TestNewForbiddenError(t *testing.T) {
for i, s := range scenarios {
t.Run(strconv.Itoa(i), func(t *testing.T) {
e := router.NewForbiddenError(s.message, s.data)
result, _ := json.Marshal(e)
result, _ := json.Marshal(e, json.Deterministic(true))
if str := string(result); str != s.expected {
t.Fatalf("Expected\n%v\ngot\n%v", s.expected, str)
@@ -168,7 +168,7 @@ func TestNewUnauthorizedError(t *testing.T) {
for i, s := range scenarios {
t.Run(strconv.Itoa(i), func(t *testing.T) {
e := router.NewUnauthorizedError(s.message, s.data)
result, _ := json.Marshal(e)
result, _ := json.Marshal(e, json.Deterministic(true))
if str := string(result); str != s.expected {
t.Fatalf("Expected\n%v\ngot\n%v", s.expected, str)
@@ -193,7 +193,7 @@ func TestNewInternalServerError(t *testing.T) {
for i, s := range scenarios {
t.Run(strconv.Itoa(i), func(t *testing.T) {
e := router.NewInternalServerError(s.message, s.data)
result, _ := json.Marshal(e)
result, _ := json.Marshal(e, json.Deterministic(true))
if str := string(result); str != s.expected {
t.Fatalf("Expected\n%v\ngot\n%v", s.expected, str)
@@ -218,7 +218,7 @@ func TestNewTooManyRequestsError(t *testing.T) {
for i, s := range scenarios {
t.Run(strconv.Itoa(i), func(t *testing.T) {
e := router.NewTooManyRequestsError(s.message, s.data)
result, _ := json.Marshal(e)
result, _ := json.Marshal(e, json.Deterministic(true))
if str := string(result); str != s.expected {
t.Fatalf("Expected\n%v\ngot\n%v", s.expected, str)
@@ -321,7 +321,7 @@ func TestToApiError(t *testing.T) {
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
raw, err := json.Marshal(router.ToApiError(s.err))
raw, err := json.Marshal(router.ToApiError(s.err), json.Deterministic(true))
if err != nil {
t.Fatal(err)
}
+20 -6
View File
@@ -1,7 +1,8 @@
package router
import (
"encoding/json"
"encoding/json/jsontext"
"encoding/json/v2"
"encoding/xml"
"errors"
"io"
@@ -192,7 +193,7 @@ func (e *Event) JSON(status int, data any) error {
// error response or no fields to pick
if rawFields == "" || status < 200 || status > 299 {
return json.NewEncoder(e.Response).Encode(data)
return json.MarshalWrite(e.Response, data)
}
// pick only the requested fields
@@ -201,7 +202,7 @@ func (e *Event) JSON(status int, data any) error {
return err
}
return json.NewEncoder(e.Response).Encode(modified)
return json.MarshalWrite(e.Response, modified)
}
// XML writes an XML response.
@@ -359,10 +360,23 @@ func (e *Event) BindBody(dst any) error {
contentType := e.Request.Header.Get(headerContentType)
if strings.HasPrefix(contentType, "application/json") {
dec := json.NewDecoder(e.Request.Body)
err := dec.Decode(dst)
// note: don't use json.UnmarshalRead because it perfoms an extra
// whitespace scanning which will trigger the auto reread and
// will start again from the beginning and causing an error
//
// (technically json.UnmarshalRead is the better option here but to make
// it work it will require extra interface or other mechanism to temp disable
// the auto reread functionality and this could be a footgun for middlewares
// that wraps the body so for now it is kept as it is and it should
// be similar to the old json.NewDecoder(e.Request.Body).Decode(dst))
dec := jsontext.NewDecoder(e.Request.Body)
err := json.UnmarshalDecode(dec, dst,
// minimize breaking changes with earlier version
// @todo remove with the "Stage 2" refactoring
json.MatchCaseInsensitiveNames(true),
)
if err == nil {
// manually call Reread because single call of json.Decoder.Decode()
// manually call Reread because single call of json.UnmarshalDecode
// doesn't ensure that the entire body is a valid json string
// and it is not guaranteed that it will reach EOF to trigger the reread reset
// (ex. in case of trailing spaces or invalid trailing parts like: `{"test":1},something`)
+28 -25
View File
@@ -3,7 +3,7 @@ package router_test
import (
"bytes"
"crypto/tls"
"encoding/json"
"encoding/json/v2"
"encoding/xml"
"errors"
"fmt"
@@ -328,7 +328,7 @@ func TestEventSetAllGetAll(t *testing.T) {
"a": 123,
"b": 456,
}
rawData, err := json.Marshal(data)
rawData, err := json.Marshal(data, json.Deterministic(true))
if err != nil {
t.Fatal(err)
}
@@ -340,7 +340,7 @@ func TestEventSetAllGetAll(t *testing.T) {
data["c"] = 789
result := event.GetAll()
rawResult, err := json.Marshal(result)
rawResult, err := json.Marshal(result, json.Deterministic(true))
if err != nil {
t.Fatal(err)
}
@@ -410,8 +410,8 @@ func TestEventHTML(t *testing.T) {
func TestEventJSON(t *testing.T) {
body := map[string]any{"a": 123, "b": 456, "c": "test"}
expectedPickedBody := `{"a":123,"c":"test"}` + "\n"
expectedFullBody := `{"a":123,"b":456,"c":"test"}` + "\n"
expectedPickedBody := `{"a":123,"c":"test"}`
expectedFullBody := `{"a":123,"b":456,"c":"test"}`
scenarios := []testResponseWriteScenario[any]{
{
@@ -673,7 +673,7 @@ func TestEventFileFS(t *testing.T) {
func TestEventError(t *testing.T) {
err := new(router.Event).Error(123, "message_test", map[string]any{"a": validation.Required, "b": "test"})
result, _ := json.Marshal(err)
result, _ := json.Marshal(err, json.Deterministic(true))
expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":123}`
if string(result) != expected {
@@ -684,7 +684,7 @@ func TestEventError(t *testing.T) {
func TestEventBadRequestError(t *testing.T) {
err := new(router.Event).BadRequestError("message_test", map[string]any{"a": validation.Required, "b": "test"})
result, _ := json.Marshal(err)
result, _ := json.Marshal(err, json.Deterministic(true))
expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":400}`
if string(result) != expected {
@@ -695,7 +695,7 @@ func TestEventBadRequestError(t *testing.T) {
func TestEventNotFoundError(t *testing.T) {
err := new(router.Event).NotFoundError("message_test", map[string]any{"a": validation.Required, "b": "test"})
result, _ := json.Marshal(err)
result, _ := json.Marshal(err, json.Deterministic(true))
expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":404}`
if string(result) != expected {
@@ -706,7 +706,7 @@ func TestEventNotFoundError(t *testing.T) {
func TestEventForbiddenError(t *testing.T) {
err := new(router.Event).ForbiddenError("message_test", map[string]any{"a": validation.Required, "b": "test"})
result, _ := json.Marshal(err)
result, _ := json.Marshal(err, json.Deterministic(true))
expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":403}`
if string(result) != expected {
@@ -717,7 +717,7 @@ func TestEventForbiddenError(t *testing.T) {
func TestEventUnauthorizedError(t *testing.T) {
err := new(router.Event).UnauthorizedError("message_test", map[string]any{"a": validation.Required, "b": "test"})
result, _ := json.Marshal(err)
result, _ := json.Marshal(err, json.Deterministic(true))
expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":401}`
if string(result) != expected {
@@ -728,7 +728,7 @@ func TestEventUnauthorizedError(t *testing.T) {
func TestEventTooManyRequestsError(t *testing.T) {
err := new(router.Event).TooManyRequestsError("message_test", map[string]any{"a": validation.Required, "b": "test"})
result, _ := json.Marshal(err)
result, _ := json.Marshal(err, json.Deterministic(true))
expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":429}`
if string(result) != expected {
@@ -739,7 +739,7 @@ func TestEventTooManyRequestsError(t *testing.T) {
func TestEventInternalServerError(t *testing.T) {
err := new(router.Event).InternalServerError("message_test", map[string]any{"a": validation.Required, "b": "test"})
result, _ := json.Marshal(err)
result, _ := json.Marshal(err, json.Deterministic(true))
expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":500}`
if string(result) != expected {
@@ -873,7 +873,7 @@ func TestEventBindBody(t *testing.T) {
t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectError, hasErr, err)
}
dstRaw, err := json.Marshal(dst)
dstRaw, err := json.Marshal(dst, json.Deterministic(true))
if err != nil {
t.Fatal(err)
}
@@ -925,29 +925,32 @@ func testEventResponseWrite[T any](
}
result := rec.Result()
defer result.Body.Close()
if result.StatusCode != scenario.expectedStatus {
t.Fatalf("Expected status code %d, got %d", scenario.expectedStatus, result.StatusCode)
}
resultBody, err := io.ReadAll(result.Body)
result.Body.Close()
rawBody, err := io.ReadAll(result.Body)
if err != nil {
t.Fatalf("Failed to read response body: %v", err)
}
strBody := string(rawBody)
resultBody, err = json.Marshal(string(resultBody))
if err != nil {
t.Fatal(err)
// try to deserialize into a map and then serialize again in a
// deterministic manner in case it is json
var jsonBody any
err = json.Unmarshal(rawBody, &jsonBody)
if err == nil {
normalized, err := json.Marshal(jsonBody, json.Deterministic(true))
if err != nil {
t.Fatalf("Failed to deterministicly serialize json body\n%s\ngot\n%v", jsonBody, err)
}
strBody = string(normalized)
}
expectedBody, err := json.Marshal(scenario.expectedBody)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(resultBody, expectedBody) {
t.Fatalf("Expected body\n%s\ngot\n%s", expectedBody, resultBody)
if scenario.expectedBody != strBody {
t.Fatalf("Expected body\n%s\ngot\n%s", scenario.expectedBody, strBody)
}
for k, ev := range scenario.expectedHeaders {
+4 -2
View File
@@ -2,7 +2,7 @@ package router
import (
"bufio"
"encoding/json"
"encoding/json/v2"
"errors"
"io"
"log"
@@ -176,7 +176,9 @@ func ErrorHandler(resp http.ResponseWriter, req *http.Request, err error) {
resp.WriteHeader(apiErr.Status)
if req.Method != http.MethodHead {
if jsonErr := json.NewEncoder(resp).Encode(apiErr); jsonErr != nil {
// note: deterministic because some logs may depend on the exact serialized error
jsonErr := json.MarshalWrite(resp, apiErr, json.Deterministic(true))
if jsonErr != nil {
log.Println(jsonErr) // truly rare case, log to stderr only for dev purposes
}
}
+1 -1
View File
@@ -2,7 +2,7 @@ package router
import (
"encoding"
"encoding/json"
"encoding/json/v2"
"errors"
"reflect"
"regexp"
+2 -2
View File
@@ -2,7 +2,7 @@ package router_test
import (
"bytes"
"encoding/json"
"encoding/json/v2"
"testing"
"time"
@@ -428,7 +428,7 @@ func TestUnmarshalRequestData(t *testing.T) {
return
}
raw, err := json.Marshal(s.dst)
raw, err := json.Marshal(s.dst, json.Deterministic(true))
if err != nil {
t.Fatal(err)
}
+2 -2
View File
@@ -1,7 +1,7 @@
package search
import (
"encoding/json"
"encoding/json/v2"
"errors"
"fmt"
"strconv"
@@ -69,7 +69,7 @@ func (f FilterData) BuildExprWithLimit(
// try to json serialize as fallback
if replacement == "" {
raw, _ := json.Marshal(v)
raw, _ := json.Marshal(v, json.Deterministic(true))
replacement = string(raw)
}
+2 -2
View File
@@ -3,7 +3,7 @@ package search_test
import (
"bytes"
"database/sql"
"encoding/json"
"encoding/json/v2"
"testing"
"github.com/pocketbase/dbx"
@@ -40,7 +40,7 @@ func TestMultiMatchSubqueryBuild(t *testing.T) {
}
// the params from all expressions should be merged in the root
rawParams, err := json.Marshal(params)
rawParams, err := json.Marshal(params, json.Deterministic(true))
if err != nil {
t.Fatal(err)
}
+7 -7
View File
@@ -3,7 +3,7 @@ package search
import (
"context"
"database/sql"
"encoding/json"
"encoding/json/v2"
"errors"
"fmt"
"strconv"
@@ -138,7 +138,7 @@ func TestProviderSort(t *testing.T) {
Sort(initialSort).
AddSort(SortField{"test3", SortDesc})
encoded, _ := json.Marshal(p.sort)
encoded, _ := json.Marshal(p.sort, json.Deterministic(true))
expected := `[{"name":"test1","direction":"ASC"},{"name":"test2","direction":"ASC"},{"name":"test3","direction":"DESC"}]`
if string(encoded) != expected {
@@ -153,7 +153,7 @@ func TestProviderFilter(t *testing.T) {
Filter(initialFilter).
AddFilter("test3")
encoded, _ := json.Marshal(p.filter)
encoded, _ := json.Marshal(p.filter, json.Deterministic(true))
expected := `["test1","test2","test3"]`
if string(encoded) != expected {
@@ -246,12 +246,12 @@ func TestProviderParse(t *testing.T) {
t.Fatalf("Expected perPage %v, got %v", s.expectPerPage, p.perPage)
}
encodedSort, _ := json.Marshal(p.sort)
encodedSort, _ := json.Marshal(p.sort, json.Deterministic(true))
if string(encodedSort) != s.expectSort {
t.Fatalf("Expected sort %v, got \n%v", s.expectSort, string(encodedSort))
}
encodedFilter, _ := json.Marshal(p.filter)
encodedFilter, _ := json.Marshal(p.filter, json.Deterministic(true))
if string(encodedFilter) != s.expectFilter {
t.Fatalf("Expected filter %v, got \n%v", s.expectFilter, string(encodedFilter))
}
@@ -453,7 +453,7 @@ func TestProviderExecNonEmptyQuery(t *testing.T) {
t.Fatalf("Expected resolver.Update to be called %d, got %d", 1, testResolver.UpdateQueryCalls)
}
encoded, _ := json.Marshal(result)
encoded, _ := json.Marshal(result, json.Deterministic(true))
if string(encoded) != s.expectResult {
t.Fatalf("Expected result %v, got \n%v", s.expectResult, string(encoded))
}
@@ -722,7 +722,7 @@ func TestProviderParseAndExec(t *testing.T) {
t.Fatalf("Expected %d db queries, got %d: \n%v", expectedQueries, len(testDB.CalledQueries), testDB.CalledQueries)
}
encoded, _ := json.Marshal(result)
encoded, _ := json.Marshal(result, json.Deterministic(true))
if string(encoded) != s.expectResult {
t.Fatalf("Expected result \n%v\ngot\n%v", s.expectResult, string(encoded))
}
+2 -2
View File
@@ -1,7 +1,7 @@
package search_test
import (
"encoding/json"
"encoding/json/v2"
"fmt"
"testing"
@@ -67,7 +67,7 @@ func TestParseSortFromString(t *testing.T) {
for _, s := range scenarios {
t.Run(s.value, func(t *testing.T) {
result := search.ParseSortFromString(s.value)
encoded, _ := json.Marshal(result)
encoded, _ := json.Marshal(result, json.Deterministic(true))
encodedStr := string(encoded)
if encodedStr != s.expected {
+1 -1
View File
@@ -1,7 +1,7 @@
package store
import (
"encoding/json"
"encoding/json/v2"
"sync"
)
+6 -6
View File
@@ -2,7 +2,7 @@ package store_test
import (
"bytes"
"encoding/json"
"encoding/json/v2"
"slices"
"strconv"
"testing"
@@ -12,7 +12,7 @@ import (
func TestNew(t *testing.T) {
data := map[string]int{"test1": 1, "test2": 2}
originalRawData, err := json.Marshal(data)
originalRawData, err := json.Marshal(data, json.Deterministic(true))
if err != nil {
t.Fatal(err)
}
@@ -22,7 +22,7 @@ func TestNew(t *testing.T) {
s.Remove("test1") // remove 1 item
// check if data was shallow copied
rawData, _ := json.Marshal(data)
rawData, _ := json.Marshal(data, json.Deterministic(true))
if !bytes.Equal(originalRawData, rawData) {
t.Fatalf("Expected data \n%s, \ngot \n%s", originalRawData, rawData)
}
@@ -44,7 +44,7 @@ func TestReset(t *testing.T) {
s := store.New(map[string]int{"test1": 1})
data := map[string]int{"test2": 2}
originalRawData, err := json.Marshal(data)
originalRawData, err := json.Marshal(data, json.Deterministic(true))
if err != nil {
t.Fatal(err)
}
@@ -53,7 +53,7 @@ func TestReset(t *testing.T) {
s.Set("test3", 3)
// check if data was shallow copied
rawData, _ := json.Marshal(data)
rawData, _ := json.Marshal(data, json.Deterministic(true))
if !bytes.Equal(originalRawData, rawData) {
t.Fatalf("Expected data \n%s, \ngot \n%s", originalRawData, rawData)
}
@@ -371,7 +371,7 @@ func TestMarshalJSON(t *testing.T) {
expected := []byte(`{"a":"test1", "b":"test2"}`)
result, err := json.Marshal(s)
result, err := json.Marshal(s, json.Deterministic(true))
if err != nil {
t.Fatal(err)
}
+1 -1
View File
@@ -1,7 +1,7 @@
package subscriptions
import (
"encoding/json"
"encoding/json/v2"
"net/url"
"strings"
"sync"
+1 -1
View File
@@ -1,7 +1,7 @@
package subscriptions_test
import (
"encoding/json"
"encoding/json/v2"
"strings"
"sync"
"testing"
+1 -1
View File
@@ -2,7 +2,7 @@ package types
import (
"database/sql/driver"
"encoding/json"
"encoding/json/v2"
"time"
"github.com/spf13/cast"
+1 -1
View File
@@ -2,7 +2,7 @@ package types
import (
"database/sql/driver"
"encoding/json"
"encoding/json/v2"
"fmt"
)
+13 -10
View File
@@ -2,7 +2,7 @@ package types
import (
"database/sql/driver"
"encoding/json"
"encoding/json/v2"
"fmt"
)
@@ -14,12 +14,9 @@ type jsonArrayAlias[T any] JSONArray[T]
// MarshalJSON implements the [json.Marshaler] interface.
func (m JSONArray[T]) MarshalJSON() ([]byte, error) {
// initialize an empty map to ensure that `[]` is returned as json
if m == nil {
m = JSONArray[T]{}
}
return json.Marshal(jsonArrayAlias[T](m))
// note: forces the Deterministic option to ensure consistent output
// in mixed json v1 and v2 configurations
return json.Marshal(jsonArrayAlias[T](m), json.Deterministic(true))
}
// String returns the string representation of the current json array.
@@ -30,8 +27,7 @@ func (m JSONArray[T]) String() string {
// Value implements the [driver.Valuer] interface.
func (m JSONArray[T]) Value() (driver.Value, error) {
data, err := json.Marshal(m)
data, err := m.MarshalJSON()
return string(data), err
}
@@ -54,5 +50,12 @@ func (m *JSONArray[T]) Scan(value any) error {
data = []byte("[]")
}
return json.Unmarshal(data, m)
err := json.Unmarshal(data, m)
if err != nil {
// reset because jsonv2 performs streaming decoding and mutates the dst even on error
*m = JSONArray[T]{}
return err
}
return nil
}
+2 -2
View File
@@ -2,7 +2,7 @@ package types_test
import (
"database/sql/driver"
"encoding/json"
"encoding/json/v2"
"fmt"
"testing"
@@ -115,7 +115,7 @@ func TestJSONArrayScan(t *testing.T) {
continue
}
result, _ := arr.MarshalJSON()
result, _ := json.Marshal(arr, json.Deterministic(true))
if string(result) != s.expectJSON {
t.Errorf("(%d) Expected %s, got %v", i, s.expectJSON, string(result))
+25 -22
View File
@@ -2,31 +2,13 @@ package types
import (
"database/sql/driver"
"encoding/json"
"encoding/json/v2"
"fmt"
)
// JSONMap defines a map that is safe for json and db read/write.
type JSONMap[T any] map[string]T
// MarshalJSON implements the [json.Marshaler] interface.
func (m JSONMap[T]) MarshalJSON() ([]byte, error) {
type alias JSONMap[T] // prevent recursion
// initialize an empty map to ensure that `{}` is returned as json
if m == nil {
m = JSONMap[T]{}
}
return json.Marshal(alias(m))
}
// String returns the string representation of the current json map.
func (m JSONMap[T]) String() string {
v, _ := m.MarshalJSON()
return string(v)
}
// Get retrieves a single value from the current JSONMap[T].
//
// This helper was added primarily to assist the goja integration since custom map types
@@ -43,10 +25,24 @@ func (m JSONMap[T]) Set(key string, value T) {
m[key] = value
}
// MarshalJSON implements the [json.Marshaler] interface.
func (m JSONMap[T]) MarshalJSON() ([]byte, error) {
type alias JSONMap[T] // prevent recursion
// note: forces the Deterministic option to ensure consistent output
// in mixed json v1 and v2 configurations
return json.Marshal(alias(m), json.Deterministic(true))
}
// String returns the string representation of the current json map.
func (m JSONMap[T]) String() string {
v, _ := m.MarshalJSON()
return string(v)
}
// Value implements the [driver.Valuer] interface.
func (m JSONMap[T]) Value() (driver.Value, error) {
data, err := json.Marshal(m)
data, err := m.MarshalJSON()
return string(data), err
}
@@ -69,5 +65,12 @@ func (m *JSONMap[T]) Scan(value any) error {
data = []byte("{}")
}
return json.Unmarshal(data, m)
err := json.Unmarshal(data, m)
if err != nil {
// reset because jsonv2 performs streaming decoding and mutates the dst even on error
*m = JSONMap[T]{}
return err
}
return nil
}
+2 -1
View File
@@ -2,6 +2,7 @@ package types_test
import (
"database/sql/driver"
"encoding/json/v2"
"fmt"
"testing"
@@ -154,7 +155,7 @@ func TestJSONArrayMapScan(t *testing.T) {
t.Fatalf("Expected %v, got %v (%v)", s.expectError, hasErr, scanErr)
}
result, _ := arr.MarshalJSON()
result, _ := json.Marshal(arr, json.Deterministic(true))
if string(result) != s.expectJSON {
t.Fatalf("Expected %s, got %s", s.expectJSON, result)
+1 -1
View File
@@ -2,7 +2,7 @@ package types
import (
"database/sql/driver"
"encoding/json"
"encoding/json/v2"
"errors"
)