]> Cypherpunks repositories - gostls13.git/commitdiff
json: add MarshalIndent (accepts user-specified indent string)
authorAndrew Gerrand <adg@golang.org>
Wed, 17 Mar 2010 04:41:16 +0000 (15:41 +1100)
committerAndrew Gerrand <adg@golang.org>
Wed, 17 Mar 2010 04:41:16 +0000 (15:41 +1100)
Fixes #661

R=r, rsc, skorobo
CC=golang-dev
https://golang.org/cl/576042

src/pkg/json/struct.go
src/pkg/json/struct_test.go

index 3357e04a3c8b781856a4330f22a29f59a87fa5e5..46f04146d6341cd09191d877f024080987a2e072 100644 (file)
@@ -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)
 }
index 66d6e79c2886d04d1dadb9c3d60b4f47c8ddb69f..d8528f28015984f92efcc0c03696c492469cb318 100644 (file)
@@ -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)
                }
        }
 }