-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migrate json wrapper and badjson to library
- Loading branch information
1 parent
ba13aad
commit d962a01
Showing
8 changed files
with
509 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.