diff --git a/src/lib/Makefile b/src/lib/Makefile index 7d855e4346..32e2918307 100644 --- a/src/lib/Makefile +++ b/src/lib/Makefile @@ -12,6 +12,7 @@ DIRS=\ hash\ http\ io\ + json\ math\ net\ os\ @@ -94,6 +95,8 @@ fmt.dirinstall: io.dirinstall reflect.dirinstall strconv.dirinstall hash.dirinstall: os.dirinstall http.dirinstall: bufio.install io.dirinstall net.dirinstall os.dirinstall strings.install io.dirinstall: os.dirinstall syscall.dirinstall +json.dirinstall: container/array.dirinstall fmt.dirinstall io.dirinstall math.dirinstall \ + strconv.dirinstall strings.install utf8.install net.dirinstall: fmt.dirinstall once.install os.dirinstall strconv.dirinstall os.dirinstall: syscall.dirinstall regexp.dirinstall: os.dirinstall diff --git a/src/lib/json/Makefile b/src/lib/json/Makefile new file mode 100644 index 0000000000..581f9d4b62 --- /dev/null +++ b/src/lib/json/Makefile @@ -0,0 +1,64 @@ +# Copyright 2009 The Go Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +# DO NOT EDIT. Automatically generated by gobuild. +# gobuild -m >Makefile +O=6 +GC=$(O)g +CC=$(O)c -w +AS=$(O)a +AR=$(O)ar + +default: packages + +clean: + rm -f *.$O *.a $O.out + +test: packages + gotest + +coverage: packages + gotest + 6cov -g `pwd` | grep -v '_test\.go:' + +%.$O: %.go + $(GC) $*.go + +%.$O: %.c + $(CC) $*.c + +%.$O: %.s + $(AS) $*.s + +O1=\ + parse.$O\ + +O2=\ + generic.$O\ + struct.$O\ + +json.a: a1 a2 + +a1: $(O1) + $(AR) grc json.a parse.$O + rm -f $(O1) + +a2: $(O2) + $(AR) grc json.a generic.$O struct.$O + rm -f $(O2) + +newpkg: clean + $(AR) grc json.a + +$(O1): newpkg +$(O2): a1 + +nuke: clean + rm -f $(GOROOT)/pkg/json.a + +packages: json.a + +install: packages + cp json.a $(GOROOT)/pkg/json.a + diff --git a/src/lib/json/generic.go b/src/lib/json/generic.go new file mode 100644 index 0000000000..e5e76bf594 --- /dev/null +++ b/src/lib/json/generic.go @@ -0,0 +1,303 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Generic JSON representation. + +package json + +import ( + "array"; + "fmt"; + "math"; + "json"; + "strconv"; + "strings"; +) + +export const ( + StringKind = iota; + NumberKind; + MapKind; // JSON term is "Object", but in Go, it's a map + ArrayKind; + BoolKind; + NullKind; +) + +export type Json interface { + Kind() int; + String() string; + Number() float64; + Bool() bool; + Get(s string) Json; + Elem(i int) Json; + Len() int; +} + +export func JsonToString(j Json) string { + if j == nil { + return "null" + } + if j.Kind() == StringKind { + return Quote(j.String()) + } + return j.String() +} + +type Null struct { } +export var null Json = &Null{} +func (*Null) Kind() int { return NullKind } +func (*Null) String() string { return "null" } +func (*Null) Number() float64 { return 0 } +func (*Null) Bool() bool { return false } +func (*Null) Get(s string) Json { return null } +func (*Null) Elem(int) Json { return null } +func (*Null) Len() int { return 0 } + +type String struct { s string; Null } +func (j *String) Kind() int { return StringKind } +func (j *String) String() string { return j.s } + +type Number struct { f float64; Null } +func (j *Number) Kind() int { return NumberKind } +func (j *Number) Number() float64 { return j.f } +func (j *Number) String() string { + if math.Floor(j.f) == j.f { + return fmt.sprintf("%.0f", j.f); + } + return fmt.sprintf("%g", j.f); +} + +type Array struct { a *array.Array; Null } +func (j *Array) Kind() int { return ArrayKind } +func (j *Array) Len() int { return j.a.Len() } +func (j *Array) Elem(i int) Json { + if i < 0 || i >= j.a.Len() { + return null + } + return j.a.At(i) +} +func (j *Array) String() string { + s := "["; + for i := 0; i < j.a.Len(); i++ { + if i > 0 { + s += ","; + } + s += JsonToString(j.a.At(i).(Json)); + } + s += "]"; + return s; +} + +type Bool struct { b bool; Null } +func (j *Bool) Kind() int { return BoolKind } +func (j *Bool) Bool() bool { return j.b } +func (j *Bool) String() string { + if j.b { + return "true" + } + return "false" +} + +type Map struct { m *map[string]Json; Null } +func (j *Map) Kind() int { return MapKind } +func (j *Map) Get(s string) Json { + if j.m == nil { + return null + } + v, ok := j.m[s]; + if !ok { + return null + } + return v; +} +func (j *Map) String() string { + s := "{"; + first := true; + for k,v range j.m { + if first { + first = false; + } else { + s += ","; + } + s += Quote(k); + s += ":"; + s += JsonToString(v); + } + s += "}"; + return s; +} + +export func Walk(j Json, path string) Json { + for len(path) > 0 { + var elem string; + if i := strings.index(path, '/'); i >= 0 { + elem = path[0:i]; + path = path[i+1:len(path)]; + } else { + elem = path; + path = ""; + } + switch j.Kind() { + case ArrayKind: + indx, err := strconv.atoi(elem); + if err != nil { + return null + } + j = j.Elem(indx); + case MapKind: + j = j.Get(elem); + default: + return null + } + } + return j +} + +export func Equal(a, b Json) bool { + switch { + case a == nil && b == nil: + return true; + case a == nil || b == nil: + return false; + case a.Kind() != b.Kind(): + return false; + } + + switch a.Kind() { + case NullKind: + return true; + case StringKind: + return a.String() == b.String(); + case NumberKind: + return a.Number() == b.Number(); + case BoolKind: + return a.Bool() == b.Bool(); + case ArrayKind: + if a.Len() != b.Len() { + return false; + } + for i := 0; i < a.Len(); i++ { + if !Equal(a.Elem(i), b.Elem(i)) { + return false; + } + } + return true; + case MapKind: + m := a.(*Map).m; + if len(m) != len(b.(*Map).m) { + return false; + } + for k,v range m { + if !Equal(v, b.Get(k)) { + return false; + } + } + return true; + } + + // invalid kind + return false; +} + + +// Parse builder for Json objects. + +type JsonBuilder struct { + // either writing to *ptr + ptr *Json; + + // or to a[i] (can't set ptr = &a[i]) + a *array.Array; + i int; + + // or to m[k] (can't set ptr = &m[k]) + m *map[string] Json; + k string; +} + +func (b *JsonBuilder) Put(j Json) { + switch { + case b.ptr != nil: + *b.ptr = j; + case b.a != nil: + b.a.Set(b.i, j); + case b.m != nil: + b.m[b.k] = j; + } +} + +func (b *JsonBuilder) Get() Json { + switch { + case b.ptr != nil: + return *b.ptr; + case b.a != nil: + return b.a.At(b.i); + case b.m != nil: + return b.m[b.k]; + } + return nil +} + +func (b *JsonBuilder) Float64(f float64) { + b.Put(&Number{f, Null{}}) +} + +func (b *JsonBuilder) Int64(i int64) { + b.Float64(float64(i)) +} + +func (b *JsonBuilder) Uint64(i uint64) { + b.Float64(float64(i)) +} + +func (b *JsonBuilder) Bool(tf bool) { + b.Put(&Bool{tf, Null{}}) +} + +func (b *JsonBuilder) Null() { + b.Put(null) +} + +func (b *JsonBuilder) String(s string) { + b.Put(&String{s, Null{}}) +} + + +func (b *JsonBuilder) Array() { + b.Put(&Array{array.New(0), Null{}}) +} + +func (b *JsonBuilder) Map() { + b.Put(&Map{new(map[string]Json), Null{}}) +} + +func (b *JsonBuilder) Elem(i int) Builder { + bb := new(JsonBuilder); + bb.a = b.Get().(*Array).a; + bb.i = i; + for i >= bb.a.Len() { + bb.a.Push(null) + } + return bb +} + +func (b *JsonBuilder) Key(k string) Builder { + bb := new(JsonBuilder); + bb.m = b.Get().(*Map).m; + bb.k = k; + bb.m[k] = null; + return bb +} + +export func StringToJson(s string) (json Json, ok bool, errtok string) { + var errindx int; + var j Json; + b := new(JsonBuilder); + b.ptr = &j; + ok, errindx, errtok = Parse(s, b); + if !ok { + return nil, false, errtok + } + return j, true, "" +} diff --git a/src/lib/json/generic_test.go b/src/lib/json/generic_test.go new file mode 100644 index 0000000000..a061af0321 --- /dev/null +++ b/src/lib/json/generic_test.go @@ -0,0 +1,72 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package json + +import ( + "json"; + "testing"; +) + +var jsontests = []string { + `null`, + `true`, + `false`, + `"abc"`, + `123`, + `0.1`, + `1e-10`, + `[]`, + `[1,2,3,4]`, + `[1,2,"abc",null,true,false]`, + `{}`, + `{"a":1}`, +} + +export func TestJson(t *testing.T) { + for i := 0; i < len(jsontests); i++ { + val, ok, errtok := StringToJson(jsontests[i]); + if !ok { + t.Errorf("StringToJson(%#q) => error near %v", jsontests[i], errtok); + continue; + } + str := JsonToString(val); + if str != jsontests[i] { + t.Errorf("JsonToString(StringToJson(%#q)) = %#q", jsontests[i], str); + continue; + } + } +} + +export func TestJsonMap(t *testing.T) { + values := new(map[string]Json); + mapstr := "{"; + for i := 0; i < len(jsontests); i++ { + val, ok, errtok := StringToJson(jsontests[i]); + if !ok { + t.Errorf("StringToJson(%#q) => error near %v", jsontests[i], errtok); + } + if i > 0 { + mapstr += ","; + } + values[jsontests[i]] = val; + mapstr += Quote(jsontests[i]); + mapstr += ":"; + mapstr += JsonToString(val); + } + mapstr += "}"; + + mapv, ok, errtok := StringToJson(mapstr); + if !ok { + t.Fatalf("StringToJson(%#q) => error near %v", mapstr, errtok); + } + if mapv == nil { + t.Fatalf("StringToJson(%#q) => nil, %v, %v", mapstr, ok, errtok); + } + for k,v range values { + if v1 := mapv.Get(k); !Equal(v1, v) { + t.Errorf("MapTest: Walk(%#q) => %v, want %v", k, v1, v); + } + } +} diff --git a/src/lib/json/parse.go b/src/lib/json/parse.go new file mode 100644 index 0000000000..1373a124a6 --- /dev/null +++ b/src/lib/json/parse.go @@ -0,0 +1,400 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// JSON (JavaScript Object Notation) parser. +// See http://www.json.org/ + +package json + +import ( + "array"; + "fmt"; + "io"; + "math"; + "strconv"; + "strings"; + "utf8"; +) + +// Strings +// +// Double quoted with escapes: \" \\ \/ \b \f \n \r \t \uXXXX. +// No literal control characters, supposedly. +// Have also seen \' and embedded newlines. + +func UnHex(p string, r, l int) (v int, ok bool) { + v = 0; + for i := r; i < l; i++ { + if i >= len(p) { + return 0, false + } + v *= 16; + switch { + case '0' <= p[i] && p[i] <= '9': + v += int(p[i] - '0'); + case 'a' <= p[i] && p[i] <= 'f': + v += int(p[i] - 'a' + 10); + case 'A' <= p[i] && p[i] <= 'F': + v += int(p[i] - 'A' + 10); + default: + return 0, false; + } + } + return v, true; +} + +export func Unquote(s string) (t string, ok bool) { + if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { + return + } + b := new([]byte, len(s)); + w := 0; + for r := 1; r < len(s)-1; { + switch { + case s[r] == '\\': + r++; + if r >= len(s)-1 { + return + } + switch s[r] { + default: + return; + case '"', '\\', '/', '\'': + b[w] = s[r]; + r++; + w++; + case 'b': + b[w] = '\b'; + r++; + w++; + case 'f': + b[w] = '\f'; + r++; + w++; + case 'n': + b[w] = '\n'; + r++; + w++; + case 'r': + b[w] = '\r'; + r++; + w++; + case 't': + b[w] = '\t'; + r++; + w++; + case 'u': + r++; + rune, ok := UnHex(s, r, 4); + if !ok { + return + } + r += 4; + w += utf8.EncodeRune(rune, b[w:len(b)]); + } + // Control characters are invalid, but we've seen raw \n. + case s[r] < ' ' && s[r] != '\n': + if s[r] == '\n' { + b[w] = '\n'; + r++; + w++; + break; + } + return; + // ASCII + case s[r] < utf8.RuneSelf: + b[w] = s[r]; + r++; + w++; + // Coerce to well-formed UTF-8. + default: + rune, size := utf8.DecodeRuneInString(s, r); + r += size; + w += utf8.EncodeRune(rune, b[w:len(b)]); + } + } + return string(b[0:w]), true +} + +export func Quote(s string) string { + chr := new([]byte, utf8.UTFMax); + chr0 := chr[0:1]; + b := new(io.ByteBuffer); + chr[0] = '"'; + b.Write(chr0); + for i := 0; i < len(s); i++ { + switch { + case s[i]=='"' || s[i]=='\\': + chr[0] = '\\'; + chr[1] = s[i]; + b.Write(chr[0:2]); + + case s[i] == '\b': + chr[0] = '\\'; + chr[1] = 'b'; + b.Write(chr[0:2]); + + case s[i] == '\f': + chr[0] = '\\'; + chr[1] = 'f'; + b.Write(chr[0:2]); + + case s[i] == '\n': + chr[0] = '\\'; + chr[1] = 'n'; + b.Write(chr[0:2]); + + case s[i] == '\r': + chr[0] = '\\'; + chr[1] = 'r'; + b.Write(chr[0:2]); + + case s[i] == '\t': + chr[0] = '\\'; + chr[1] = 't'; + b.Write(chr[0:2]); + + case 0x20 <= s[i] && s[i] < utf8.RuneSelf: + chr[0] = s[i]; + b.Write(chr0); + } + } + chr[0] = '"'; + b.Write(chr0); + return string(b.Data()); +} + + +// Lexer + +type Lexer struct { + s string; + i int; + kind int; + token string; +} + +func Punct(c byte) bool { + return c=='"' || c=='[' || c==']' || c==':' || c=='{' || c=='}' || c==',' +} + +func White(c byte) bool { + return c==' ' || c=='\t' || c=='\n' || c=='\v' +} + +func SkipWhite(p string, i int) int { + for i < len(p) && White(p[i]) { + i++ + } + return i +} + +func SkipToken(p string, i int) int { + for i < len(p) && !Punct(p[i]) && !White(p[i]) { + i++ + } + return i +} + +func SkipString(p string, i int) int { + for i++; i < len(p) && p[i] != '"'; i++ { + if p[i] == '\\' { + i++ + } + } + if i >= len(p) { + return i + } + return i+1 +} + +func (t *Lexer) Next() { + i, s := t.i, t.s; + i = SkipWhite(s, i); + if i >= len(s) { + t.kind = 0; + t.token = ""; + t.i = len(s); + return; + } + + c := s[i]; + switch { + case c == '-' || '0' <= c && c <= '9': + j := SkipToken(s, i); + t.kind = '1'; + t.token = s[i:j]; + i = j; + + case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z': + j := SkipToken(s, i); + t.kind = 'a'; + t.token = s[i:j]; + i = j; + + case c == '"': + j := SkipString(s, i); + t.kind = '"'; + t.token = s[i:j]; + i = j; + + case c == '[', c == ']', c == ':', c == '{', c == '}', c == ',': + t.kind = int(c); + t.token = s[i:i+1]; + i++; + + default: + t.kind = '?'; + t.token = s[i:i+1]; + } + + t.i = i; +} + + +// Parser +// +// Implements parsing but not the actions. Those are +// carried out by the implementation of the Builder interface. +// A Builder represents the object being created. +// Calling a method like Int64(i) sets that object to i. +// Calling a method like Elem(i) or Key(s) creates a +// new builder for a subpiece of the object (logically, +// an array element or a map key). +// +// There are two Builders, in other files. +// The JsonBuilder builds a generic Json structure +// in which maps are maps. +// The StructBuilder copies data into a possibly +// nested data structure, using the "map keys" +// as struct field names. + +type Value interface {} + +export type Builder interface { + // Set value + Int64(i int64); + Uint64(i uint64); + Float64(f float64); + String(s string); + Bool(b bool); + Null(); + Array(); + Map(); + + // Create sub-Builders + Elem(i int) Builder; + Key(s string) Builder; +} + +func ParseValue(lex *Lexer, build Builder) bool { + ok := false; +Switch: + switch lex.kind { + case 0: + break; + case '1': + // If the number is exactly an integer, use that. + if i, err := strconv.atoi64(lex.token); err == nil { + build.Int64(i); + ok = true; + } + else if i, err := strconv.atoui64(lex.token); err == nil { + build.Uint64(i); + ok = true; + } + // Fall back to floating point. + else if f, err := strconv.atof64(lex.token); err == nil { + build.Float64(f); + ok = true; + } + + case 'a': + switch lex.token { + case "true": + build.Bool(true); + ok = true; + case "false": + build.Bool(false); + ok = true; + case "null": + build.Null(); + ok = true; + } + + case '"': + if str, ok1 := Unquote(lex.token); ok1 { + build.String(str); + ok = true; + } + + case '[': + // array + build.Array(); + lex.Next(); + n := 0; + for lex.kind != ']' { + if n > 0 { + if lex.kind != ',' { + break Switch; + } + lex.Next(); + } + if !ParseValue(lex, build.Elem(n)) { + break Switch; + } + n++; + } + ok = true; + + case '{': + // map + lex.Next(); + build.Map(); + n := 0; + for lex.kind != '}' { + if n > 0 { + if lex.kind != ',' { + break Switch; + } + lex.Next(); + } + if lex.kind != '"' { + break Switch; + } + key, ok := Unquote(lex.token); + if !ok { + break Switch; + } + lex.Next(); + if lex.kind != ':' { + break Switch; + } + lex.Next(); + if !ParseValue(lex, build.Key(key)) { + break Switch; + } + n++; + } + ok = true; + } + + if ok { + lex.Next(); + } + return ok; +} + +export func Parse(s string, build Builder) (ok bool, errindx int, errtok string) { + lex := new(Lexer); + lex.s = s; + lex.Next(); + if ParseValue(lex, build) { + if lex.kind == 0 { // EOF + return true, 0, "" + } + } + return false, lex.i, lex.token +} + diff --git a/src/lib/json/struct.go b/src/lib/json/struct.go new file mode 100644 index 0000000000..091dd7b22a --- /dev/null +++ b/src/lib/json/struct.go @@ -0,0 +1,214 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Marshalling and unmarshalling of +// JSON data into Go structs using reflection. + +package json + +import ( + "json"; + "reflect"; +) + +type StructBuilder struct { + val reflect.Value +} + +var nobuilder *StructBuilder + +func SetFloat(v reflect.Value, f float64) { + switch v.Kind() { + case reflect.FloatKind: + v.(reflect.FloatValue).Set(float(f)); + case reflect.Float32Kind: + v.(reflect.Float32Value).Set(float32(f)); + case reflect.Float64Kind: + v.(reflect.Float64Value).Set(float64(f)); + } +} + +func SetInt(v reflect.Value, i int64) { + switch v.Kind() { + case reflect.IntKind: + v.(reflect.IntValue).Set(int(i)); + case reflect.Int8Kind: + v.(reflect.Int8Value).Set(int8(i)); + case reflect.Int16Kind: + v.(reflect.Int16Value).Set(int16(i)); + case reflect.Int32Kind: + v.(reflect.Int32Value).Set(int32(i)); + case reflect.Int64Kind: + v.(reflect.Int64Value).Set(int64(i)); + case reflect.UintKind: + v.(reflect.UintValue).Set(uint(i)); + case reflect.Uint8Kind: + v.(reflect.Uint8Value).Set(uint8(i)); + case reflect.Uint16Kind: + v.(reflect.Uint16Value).Set(uint16(i)); + case reflect.Uint32Kind: + v.(reflect.Uint32Value).Set(uint32(i)); + case reflect.Uint64Kind: + v.(reflect.Uint64Value).Set(uint64(i)); + } +} + +func (b *StructBuilder) Int64(i int64) { + if b == nil { + return + } + v := b.val; + switch v.Kind() { + case reflect.FloatKind, reflect.Float32Kind, reflect.Float64Kind: + SetFloat(v, float64(i)); + default: + SetInt(v, i); + } +} + +func (b *StructBuilder) Uint64(i uint64) { + if b == nil { + return + } + v := b.val; + switch v.Kind() { + case reflect.FloatKind, reflect.Float32Kind, reflect.Float64Kind: + SetFloat(v, float64(i)); + default: + SetInt(v, int64(i)); + } +} + +func (b *StructBuilder) Float64(f float64) { + if b == nil { + return + } + v := b.val; + switch v.Kind() { + case reflect.FloatKind, reflect.Float32Kind, reflect.Float64Kind: + SetFloat(v, f); + default: + SetInt(v, int64(f)); + } +} + +func (b *StructBuilder) Null() { +} + +func (b *StructBuilder) String(s string) { + if b == nil { + return + } + if v := b.val; v.Kind() == reflect.StringKind { + v.(reflect.StringValue).Set(s); + } +} + +func (b *StructBuilder) Bool(tf bool) { + if b == nil { + return + } + if v := b.val; v.Kind() == reflect.BoolKind { + v.(reflect.BoolValue).Set(tf); + } +} + +func (b *StructBuilder) Array() { + if b == nil { + return + } + if v := b.val; v.Kind() == reflect.PtrKind { + pv := v.(reflect.PtrValue); + psubtype := pv.Type().(reflect.PtrType).Sub(); + if pv.Get() == nil && psubtype.Kind() == reflect.ArrayKind { + av := reflect.NewOpenArrayValue(psubtype, 0, 8); + pv.SetSub(av); + } + } +} + +func (b *StructBuilder) Elem(i int) Builder { + if b == nil || i < 0 { + return nobuilder + } + v := b.val; + if v.Kind() == reflect.PtrKind { + // If we have a pointer to an array, allocate or grow + // the array as necessary. Then set v to the array itself. + pv := v.(reflect.PtrValue); + psub := pv.Sub(); + if psub.Kind() == reflect.ArrayKind { + av := psub.(reflect.ArrayValue); + if i > av.Cap() { + n := av.Cap(); + if n < 8 { + n = 8 + } + for n <= i { + n *= 2 + } + av1 := reflect.NewOpenArrayValue(av.Type(), av.Len(), n); + reflect.CopyArray(av1, av, av.Len()); + pv.SetSub(av1); + av = av1; + } + } + v = psub; + } + if v.Kind() == reflect.ArrayKind { + // Array was grown above, or is fixed size. + av := v.(reflect.ArrayValue); + if av.Len() <= i && i < av.Cap() { + av.SetLen(i+1); + } + if i < av.Len() { + return &StructBuilder{ av.Elem(i) } + } + } + return nobuilder +} + +func (b *StructBuilder) Map() { + if b == nil { + return + } + if v := b.val; v.Kind() == reflect.PtrKind { + pv := v.(reflect.PtrValue); + if pv.Get() == nil { + pv.SetSub(reflect.NewInitValue(pv.Type().(reflect.PtrType).Sub())) + } + } +} + +func (b *StructBuilder) Key(k string) Builder { + if b == nil { + return nobuilder + } + v := b.val; + if v.Kind() == reflect.PtrKind { + v = v.(reflect.PtrValue).Sub(); + } + if v.Kind() == reflect.StructKind { + sv := v.(reflect.StructValue); + t := v.Type().(reflect.StructType); + for i := 0; i < t.Len(); i++ { + name, typ, tag, off := t.Field(i); + if k == name { + return &StructBuilder{ sv.Field(i) } + } + } + } + return nobuilder +} + +export func Unmarshal(s string, val interface{}) (ok bool, errtok string) { + var errindx int; + var val1 interface{}; + b := &StructBuilder{ reflect.NewValue(val) }; + ok, errindx, errtok = Parse(s, b); + if !ok { + return false, errtok + } + return true, "" +} diff --git a/src/lib/json/struct_test.go b/src/lib/json/struct_test.go new file mode 100644 index 0000000000..c5dc903da0 --- /dev/null +++ b/src/lib/json/struct_test.go @@ -0,0 +1,82 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package json + +import ( + "json"; + "testing"; +) + +type MyStruct struct { + t bool; + f bool; + s string; + i8 int8; + i16 int16; + i32 int32; + i64 int64; + u8 uint8; + u16 uint16; + u32 uint32; + u64 uint64; + i int; + u uint; + fl float; + fl32 float32; + fl64 float64; + a *[]string; + my *MyStruct; +}; + +const Encoded = + `{"t":true,"f":false,"s":"abc","i8":1,"i16":2,"i32":3,"i64":4,` + ` "u8":5,"u16":6,"u32":7,"u64":8,` + ` "i":-9,"u":10,"bogusfield":"should be ignored",` + ` "fl":11.5,"fl32":12.25,"fl64":13.75,` + ` "a":["x","y","z"],"my":{"s":"subguy"}}`; + + +func Check(t *testing.T, ok bool, name string, v interface{}) { + if !ok { + t.Errorf("%s = %v (BAD)", name, v); + } else { + t.Logf("%s = %v (good)", name, v); + } +} + +export func TestUnmarshal(t *testing.T) { + var m MyStruct; + m.f = true; + ok, errtok := Unmarshal(Encoded, &m); + if !ok { + t.Fatalf("Unmarshal failed near %s", errtok); + } + Check(t, m.t==true, "t", m.t); + Check(t, m.f==false, "f", m.f); + Check(t, m.s=="abc", "s", m.s); + Check(t, m.i8==1, "i8", m.i8); + Check(t, m.i16==2, "i16", m.i16); + Check(t, m.i32==3, "i32", m.i32); + Check(t, m.i64==4, "i64", m.i64); + Check(t, m.u8==5, "u8", m.u8); + Check(t, m.u16==6, "u16", m.u16); + Check(t, m.u32==7, "u32", m.u32); + Check(t, m.u64==8, "u64", m.u64); + Check(t, m.i==-9, "i", m.i); + Check(t, m.u==10, "u", m.u); + Check(t, m.fl==11.5, "fl", m.fl); + Check(t, m.fl32==12.25, "fl32", m.fl32); + Check(t, m.fl64==13.75, "fl64", m.fl64); + Check(t, m.a!=nil, "a", m.a); + if m.a != nil { + Check(t, m.a[0]=="x", "a[0]", m.a[0]); + Check(t, m.a[1]=="y", "a[1]", m.a[1]); + Check(t, m.a[2]=="z", "a[2]", m.a[2]); + } + Check(t, m.my!=nil, "my", m.my); + if m.my != nil { + Check(t, m.my.s=="subguy", "my.s", m.my.s); + } +} diff --git a/src/run.bash b/src/run.bash index dccc924eb2..979a5ac020 100755 --- a/src/run.bash +++ b/src/run.bash @@ -26,6 +26,7 @@ maketest() { maketest \ lib/fmt\ lib/hash\ + lib/json\ lib/math\ lib/reflect\ lib/regexp\