diff --git a/json.go b/json.go
index 53f176a..cd09edc 100644
--- a/json.go
+++ b/json.go
@@ -23,7 +23,9 @@ func (om *OrderedMap[K, V]) MarshalJSON() ([]byte, error) { //nolint:funlen
return []byte("null"), nil
}
- writer := jwriter.Writer{}
+ writer := jwriter.Writer{
+ NoEscapeHTML: om.disableHTMLEscape,
+ }
writer.RawByte('{')
for pair, firstIteration := om.Oldest(), true; pair != nil; pair = pair.Next() {
@@ -78,7 +80,7 @@ func (om *OrderedMap[K, V]) MarshalJSON() ([]byte, error) { //nolint:funlen
writer.RawByte(':')
// the error is checked at the end of the function
- writer.Raw(json.Marshal(pair.Value))
+ writer.Raw(jsonMarshal(pair.Value, om.disableHTMLEscape))
}
writer.RawByte('}')
@@ -86,6 +88,18 @@ func (om *OrderedMap[K, V]) MarshalJSON() ([]byte, error) { //nolint:funlen
return dumpWriter(&writer)
}
+func jsonMarshal(t interface{}, disableHTMlEscape bool) ([]byte, error) {
+ if disableHTMlEscape {
+ buffer := &bytes.Buffer{}
+ encoder := json.NewEncoder(buffer)
+ encoder.SetEscapeHTML(false)
+ err := encoder.Encode(t)
+ // Encode() adds an extra newline, strip it off to guarantee same behavior as json.Marshal
+ return bytes.TrimRight(buffer.Bytes(), "\n"), err
+ }
+ return json.Marshal(t)
+}
+
func dumpWriter(writer *jwriter.Writer) ([]byte, error) {
if writer.Error != nil {
return nil, writer.Error
@@ -103,7 +117,7 @@ func dumpWriter(writer *jwriter.Writer) ([]byte, error) {
// UnmarshalJSON implements the json.Unmarshaler interface.
func (om *OrderedMap[K, V]) UnmarshalJSON(data []byte) error {
if om.list == nil {
- om.initialize(0)
+ om.initialize(0, om.disableHTMLEscape)
}
return jsonparser.ObjectEach(
diff --git a/json_test.go b/json_test.go
index 42b89ab..bfb08b7 100644
--- a/json_test.go
+++ b/json_test.go
@@ -107,6 +107,26 @@ func TestMarshalJSON(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, `{}`, string(b))
})
+
+ t.Run("HTML escaping enabled (default)", func(t *testing.T) {
+ om := New[marshallable, any]()
+ om.Set(marshallable(1), "hello this is bold")
+ om.Set(marshallable(28), "some book")
+
+ b, err := jsonMarshal(om, false)
+ assert.NoError(t, err)
+ 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))
+ })
+
+ t.Run("HTML escaping disabled", func(t *testing.T) {
+ om := New[marshallable, any](WithDisableHTMLEscape[marshallable, any]())
+ om.Set(marshallable(1), "hello this is bold")
+ om.Set(marshallable(28), "some book")
+
+ b, err := jsonMarshal(om, true /* we need to disable HTML escaping here also */)
+ assert.NoError(t, err)
+ assert.Equal(t, `{"#1#":"hello this is bold","#28#":"some book"}`, string(b))
+ })
}
func TestUnmarshallJSON(t *testing.T) {
diff --git a/orderedmap.go b/orderedmap.go
index 45bf862..6198eb6 100644
--- a/orderedmap.go
+++ b/orderedmap.go
@@ -21,13 +21,15 @@ type Pair[K comparable, V any] struct {
}
type OrderedMap[K comparable, V any] struct {
- pairs map[K]*Pair[K, V]
- list *list.List[*Pair[K, V]]
+ pairs map[K]*Pair[K, V]
+ list *list.List[*Pair[K, V]]
+ disableHTMLEscape bool
}
type initConfig[K comparable, V any] struct {
- capacity int
- initialData []Pair[K, V]
+ capacity int
+ initialData []Pair[K, V]
+ disableHTMLEscape bool
}
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[
}
}
+// WithDisableHTMLEscape disables HTMl escaping when marshalling to JSON
+func WithDisableHTMLEscape[K comparable, V any]() InitOption[K, V] {
+ return func(c *initConfig[K, V]) {
+ c.disableHTMLEscape = true
+ }
+}
+
// New creates a new OrderedMap.
// options can either be one or several InitOption[K, V], or a single integer,
// 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] {
invalidOption()
}
config.capacity = option
+ case bool:
+ if len(options) != 1 {
+ invalidOption()
+ }
+ config.disableHTMLEscape = option
case InitOption[K, V]:
option(&config)
@@ -72,7 +86,7 @@ func New[K comparable, V any](options ...any) *OrderedMap[K, V] {
}
}
- orderedMap.initialize(config.capacity)
+ orderedMap.initialize(config.capacity, config.disableHTMLEscape)
orderedMap.AddPairs(config.initialData...)
return orderedMap
@@ -82,9 +96,10 @@ const invalidOptionMessage = `when using orderedmap.New[K,V]() with options, eit
func invalidOption() { panic(invalidOptionMessage) }
-func (om *OrderedMap[K, V]) initialize(capacity int) {
+func (om *OrderedMap[K, V]) initialize(capacity int, disableHTMLEscape bool) {
om.pairs = make(map[K]*Pair[K, V], capacity)
om.list = list.New[*Pair[K, V]]()
+ om.disableHTMLEscape = disableHTMLEscape
}
// Get looks for the given key, and returns the value associated with it,
diff --git a/yaml.go b/yaml.go
index 6022471..75c2efb 100644
--- a/yaml.go
+++ b/yaml.go
@@ -50,7 +50,7 @@ func (om *OrderedMap[K, V]) UnmarshalYAML(value *yaml.Node) error {
}
if om.list == nil {
- om.initialize(0)
+ om.initialize(0, om.disableHTMLEscape)
}
for index := 0; index < len(value.Content); index += 2 {