diff --git a/encode.go b/encode.go index 7a964a9..478eb56 100644 --- a/encode.go +++ b/encode.go @@ -20,12 +20,16 @@ type EncoderOptions struct { Eol string // Place braces on the same line BracesSameLine bool - // Deprecated: Hjson always emits braces + // Emit braces for the root object EmitRootBraces bool // Always place string in quotes QuoteAlways bool + // Place string in quotes if it could otherwise be a number, boolean or null + QuoteAmbiguousStrings bool // Indent string IndentBy string + // Base indentation string + BaseIndentation string // Allow the -0 value (unlike ES6) AllowMinusZero bool // Encode unknown values as 'null' @@ -39,7 +43,9 @@ func DefaultOptions() EncoderOptions { opt.BracesSameLine = false opt.EmitRootBraces = true opt.QuoteAlways = false + opt.QuoteAmbiguousStrings = true opt.IndentBy = " " + opt.BaseIndentation = "" opt.AllowMinusZero = false opt.UnknownAsNull = false return opt @@ -97,9 +103,8 @@ func (e *hjsonEncoder) quote(value string, separator string, isRootObject bool) if len(value) == 0 { e.WriteString(separator + `""`) } else if e.QuoteAlways || - needsQuotes.MatchString(value) || - startsWithNumber([]byte(value)) || - startsWithKeyword.MatchString(value) { + needsQuotes.MatchString(value) || (e.QuoteAmbiguousStrings && (startsWithNumber([]byte(value)) || + startsWithKeyword.MatchString(value))) { // If the string contains no control characters, no quote characters, and no // backslash characters, then we can safely slap some quotes around it. @@ -178,6 +183,7 @@ func (s sortAlpha) Less(i, j int) bool { func (e *hjsonEncoder) writeIndent(indent int) { e.WriteString(e.Eol) + e.WriteString(e.BaseIndentation) for i := 0; i < indent; i++ { e.WriteString(e.IndentBy) } @@ -295,20 +301,25 @@ func (e *hjsonEncoder) str(value reflect.Value, noIndent bool, separator string, } indent1 := e.indent - e.indent++ - if !noIndent && !e.BracesSameLine { - e.writeIndent(indent1) - } else { - e.WriteString(separator) + if !isRootObject || e.EmitRootBraces { + if !noIndent && !e.BracesSameLine { + e.writeIndent(e.indent) + } else { + e.WriteString(separator) + } + + e.indent++ + e.WriteString("{") } - e.WriteString("{") keys := value.MapKeys() sort.Sort(sortAlpha(keys)) // Join all of the member texts together, separated with newlines for i := 0; i < len; i++ { - e.writeIndent(e.indent) + if i > 0 || !isRootObject || e.EmitRootBraces { + e.writeIndent(e.indent) + } e.WriteString(e.quoteName(keys[i].String())) e.WriteString(":") if err := e.str(value.MapIndex(keys[i]), false, " ", false); err != nil { @@ -316,8 +327,10 @@ func (e *hjsonEncoder) str(value reflect.Value, noIndent bool, separator string, } } - e.writeIndent(indent1) - e.WriteString("}") + if !isRootObject || e.EmitRootBraces { + e.writeIndent(indent1) + e.WriteString("}") + } e.indent = indent1 case reflect.Struct: @@ -330,13 +343,16 @@ func (e *hjsonEncoder) str(value reflect.Value, noIndent bool, separator string, } indent1 := e.indent - e.indent++ - if !noIndent && !e.BracesSameLine { - e.writeIndent(indent1) - } else { - e.WriteString(separator) + if !isRootObject || e.EmitRootBraces { + if !noIndent && !e.BracesSameLine { + e.writeIndent(e.indent) + } else { + e.WriteString(separator) + } + + e.indent++ + e.WriteString("{") } - e.WriteString("{") // Join all of the member texts together, separated with newlines for i := 0; i < l; i++ { @@ -366,11 +382,15 @@ func (e *hjsonEncoder) str(value reflect.Value, noIndent bool, separator string, } if len(jsonComment) > 0 { for _, line := range strings.Split(jsonComment, e.Eol) { - e.writeIndent(e.indent) + if i > 0 || !isRootObject || e.EmitRootBraces { + e.writeIndent(e.indent) + } e.WriteString(fmt.Sprintf("# %s", line)) } } - e.writeIndent(e.indent) + if i > 0 || !isRootObject || e.EmitRootBraces { + e.writeIndent(e.indent) + } e.WriteString(e.quoteName(name)) e.WriteString(":") if err := e.str(curField, false, " ", false); err != nil { @@ -381,8 +401,10 @@ func (e *hjsonEncoder) str(value reflect.Value, noIndent bool, separator string, } } - e.writeIndent(indent1) - e.WriteString("}") + if !isRootObject || e.EmitRootBraces { + e.writeIndent(indent1) + e.WriteString("}") + } e.indent = indent1 @@ -456,10 +478,13 @@ func MarshalWithOptions(v interface{}, options EncoderOptions) ([]byte, error) { e.indent = 0 e.Eol = options.Eol e.BracesSameLine = options.BracesSameLine + e.EmitRootBraces = options.EmitRootBraces e.QuoteAlways = options.QuoteAlways + e.QuoteAmbiguousStrings = options.QuoteAmbiguousStrings e.IndentBy = options.IndentBy + e.BaseIndentation = options.BaseIndentation - err := e.str(reflect.ValueOf(v), true, "", true) + err := e.str(reflect.ValueOf(v), true, e.BaseIndentation, true) if err != nil { return nil, err } diff --git a/encode_test.go b/encode_test.go index 8eddf9a..6011777 100644 --- a/encode_test.go +++ b/encode_test.go @@ -164,4 +164,75 @@ func TestEncodeSliceOfPtrOfPtrOfString(t *testing.T) { ]`)) { t.Error("Marshaler interface error") } -} \ No newline at end of file +} + +func TestNoRootBraces(t *testing.T) { + input := struct { + Foo string + }{ + Foo: "Bar", + } + opt := DefaultOptions() + opt.EmitRootBraces = false + buf, err := MarshalWithOptions(input, opt) + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(buf, []byte(`Foo: Bar`)) { + t.Error("Encode struct with EmitRootBraces false") + } + + theMap := map[string]interface{}{ + "Foo": "Bar", + } + buf, err = MarshalWithOptions(theMap, opt) + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(buf, []byte(`Foo: Bar`)) { + t.Error("Encode map with EmitRootBraces false") + } +} + +func TestBaseIndentation(t *testing.T) { + input := struct { + Foo string + }{ + Foo: "Bar", + } + facit := []byte(` { + Foo: Bar + }`) + opt := DefaultOptions() + opt.BaseIndentation = " " + buf, err := MarshalWithOptions(input, opt) + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(buf, facit) { + t.Error("Encode with BaseIndentation, comparison:\n", string(buf), "\n", string(facit)) + } +} + +func TestQuoteAmbiguousStrings(t *testing.T) { + theMap := map[string]interface{}{ + "One": "1", + "Null": "null", + "False": "false", + } + facit := []byte(`{ + False: false + Null: null + One: 1 +}`) + opt := DefaultOptions() + opt.QuoteAlways = false + opt.QuoteAmbiguousStrings = false + buf, err := MarshalWithOptions(theMap, opt) + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(buf, facit) { + t.Error("Encode with QuoteAmbiguousStrings false, comparison:\n", string(buf), "\n", string(facit)) + } +}