From 28b865acf0537aaab0b83f9be9afd400a9819a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 3 Jul 2022 19:43:27 +0800 Subject: [PATCH] Refactor json --- common/badjson/array.go | 46 ++++++++++ common/badjson/json.go | 43 +++++++++ common/badjson/object.go | 78 ++++++++++++++++ common/linkedhashmap/map.go | 171 ------------------------------------ constant/require.go | 7 -- go.mod | 2 +- go.sum | 4 +- option/json.go | 16 ++-- 8 files changed, 179 insertions(+), 188 deletions(-) create mode 100644 common/badjson/array.go create mode 100644 common/badjson/json.go create mode 100644 common/badjson/object.go delete mode 100644 common/linkedhashmap/map.go delete mode 100644 constant/require.go diff --git a/common/badjson/array.go b/common/badjson/array.go new file mode 100644 index 00000000..516c383c --- /dev/null +++ b/common/badjson/array.go @@ -0,0 +1,46 @@ +package badjson + +import ( + "bytes" + + "github.com/goccy/go-json" + E "github.com/sagernet/sing/common/exceptions" +) + +type JSONArray[T any] []T + +func (a JSONArray[T]) MarshalJSON() ([]byte, error) { + return json.Marshal([]T(a)) +} + +func (a *JSONArray[T]) 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[T]) decodeJSON(decoder *json.Decoder) error { + for decoder.More() { + var item T + err := decoder.Decode(&item) + if err != nil { + return err + } + } + return nil +} diff --git a/common/badjson/json.go b/common/badjson/json.go new file mode 100644 index 00000000..903960ac --- /dev/null +++ b/common/badjson/json.go @@ -0,0 +1,43 @@ +package badjson + +import ( + "github.com/goccy/go-json" + E "github.com/sagernet/sing/common/exceptions" +) + +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 + } else if rawToken != json.Delim('}') { + return nil, E.New("excepted object end, but got ", rawToken) + } + return object, nil + case '[': + var array JSONArray[any] + 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 +} diff --git a/common/badjson/object.go b/common/badjson/object.go new file mode 100644 index 00000000..8b9020a2 --- /dev/null +++ b/common/badjson/object.go @@ -0,0 +1,78 @@ +package badjson + +import ( + "bytes" + "strings" + + "github.com/goccy/go-json" + E "github.com/sagernet/sing/common/exceptions" + "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 err + } + 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 + err := decoder.Decode(&entryKey) + if err != nil { + return err + } + var entryValue any + entryValue, err = decodeJSON(decoder) + if err != nil { + return err + } + m.Put(entryKey, entryValue) + } + return nil +} diff --git a/common/linkedhashmap/map.go b/common/linkedhashmap/map.go deleted file mode 100644 index cdae5d9f..00000000 --- a/common/linkedhashmap/map.go +++ /dev/null @@ -1,171 +0,0 @@ -package linkedhashmap - -import ( - "bytes" - - "github.com/goccy/go-json" - "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/x/list" -) - -type Map[K comparable, V any] struct { - raw list.List[mapEntry[K, V]] - rawMap map[K]*list.Element[mapEntry[K, V]] -} - -func (m *Map[K, V]) init() { - if m.rawMap == nil { - m.rawMap = make(map[K]*list.Element[mapEntry[K, V]]) - } -} - -func (m *Map[K, V]) MarshalJSON() ([]byte, error) { - buffer := new(bytes.Buffer) - buffer.WriteString("{") - for item := m.raw.Front(); item != nil; { - entry := item.Value - err := json.NewEncoder(buffer).Encode(entry.Key) - if err != nil { - return nil, err - } - buffer.WriteString(": ") - err = json.NewEncoder(buffer).Encode(entry.Value) - if err != nil { - return nil, err - } - item = item.Next() - if item != nil { - buffer.WriteString(", ") - } - } - buffer.WriteString("}") - return buffer.Bytes(), nil -} - -func (m *Map[K, V]) UnmarshalJSON(content []byte) error { - decoder := json.NewDecoder(bytes.NewReader(content)) - m.Clear() - m.init() - 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) - } - for decoder.More() { - var entryKey K - err = decoder.Decode(&entryKey) - if err != nil { - return err - } - var entryValue V - err = decoder.Decode(&entryValue) - if err != nil { - return err - } - m.rawMap[entryKey] = m.raw.PushBack(mapEntry[K, V]{Key: entryKey, Value: entryValue}) - } - 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 -} - -type mapEntry[K comparable, V any] struct { - Key K - Value V -} - -func (m *Map[K, V]) Size() int { - return m.raw.Size() -} - -func (m *Map[K, V]) IsEmpty() bool { - return m.raw.IsEmpty() -} - -func (m *Map[K, V]) ContainsKey(key K) bool { - m.init() - _, loaded := m.rawMap[key] - return loaded -} - -func (m *Map[K, V]) Get(key K) (V, bool) { - m.init() - value, loaded := m.rawMap[key] - return value.Value.Value, loaded -} - -func (m *Map[K, V]) Put(key K, value V) V { - m.init() - entry, loaded := m.rawMap[key] - if loaded { - oldValue := entry.Value.Value - entry.Value.Value = value - return oldValue - } - entry = m.raw.PushBack(mapEntry[K, V]{Key: key, Value: value}) - m.rawMap[key] = entry - return common.DefaultValue[V]() -} - -func (m *Map[K, V]) PutAll(other *Map[K, V]) { - for item := other.raw.Front(); item != nil; item = item.Next() { - m.Put(item.Value.Key, item.Value.Value) - } -} - -func (m *Map[K, V]) Remove(key K) bool { - m.init() - entry, loaded := m.rawMap[key] - if !loaded { - return false - } - m.raw.Remove(entry) - delete(m.rawMap, key) - return true -} - -func (m *Map[K, V]) RemoveAll(keys []K) { - m.init() - for _, key := range keys { - entry, loaded := m.rawMap[key] - if !loaded { - continue - } - m.raw.Remove(entry) - delete(m.rawMap, key) - } -} - -func (m *Map[K, V]) AsMap() map[K]V { - result := make(map[K]V, m.raw.Len()) - for item := m.raw.Front(); item != nil; item = item.Next() { - result[item.Value.Key] = item.Value.Value - } - return result -} - -func (m *Map[K, V]) Keys() []K { - result := make([]K, 0, m.raw.Len()) - for item := m.raw.Front(); item != nil; item = item.Next() { - result = append(result, item.Value.Key) - } - return result -} - -func (m *Map[K, V]) Values() []V { - result := make([]V, 0, m.raw.Len()) - for item := m.raw.Front(); item != nil; item = item.Next() { - result = append(result, item.Value.Value) - } - return result -} - -func (m *Map[K, V]) Clear() { - *m = Map[K, V]{} -} diff --git a/constant/require.go b/constant/require.go deleted file mode 100644 index c61c5818..00000000 --- a/constant/require.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !go1.19 - -package constant - -func init() { - panic("sing-box requires Go 1.19 or later") -} diff --git a/go.mod b/go.mod index f66ef16f..a71d952f 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/goccy/go-json v0.9.8 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/oschwald/geoip2-golang v1.7.0 - github.com/sagernet/sing v0.0.0-20220703051339-f128942ffe12 + github.com/sagernet/sing v0.0.0-20220703114149-368e41b67bc4 github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.5.0 diff --git a/go.sum b/go.sum index 9668b73c..ee39dd12 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,8 @@ github.com/oschwald/maxminddb-golang v1.9.0/go.mod h1:TK+s/Z2oZq0rSl4PSeAEoP0bgm github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagernet/sing v0.0.0-20220703051339-f128942ffe12 h1:HN3IoHyR2tpI4WwBVSDo5VJ0tKrQKqltkjlHTG9vbdo= -github.com/sagernet/sing v0.0.0-20220703051339-f128942ffe12/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c= +github.com/sagernet/sing v0.0.0-20220703114149-368e41b67bc4 h1:ePp3j7E71+yJfuIxDLzYkngK1AelkP2jITjkMKaHoBs= +github.com/sagernet/sing v0.0.0-20220703114149-368e41b67bc4/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c= github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 h1:whNDUGOAX5GPZkSy4G3Gv9QyIgk5SXRyjkRuP7ohF8k= github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649/go.mod h1:MuyT+9fEPjvauAv0fSE0a6Q+l0Tv2ZrAafTkYfnxBFw= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= diff --git a/option/json.go b/option/json.go index bf4170cb..43e44fb5 100644 --- a/option/json.go +++ b/option/json.go @@ -4,15 +4,15 @@ import ( "bytes" "github.com/goccy/go-json" - "github.com/sagernet/sing-box/common/linkedhashmap" + "github.com/sagernet/sing-box/common/badjson" ) -func ToMap(v any) (*linkedhashmap.Map[string, any], error) { +func ToMap(v any) (*badjson.JSONObject, error) { inputContent, err := json.Marshal(v) if err != nil { return nil, err } - var content linkedhashmap.Map[string, any] + var content badjson.JSONObject err = content.UnmarshalJSON(inputContent) if err != nil { return nil, err @@ -20,8 +20,8 @@ func ToMap(v any) (*linkedhashmap.Map[string, any], error) { return &content, nil } -func MergeObjects(objects ...any) (*linkedhashmap.Map[string, any], error) { - var content linkedhashmap.Map[string, any] +func MergeObjects(objects ...any) (*badjson.JSONObject, error) { + var content badjson.JSONObject for _, object := range objects { objectMap, err := ToMap(object) if err != nil { @@ -45,12 +45,14 @@ func UnmarshallExcluded(inputContent []byte, parentObject any, object any) error if err != nil { return err } - var content linkedhashmap.Map[string, any] + var content badjson.JSONObject err = content.UnmarshalJSON(inputContent) if err != nil { return err } - content.RemoveAll(parentContent.Keys()) + for _, key := range parentContent.Keys() { + content.Remove(key) + } inputContent, err = content.MarshalJSON() if err != nil { return err