mirror of
https://github.com/SagerNet/sing.git
synced 2025-04-04 20:37:40 +03:00
Migrate json wrapper and badjson to library
This commit is contained in:
parent
2c22cd4a77
commit
9eb28eff58
8 changed files with 509 additions and 0 deletions
46
common/json/badjson/array.go
Normal file
46
common/json/badjson/array.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package badjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JSONArray []any
|
||||||
|
|
||||||
|
func (a JSONArray) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal([]any(a))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *JSONArray) UnmarshalJSON(content []byte) error {
|
||||||
|
decoder := json.NewDecoder(bytes.NewReader(content))
|
||||||
|
arrayStart, err := decoder.Token()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if arrayStart != json.Delim('[') {
|
||||||
|
return E.New("excepted array start, but got ", arrayStart)
|
||||||
|
}
|
||||||
|
err = a.decodeJSON(decoder)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
arrayEnd, err := decoder.Token()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if arrayEnd != json.Delim(']') {
|
||||||
|
return E.New("excepted array end, but got ", arrayEnd)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *JSONArray) decodeJSON(decoder *json.Decoder) error {
|
||||||
|
for decoder.More() {
|
||||||
|
item, err := decodeJSON(decoder)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*a = append(*a, item)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
54
common/json/badjson/json.go
Normal file
54
common/json/badjson/json.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package badjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Decode(content []byte) (any, error) {
|
||||||
|
decoder := json.NewDecoder(bytes.NewReader(content))
|
||||||
|
return decodeJSON(decoder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeJSON(decoder *json.Decoder) (any, error) {
|
||||||
|
rawToken, err := decoder.Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch token := rawToken.(type) {
|
||||||
|
case json.Delim:
|
||||||
|
switch token {
|
||||||
|
case '{':
|
||||||
|
var object JSONObject
|
||||||
|
err = object.decodeJSON(decoder)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rawToken, err = decoder.Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if rawToken != json.Delim('}') {
|
||||||
|
return nil, E.New("excepted object end, but got ", rawToken)
|
||||||
|
}
|
||||||
|
return &object, nil
|
||||||
|
case '[':
|
||||||
|
var array JSONArray
|
||||||
|
err = array.decodeJSON(decoder)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rawToken, err = decoder.Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if rawToken != json.Delim(']') {
|
||||||
|
return nil, E.New("excepted array end, but got ", rawToken)
|
||||||
|
}
|
||||||
|
return array, nil
|
||||||
|
default:
|
||||||
|
return nil, E.New("excepted object or array end: ", token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rawToken, nil
|
||||||
|
}
|
79
common/json/badjson/merge.go
Normal file
79
common/json/badjson/merge.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package badjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MergeOptions[T any](source T, destination T) (T, error) {
|
||||||
|
rawSource, err := json.Marshal(source)
|
||||||
|
if err != nil {
|
||||||
|
return common.DefaultValue[T](), E.Cause(err, "marshal source")
|
||||||
|
}
|
||||||
|
rawDestination, err := json.Marshal(destination)
|
||||||
|
if err != nil {
|
||||||
|
return common.DefaultValue[T](), E.Cause(err, "marshal destination")
|
||||||
|
}
|
||||||
|
rawMerged, err := MergeJSON(rawSource, rawDestination)
|
||||||
|
if err != nil {
|
||||||
|
return common.DefaultValue[T](), E.Cause(err, "merge options")
|
||||||
|
}
|
||||||
|
var merged T
|
||||||
|
err = json.Unmarshal(rawMerged, &merged)
|
||||||
|
if err != nil {
|
||||||
|
return common.DefaultValue[T](), E.Cause(err, "unmarshal merged options")
|
||||||
|
}
|
||||||
|
return merged, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeJSON(rawSource json.RawMessage, rawDestination json.RawMessage) (json.RawMessage, error) {
|
||||||
|
source, err := Decode(rawSource)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "decode source")
|
||||||
|
}
|
||||||
|
destination, err := Decode(rawDestination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "decode destination")
|
||||||
|
}
|
||||||
|
merged, err := mergeJSON(source, destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return json.Marshal(merged)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeJSON(anySource any, anyDestination any) (any, error) {
|
||||||
|
switch destination := anyDestination.(type) {
|
||||||
|
case JSONArray:
|
||||||
|
switch source := anySource.(type) {
|
||||||
|
case JSONArray:
|
||||||
|
destination = append(destination, source...)
|
||||||
|
default:
|
||||||
|
destination = append(destination, source)
|
||||||
|
}
|
||||||
|
return destination, nil
|
||||||
|
case *JSONObject:
|
||||||
|
switch source := anySource.(type) {
|
||||||
|
case *JSONObject:
|
||||||
|
for _, entry := range source.Entries() {
|
||||||
|
oldValue, loaded := destination.Get(entry.Key)
|
||||||
|
if loaded {
|
||||||
|
var err error
|
||||||
|
entry.Value, err = mergeJSON(entry.Value, oldValue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "merge object item ", entry.Key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
destination.Put(entry.Key, entry.Value)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, E.New("cannot merge json object into ", reflect.TypeOf(destination))
|
||||||
|
}
|
||||||
|
return destination, nil
|
||||||
|
default:
|
||||||
|
return destination, nil
|
||||||
|
}
|
||||||
|
}
|
79
common/json/badjson/object.go
Normal file
79
common/json/badjson/object.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package badjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/json"
|
||||||
|
"github.com/sagernet/sing/common/x/linkedhashmap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JSONObject struct {
|
||||||
|
linkedhashmap.Map[string, any]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m JSONObject) MarshalJSON() ([]byte, error) {
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
buffer.WriteString("{")
|
||||||
|
items := m.Entries()
|
||||||
|
iLen := len(items)
|
||||||
|
for i, entry := range items {
|
||||||
|
keyContent, err := json.Marshal(entry.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buffer.WriteString(strings.TrimSpace(string(keyContent)))
|
||||||
|
buffer.WriteString(": ")
|
||||||
|
valueContent, err := json.Marshal(entry.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buffer.WriteString(strings.TrimSpace(string(valueContent)))
|
||||||
|
if i < iLen-1 {
|
||||||
|
buffer.WriteString(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer.WriteString("}")
|
||||||
|
return buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *JSONObject) UnmarshalJSON(content []byte) error {
|
||||||
|
decoder := json.NewDecoder(bytes.NewReader(content))
|
||||||
|
m.Clear()
|
||||||
|
objectStart, err := decoder.Token()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if objectStart != json.Delim('{') {
|
||||||
|
return E.New("expected json object start, but starts with ", objectStart)
|
||||||
|
}
|
||||||
|
err = m.decodeJSON(decoder)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "decode json object content")
|
||||||
|
}
|
||||||
|
objectEnd, err := decoder.Token()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if objectEnd != json.Delim('}') {
|
||||||
|
return E.New("expected json object end, but ends with ", objectEnd)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *JSONObject) decodeJSON(decoder *json.Decoder) error {
|
||||||
|
for decoder.More() {
|
||||||
|
var entryKey string
|
||||||
|
keyToken, err := decoder.Token()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
entryKey = keyToken.(string)
|
||||||
|
var entryValue any
|
||||||
|
entryValue, err = decodeJSON(decoder)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "decode value for ", entryKey)
|
||||||
|
}
|
||||||
|
m.Put(entryKey, entryValue)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
79
common/json/badjson/typed.go
Normal file
79
common/json/badjson/typed.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package badjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/json"
|
||||||
|
"github.com/sagernet/sing/common/x/linkedhashmap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TypedMap[T any] struct {
|
||||||
|
linkedhashmap.Map[string, T]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m TypedMap[T]) MarshalJSON() ([]byte, error) {
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
buffer.WriteString("{")
|
||||||
|
items := m.Entries()
|
||||||
|
iLen := len(items)
|
||||||
|
for i, entry := range items {
|
||||||
|
keyContent, err := json.Marshal(entry.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buffer.WriteString(strings.TrimSpace(string(keyContent)))
|
||||||
|
buffer.WriteString(": ")
|
||||||
|
valueContent, err := json.Marshal(entry.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buffer.WriteString(strings.TrimSpace(string(valueContent)))
|
||||||
|
if i < iLen-1 {
|
||||||
|
buffer.WriteString(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer.WriteString("}")
|
||||||
|
return buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TypedMap[T]) UnmarshalJSON(content []byte) error {
|
||||||
|
decoder := json.NewDecoder(bytes.NewReader(content))
|
||||||
|
m.Clear()
|
||||||
|
objectStart, err := decoder.Token()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if objectStart != json.Delim('{') {
|
||||||
|
return E.New("expected json object start, but starts with ", objectStart)
|
||||||
|
}
|
||||||
|
err = m.decodeJSON(decoder)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "decode json object content")
|
||||||
|
}
|
||||||
|
objectEnd, err := decoder.Token()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if objectEnd != json.Delim('}') {
|
||||||
|
return E.New("expected json object end, but ends with ", objectEnd)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TypedMap[T]) decodeJSON(decoder *json.Decoder) error {
|
||||||
|
for decoder.More() {
|
||||||
|
var entryKey string
|
||||||
|
keyToken, err := decoder.Token()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
entryKey = keyToken.(string)
|
||||||
|
var entryValue T
|
||||||
|
err = decoder.Decode(&entryValue)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.Put(entryKey, entryValue)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
128
common/json/comment.go
Normal file
128
common/json/comment.go
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// kanged from v2ray
|
||||||
|
|
||||||
|
type commentFilterState = byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
commentFilterStateContent commentFilterState = iota
|
||||||
|
commentFilterStateEscape
|
||||||
|
commentFilterStateDoubleQuote
|
||||||
|
commentFilterStateDoubleQuoteEscape
|
||||||
|
commentFilterStateSingleQuote
|
||||||
|
commentFilterStateSingleQuoteEscape
|
||||||
|
commentFilterStateComment
|
||||||
|
commentFilterStateSlash
|
||||||
|
commentFilterStateMultilineComment
|
||||||
|
commentFilterStateMultilineCommentStar
|
||||||
|
)
|
||||||
|
|
||||||
|
type CommentFilter struct {
|
||||||
|
br *bufio.Reader
|
||||||
|
state commentFilterState
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCommentFilter(reader io.Reader) io.Reader {
|
||||||
|
return &CommentFilter{br: bufio.NewReader(reader)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *CommentFilter) Read(b []byte) (int, error) {
|
||||||
|
p := b[:0]
|
||||||
|
for len(p) < len(b)-2 {
|
||||||
|
x, err := v.br.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
switch v.state {
|
||||||
|
case commentFilterStateContent:
|
||||||
|
switch x {
|
||||||
|
case '"':
|
||||||
|
v.state = commentFilterStateDoubleQuote
|
||||||
|
p = append(p, x)
|
||||||
|
case '\'':
|
||||||
|
v.state = commentFilterStateSingleQuote
|
||||||
|
p = append(p, x)
|
||||||
|
case '\\':
|
||||||
|
v.state = commentFilterStateEscape
|
||||||
|
case '#':
|
||||||
|
v.state = commentFilterStateComment
|
||||||
|
case '/':
|
||||||
|
v.state = commentFilterStateSlash
|
||||||
|
default:
|
||||||
|
p = append(p, x)
|
||||||
|
}
|
||||||
|
case commentFilterStateEscape:
|
||||||
|
p = append(p, '\\', x)
|
||||||
|
v.state = commentFilterStateContent
|
||||||
|
case commentFilterStateDoubleQuote:
|
||||||
|
switch x {
|
||||||
|
case '"':
|
||||||
|
v.state = commentFilterStateContent
|
||||||
|
p = append(p, x)
|
||||||
|
case '\\':
|
||||||
|
v.state = commentFilterStateDoubleQuoteEscape
|
||||||
|
default:
|
||||||
|
p = append(p, x)
|
||||||
|
}
|
||||||
|
case commentFilterStateDoubleQuoteEscape:
|
||||||
|
p = append(p, '\\', x)
|
||||||
|
v.state = commentFilterStateDoubleQuote
|
||||||
|
case commentFilterStateSingleQuote:
|
||||||
|
switch x {
|
||||||
|
case '\'':
|
||||||
|
v.state = commentFilterStateContent
|
||||||
|
p = append(p, x)
|
||||||
|
case '\\':
|
||||||
|
v.state = commentFilterStateSingleQuoteEscape
|
||||||
|
default:
|
||||||
|
p = append(p, x)
|
||||||
|
}
|
||||||
|
case commentFilterStateSingleQuoteEscape:
|
||||||
|
p = append(p, '\\', x)
|
||||||
|
v.state = commentFilterStateSingleQuote
|
||||||
|
case commentFilterStateComment:
|
||||||
|
if x == '\n' {
|
||||||
|
v.state = commentFilterStateContent
|
||||||
|
p = append(p, '\n')
|
||||||
|
}
|
||||||
|
case commentFilterStateSlash:
|
||||||
|
switch x {
|
||||||
|
case '/':
|
||||||
|
v.state = commentFilterStateComment
|
||||||
|
case '*':
|
||||||
|
v.state = commentFilterStateMultilineComment
|
||||||
|
default:
|
||||||
|
p = append(p, '/', x)
|
||||||
|
}
|
||||||
|
case commentFilterStateMultilineComment:
|
||||||
|
switch x {
|
||||||
|
case '*':
|
||||||
|
v.state = commentFilterStateMultilineCommentStar
|
||||||
|
case '\n':
|
||||||
|
p = append(p, '\n')
|
||||||
|
}
|
||||||
|
case commentFilterStateMultilineCommentStar:
|
||||||
|
switch x {
|
||||||
|
case '/':
|
||||||
|
v.state = commentFilterStateContent
|
||||||
|
case '*':
|
||||||
|
// Stay
|
||||||
|
case '\n':
|
||||||
|
p = append(p, '\n')
|
||||||
|
default:
|
||||||
|
v.state = commentFilterStateMultilineComment
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("Unknown state.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
23
common/json/context.go
Normal file
23
common/json/context.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
//go:build go1.21 && !without_contextjson
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing/common/json/internal/contextjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Marshal = json.Marshal
|
||||||
|
Unmarshal = json.Unmarshal
|
||||||
|
NewEncoder = json.NewEncoder
|
||||||
|
NewDecoder = json.NewDecoder
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Encoder = json.Encoder
|
||||||
|
Decoder = json.Decoder
|
||||||
|
Token = json.Token
|
||||||
|
Delim = json.Delim
|
||||||
|
SyntaxError = json.SyntaxError
|
||||||
|
RawMessage = json.RawMessage
|
||||||
|
)
|
21
common/json/std.go
Normal file
21
common/json/std.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
//go:build !go1.21 || without_contextjson
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
var (
|
||||||
|
Marshal = json.Marshal
|
||||||
|
Unmarshal = json.Unmarshal
|
||||||
|
NewEncoder = json.NewEncoder
|
||||||
|
NewDecoder = json.NewDecoder
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Encoder = json.Encoder
|
||||||
|
Decoder = json.Decoder
|
||||||
|
Token = json.Token
|
||||||
|
Delim = json.Delim
|
||||||
|
SyntaxError = json.SyntaxError
|
||||||
|
RawMessage = json.RawMessage
|
||||||
|
)
|
Loading…
Add table
Add a link
Reference in a new issue