Skip to content

Commit 46d6868

Browse files
authored
Merge pull request #45 from rkettelerij/disable-html-escape-json
feat: allow user to disable HTML escaping when marshalling to JSON.
2 parents 834b7a3 + 4632b9b commit 46d6868

File tree

4 files changed

+59
-10
lines changed

4 files changed

+59
-10
lines changed

json.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ func (om *OrderedMap[K, V]) MarshalJSON() ([]byte, error) { //nolint:funlen
2323
return []byte("null"), nil
2424
}
2525

26-
writer := jwriter.Writer{}
26+
writer := jwriter.Writer{
27+
NoEscapeHTML: om.disableHTMLEscape,
28+
}
2729
writer.RawByte('{')
2830

2931
for pair, firstIteration := om.Oldest(), true; pair != nil; pair = pair.Next() {
@@ -78,14 +80,26 @@ func (om *OrderedMap[K, V]) MarshalJSON() ([]byte, error) { //nolint:funlen
7880

7981
writer.RawByte(':')
8082
// the error is checked at the end of the function
81-
writer.Raw(json.Marshal(pair.Value))
83+
writer.Raw(jsonMarshal(pair.Value, om.disableHTMLEscape))
8284
}
8385

8486
writer.RawByte('}')
8587

8688
return dumpWriter(&writer)
8789
}
8890

91+
func jsonMarshal(t interface{}, disableHTMLEscape bool) ([]byte, error) {
92+
if disableHTMLEscape {
93+
buffer := &bytes.Buffer{}
94+
encoder := json.NewEncoder(buffer)
95+
encoder.SetEscapeHTML(false)
96+
err := encoder.Encode(t)
97+
// Encode() adds an extra newline, strip it off to guarantee same behavior as json.Marshal
98+
return bytes.TrimRight(buffer.Bytes(), "\n"), err
99+
}
100+
return json.Marshal(t)
101+
}
102+
89103
func dumpWriter(writer *jwriter.Writer) ([]byte, error) {
90104
if writer.Error != nil {
91105
return nil, writer.Error
@@ -103,7 +117,7 @@ func dumpWriter(writer *jwriter.Writer) ([]byte, error) {
103117
// UnmarshalJSON implements the json.Unmarshaler interface.
104118
func (om *OrderedMap[K, V]) UnmarshalJSON(data []byte) error {
105119
if om.list == nil {
106-
om.initialize(0)
120+
om.initialize(0, om.disableHTMLEscape)
107121
}
108122

109123
return jsonparser.ObjectEach(

json_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,26 @@ func TestMarshalJSON(t *testing.T) {
107107
assert.NoError(t, err)
108108
assert.Equal(t, `{}`, string(b))
109109
})
110+
111+
t.Run("HTML escaping enabled (default)", func(t *testing.T) {
112+
om := New[marshallable, any]()
113+
om.Set(marshallable(1), "hello <strong>this is bold</strong>")
114+
om.Set(marshallable(28), "<?xml version=\"1.0\"?><catalog><book>some book</book></catalog>")
115+
116+
b, err := jsonMarshal(om, false)
117+
assert.NoError(t, err)
118+
assert.Equal(t, `{"#1#":"hello \u003cstrong\u003ethis is bold\u003c/strong\u003e","#28#":"\u003c?xml version=\"1.0\"?\u003e\u003ccatalog\u003e\u003cbook\u003esome book\u003c/book\u003e\u003c/catalog\u003e"}`, string(b))
119+
})
120+
121+
t.Run("HTML escaping disabled", func(t *testing.T) {
122+
om := New[marshallable, any](WithDisableHTMLEscape[marshallable, any]())
123+
om.Set(marshallable(1), "hello <strong>this is bold</strong>")
124+
om.Set(marshallable(28), "<?xml version=\"1.0\"?><catalog><book>some book</book></catalog>")
125+
126+
b, err := jsonMarshal(om, true /* we need to disable HTML escaping here also */)
127+
assert.NoError(t, err)
128+
assert.Equal(t, `{"#1#":"hello <strong>this is bold</strong>","#28#":"<?xml version=\"1.0\"?><catalog><book>some book</book></catalog>"}`, string(b))
129+
})
110130
}
111131

112132
func TestUnmarshallJSON(t *testing.T) {

orderedmap.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ type Pair[K comparable, V any] struct {
2121
}
2222

2323
type OrderedMap[K comparable, V any] struct {
24-
pairs map[K]*Pair[K, V]
25-
list *list.List[*Pair[K, V]]
24+
pairs map[K]*Pair[K, V]
25+
list *list.List[*Pair[K, V]]
26+
disableHTMLEscape bool
2627
}
2728

2829
type initConfig[K comparable, V any] struct {
29-
capacity int
30-
initialData []Pair[K, V]
30+
capacity int
31+
initialData []Pair[K, V]
32+
disableHTMLEscape bool
3133
}
3234

3335
type InitOption[K comparable, V any] func(config *initConfig[K, V])
@@ -49,6 +51,13 @@ func WithInitialData[K comparable, V any](initialData ...Pair[K, V]) InitOption[
4951
}
5052
}
5153

54+
// WithDisableHTMLEscape disables HTMl escaping when marshalling to JSON
55+
func WithDisableHTMLEscape[K comparable, V any]() InitOption[K, V] {
56+
return func(c *initConfig[K, V]) {
57+
c.disableHTMLEscape = true
58+
}
59+
}
60+
5261
// New creates a new OrderedMap.
5362
// options can either be one or several InitOption[K, V], or a single integer,
5463
// which is then interpreted as a capacity hint, à la make(map[K]V, capacity).
@@ -63,6 +72,11 @@ func New[K comparable, V any](options ...any) *OrderedMap[K, V] {
6372
invalidOption()
6473
}
6574
config.capacity = option
75+
case bool:
76+
if len(options) != 1 {
77+
invalidOption()
78+
}
79+
config.disableHTMLEscape = option
6680

6781
case InitOption[K, V]:
6882
option(&config)
@@ -72,7 +86,7 @@ func New[K comparable, V any](options ...any) *OrderedMap[K, V] {
7286
}
7387
}
7488

75-
orderedMap.initialize(config.capacity)
89+
orderedMap.initialize(config.capacity, config.disableHTMLEscape)
7690
orderedMap.AddPairs(config.initialData...)
7791

7892
return orderedMap
@@ -82,9 +96,10 @@ const invalidOptionMessage = `when using orderedmap.New[K,V]() with options, eit
8296

8397
func invalidOption() { panic(invalidOptionMessage) }
8498

85-
func (om *OrderedMap[K, V]) initialize(capacity int) {
99+
func (om *OrderedMap[K, V]) initialize(capacity int, disableHTMLEscape bool) {
86100
om.pairs = make(map[K]*Pair[K, V], capacity)
87101
om.list = list.New[*Pair[K, V]]()
102+
om.disableHTMLEscape = disableHTMLEscape
88103
}
89104

90105
// Get looks for the given key, and returns the value associated with it,

yaml.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func (om *OrderedMap[K, V]) UnmarshalYAML(value *yaml.Node) error {
5050
}
5151

5252
if om.list == nil {
53-
om.initialize(0)
53+
om.initialize(0, om.disableHTMLEscape)
5454
}
5555

5656
for index := 0; index < len(value.Content); index += 2 {

0 commit comments

Comments
 (0)