Skip to content

Commit

Permalink
Migrate json wrapper and badjson to library
Browse files Browse the repository at this point in the history
  • Loading branch information
nekohasekai committed Dec 5, 2023
1 parent ba13aad commit d962a01
Show file tree
Hide file tree
Showing 8 changed files with 509 additions and 0 deletions.
46 changes: 46 additions & 0 deletions common/json/badjson/array.go
Original file line number Diff line number Diff line change
@@ -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 changes: 54 additions & 0 deletions common/json/badjson/json.go
Original file line number Diff line number Diff line change
@@ -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 changes: 79 additions & 0 deletions common/json/badjson/merge.go
Original file line number Diff line number Diff line change
@@ -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 changes: 79 additions & 0 deletions common/json/badjson/object.go
Original file line number Diff line number Diff line change
@@ -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 changes: 79 additions & 0 deletions common/json/badjson/typed.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit d962a01

Please sign in to comment.