summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndrew Gerrand <adg@golang.org>2010-03-17 15:41:16 +1100
committerAndrew Gerrand <adg@golang.org>2010-03-17 15:41:16 +1100
commita2de5bd5a797785e44482e94532b99730c58a2fa (patch)
tree53ac09b2d59448188c8ab830472839811983cb7b /src
parent940aea30536639dff944b7e1eca0797173116585 (diff)
downloadgo-a2de5bd5a797785e44482e94532b99730c58a2fa.tar.gz
json: add MarshalIndent (accepts user-specified indent string)
Fixes issue 661 R=r, rsc, skorobo CC=golang-dev http://codereview.appspot.com/576042
Diffstat (limited to 'src')
-rw-r--r--src/pkg/json/struct.go143
-rw-r--r--src/pkg/json/struct_test.go93
2 files changed, 184 insertions, 52 deletions
diff --git a/src/pkg/json/struct.go b/src/pkg/json/struct.go
index 3357e04a3..46f04146d 100644
--- a/src/pkg/json/struct.go
+++ b/src/pkg/json/struct.go
@@ -8,6 +8,7 @@
package json
import (
+ "bytes"
"fmt"
"io"
"os"
@@ -318,131 +319,160 @@ func (e *MarshalError) String() string {
return "json cannot encode value of type " + e.T.String()
}
-func writeArrayOrSlice(w io.Writer, val reflect.ArrayOrSliceValue) (err os.Error) {
- if _, err = fmt.Fprint(w, "["); err != nil {
- return
+type writeState struct {
+ bytes.Buffer
+ indent string
+ newlines bool
+ depth int
+}
+
+func (s *writeState) descend(bra byte) {
+ s.depth++
+ s.WriteByte(bra)
+}
+
+func (s *writeState) ascend(ket byte) {
+ s.depth--
+ s.writeIndent()
+ s.WriteByte(ket)
+}
+
+func (s *writeState) writeIndent() {
+ if s.newlines {
+ s.WriteByte('\n')
+ }
+ for i := 0; i < s.depth; i++ {
+ s.WriteString(s.indent)
}
+}
+
+func (s *writeState) writeArrayOrSlice(val reflect.ArrayOrSliceValue) (err os.Error) {
+ s.descend('[')
for i := 0; i < val.Len(); i++ {
- if err = writeValue(w, val.Elem(i)); err != nil {
+ s.writeIndent()
+
+ if err = s.writeValue(val.Elem(i)); err != nil {
return
}
if i < val.Len()-1 {
- if _, err = fmt.Fprint(w, ","); err != nil {
- return
- }
+ s.WriteByte(',')
}
}
- _, err = fmt.Fprint(w, "]")
+ s.ascend(']')
return
}
-func writeMap(w io.Writer, val *reflect.MapValue) (err os.Error) {
+func (s *writeState) writeMap(val *reflect.MapValue) (err os.Error) {
key := val.Type().(*reflect.MapType).Key()
if _, ok := key.(*reflect.StringType); !ok {
return &MarshalError{val.Type()}
}
- keys := val.Keys()
- if _, err = fmt.Fprint(w, "{"); err != nil {
- return
- }
+ s.descend('{')
+ keys := val.Keys()
for i := 0; i < len(keys); i++ {
- if _, err = fmt.Fprintf(w, "%s:", Quote(keys[i].(*reflect.StringValue).Get())); err != nil {
- return
- }
+ s.writeIndent()
+
+ fmt.Fprintf(s, "%s:", Quote(keys[i].(*reflect.StringValue).Get()))
- if err = writeValue(w, val.Elem(keys[i])); err != nil {
+ if err = s.writeValue(val.Elem(keys[i])); err != nil {
return
}
if i < len(keys)-1 {
- if _, err = fmt.Fprint(w, ","); err != nil {
- return
- }
+ s.WriteByte(',')
}
}
- _, err = fmt.Fprint(w, "}")
+ s.ascend('}')
return
}
-func writeStruct(w io.Writer, val *reflect.StructValue) (err os.Error) {
- if _, err = fmt.Fprint(w, "{"); err != nil {
- return
- }
+func (s *writeState) writeStruct(val *reflect.StructValue) (err os.Error) {
+ s.descend('{')
typ := val.Type().(*reflect.StructType)
for i := 0; i < val.NumField(); i++ {
+ s.writeIndent()
+
fieldValue := val.Field(i)
- if _, err = fmt.Fprintf(w, "%s:", Quote(typ.Field(i).Name)); err != nil {
- return
- }
- if err = writeValue(w, fieldValue); err != nil {
+ fmt.Fprintf(s, "%s:", Quote(typ.Field(i).Name))
+ if err = s.writeValue(fieldValue); err != nil {
return
}
if i < val.NumField()-1 {
- if _, err = fmt.Fprint(w, ","); err != nil {
- return
- }
+ s.WriteByte(',')
}
}
- _, err = fmt.Fprint(w, "}")
+ s.ascend('}')
return
}
-func writeValue(w io.Writer, val reflect.Value) (err os.Error) {
+func (s *writeState) writeValue(val reflect.Value) (err os.Error) {
if val == nil {
- _, err = fmt.Fprint(w, "null")
+ fmt.Fprint(s, "null")
return
}
switch v := val.(type) {
case *reflect.StringValue:
- _, err = fmt.Fprint(w, Quote(v.Get()))
+ fmt.Fprint(s, Quote(v.Get()))
case *reflect.ArrayValue:
- err = writeArrayOrSlice(w, v)
+ err = s.writeArrayOrSlice(v)
case *reflect.SliceValue:
- err = writeArrayOrSlice(w, v)
+ err = s.writeArrayOrSlice(v)
case *reflect.MapValue:
- err = writeMap(w, v)
+ err = s.writeMap(v)
case *reflect.StructValue:
- err = writeStruct(w, v)
+ err = s.writeStruct(v)
case *reflect.ChanValue,
*reflect.UnsafePointerValue,
*reflect.FuncValue:
err = &MarshalError{val.Type()}
case *reflect.InterfaceValue:
if v.IsNil() {
- _, err = fmt.Fprint(w, "null")
+ fmt.Fprint(s, "null")
} else {
- err = writeValue(w, v.Elem())
+ err = s.writeValue(v.Elem())
}
case *reflect.PtrValue:
if v.IsNil() {
- _, err = fmt.Fprint(w, "null")
+ fmt.Fprint(s, "null")
} else {
- err = writeValue(w, v.Elem())
+ err = s.writeValue(v.Elem())
}
case *reflect.UintptrValue:
- _, err = fmt.Fprintf(w, "%d", v.Get())
+ fmt.Fprintf(s, "%d", v.Get())
case *reflect.Uint64Value:
- _, err = fmt.Fprintf(w, "%d", v.Get())
+ fmt.Fprintf(s, "%d", v.Get())
case *reflect.Uint32Value:
- _, err = fmt.Fprintf(w, "%d", v.Get())
+ fmt.Fprintf(s, "%d", v.Get())
case *reflect.Uint16Value:
- _, err = fmt.Fprintf(w, "%d", v.Get())
+ fmt.Fprintf(s, "%d", v.Get())
case *reflect.Uint8Value:
- _, err = fmt.Fprintf(w, "%d", v.Get())
+ fmt.Fprintf(s, "%d", v.Get())
default:
value := val.(reflect.Value)
- _, err = fmt.Fprintf(w, "%#v", value.Interface())
+ fmt.Fprintf(s, "%#v", value.Interface())
+ }
+ return
+}
+
+func (s *writeState) marshal(w io.Writer, val interface{}) (err os.Error) {
+ err = s.writeValue(reflect.NewValue(val))
+ if err != nil {
+ return
}
+ if s.newlines {
+ s.WriteByte('\n')
+ }
+ _, err = s.WriteTo(w)
return
}
@@ -451,5 +481,16 @@ func writeValue(w io.Writer, val reflect.Value) (err os.Error) {
// Due to limitations in JSON, val cannot include cyclic data
// structures, channels, functions, or maps.
func Marshal(w io.Writer, val interface{}) os.Error {
- return writeValue(w, reflect.NewValue(val))
+ s := &writeState{indent: "", newlines: false, depth: 0}
+ return s.marshal(w, val)
+}
+
+// MarshalIndent writes the JSON encoding of val to w,
+// indenting nested values using the indent string.
+//
+// Due to limitations in JSON, val cannot include cyclic data
+// structures, channels, functions, or maps.
+func MarshalIndent(w io.Writer, val interface{}, indent string) os.Error {
+ s := &writeState{indent: indent, newlines: true, depth: 0}
+ return s.marshal(w, val)
}
diff --git a/src/pkg/json/struct_test.go b/src/pkg/json/struct_test.go
index 66d6e79c2..d8528f280 100644
--- a/src/pkg/json/struct_test.go
+++ b/src/pkg/json/struct_test.go
@@ -246,7 +246,98 @@ func TestMarshal(t *testing.T) {
s := buf.String()
if s != tt.out {
- t.Errorf("Marshal(%T) = %q, want %q\n", tt.val, tt.out, s)
+ t.Errorf("Marshal(%T) = %q, want %q\n", tt.val, s, tt.out)
+ }
+ }
+}
+
+type marshalIndentTest struct {
+ val interface{}
+ indent string
+ out string
+}
+
+const marshalIndentTest1 = `[
+ 1,
+ 2,
+ 3,
+ 4
+]
+`
+const marshalIndentTest2 = `[
+[
+1,
+2
+],
+[
+3,
+4
+]
+]
+`
+const marshalIndentTest3 = `[
+ [
+ 1,
+ 2
+ ],
+ [
+ 3,
+ 4
+ ]
+]
+`
+const marshalIndentTest4 = `[
+ [
+ 1,
+ 2
+ ],
+ [
+ 3,
+ 4
+ ]
+]
+`
+const marshalIndentTest5 = `{
+ "a":1,
+ "b":"hello"
+}
+`
+const marshalIndentTest6 = `{
+ "3":[
+ 1,
+ 2,
+ 3
+ ]
+}
+`
+
+var marshalIndentTests = []marshalIndentTest{
+ marshalIndentTest{[]int{1, 2, 3, 4}, " ", marshalIndentTest1},
+ marshalIndentTest{[][]int{[]int{1, 2}, []int{3, 4}}, "", marshalIndentTest2},
+ marshalIndentTest{[][]int{[]int{1, 2}, []int{3, 4}}, " ", marshalIndentTest3},
+ marshalIndentTest{[][]int{[]int{1, 2}, []int{3, 4}}, " ", marshalIndentTest4},
+ marshalIndentTest{struct {
+ a int
+ b string
+ }{1, "hello"},
+ " ",
+ marshalIndentTest5,
+ },
+ marshalIndentTest{map[string][]int{"3": []int{1, 2, 3}}, " ", marshalIndentTest6},
+}
+
+func TestMarshalIndent(t *testing.T) {
+ for _, tt := range marshalIndentTests {
+ var buf bytes.Buffer
+
+ err := MarshalIndent(&buf, tt.val, tt.indent)
+ if err != nil {
+ t.Fatalf("MarshalIndent(%v): %s", tt.val, err)
+ }
+
+ s := buf.String()
+ if s != tt.out {
+ t.Errorf("MarshalIndent(%v) = %q, want %q\n", tt.val, s, tt.out)
}
}
}