-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathorderedmap.go
More file actions
155 lines (120 loc) · 3.41 KB
/
orderedmap.go
File metadata and controls
155 lines (120 loc) · 3.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package orderedmapjson
import (
"bytes"
"encoding/json"
"fmt"
"io"
"strings"
"github.com/elliotchance/orderedmap/v3"
)
type orderedMap[V any] struct {
*orderedmap.OrderedMap[string, V]
escapeHTML bool
useNumber bool
}
func newOrderedMap[V any]() *orderedMap[V] {
return &orderedMap[V]{
OrderedMap: orderedmap.NewOrderedMap[string, V](),
escapeHTML: true, // Default to true for consistency with encoding/json
}
}
func newOrderedMapWithCapacity[V any](capacity int) *orderedMap[V] {
return &orderedMap[V]{
OrderedMap: orderedmap.NewOrderedMapWithCapacity[string, V](capacity),
escapeHTML: true, // Default to true for consistency with encoding/json
}
}
// SetEscapeHTML sets whether problematic HTML characters (<, >, and &) are escaped by MarshalJSON.
// Note that json.Marshal HTML-escapes the output of MarshalJSON regardless of this setting.
// To produce unescaped output, use a json.Encoder with SetEscapeHTML(false) or call MarshalJSON directly.
func (m *orderedMap[V]) SetEscapeHTML(on bool) {
m.escapeHTML = on
}
// SetUseNumber sets whether UnmarshalJSON decodes numbers as json.Number instead of float64,
// like json.Decoder's UseNumber.
func (m *orderedMap[V]) SetUseNumber(on bool) {
m.useNumber = on
}
// clear removes all entries from the map, keeping its settings.
func (m *orderedMap[V]) clear() {
m.OrderedMap = orderedmap.NewOrderedMap[string, V]()
}
func (m *orderedMap[V]) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
buf.WriteByte('{')
encoder := json.NewEncoder(&buf)
encoder.SetEscapeHTML(m.escapeHTML)
index := 0
for k, v := range m.AllFromFront() {
if index > 0 {
buf.WriteByte(',')
}
if err := encoder.Encode(k); err != nil {
return nil, err
}
// Remove the newline added by Encode
buf.Truncate(buf.Len() - 1)
buf.WriteByte(':')
if err := encoder.Encode(v); err != nil {
return nil, err
}
// Remove the newline added by Encode
buf.Truncate(buf.Len() - 1)
index++
}
buf.WriteByte('}')
return buf.Bytes(), nil
}
func (m *orderedMap[V]) String() string {
builder := strings.Builder{}
builder.WriteString("{")
index := 0
for k, v := range m.AllFromFront() {
if index > 0 {
builder.WriteString(",")
}
builder.WriteString(fmt.Sprintf("%v:%s", k, formatValue(v)))
index++
}
builder.WriteString("}")
return builder.String()
}
// formatValue formats a value for String, formatting nested []any values
// consistently with the map and slice String methods.
func formatValue(value any) string {
values, ok := value.([]any)
if !ok {
return fmt.Sprintf("%v", value)
}
builder := strings.Builder{}
builder.WriteString("[")
for i, v := range values {
if i > 0 {
builder.WriteString(",")
}
builder.WriteString(formatValue(v))
}
builder.WriteString("]")
return builder.String()
}
// Copy returns a shallow copy of the map: the values are copied as-is,
// so nested maps and slices are shared between the original and the copy.
func (m *orderedMap[V]) Copy() *orderedMap[V] {
return &orderedMap[V]{
OrderedMap: m.OrderedMap.Copy(),
escapeHTML: m.escapeHTML,
useNumber: m.useNumber,
}
}
var jsonNull = []byte("null")
// ensureNoTrailingData returns an error if the decoder has any tokens left.
func ensureNoTrailingData(decoder *json.Decoder) error {
token, err := decoder.Token()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
return fmt.Errorf("unexpected data after top-level value: %v", token)
}