| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- package xpj
- import (
- "bytes"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- )
- // ParseJSON builds a tree from one JSON document. Numbers keep their text.
- func ParseJSON(body []byte) (*Node, error) {
- dec := json.NewDecoder(bytes.NewReader(body))
- dec.UseNumber()
- root, err := parseValue(dec)
- if err != nil {
- return nil, err
- }
- if _, err := dec.Token(); err != io.EOF {
- if err == nil {
- err = errors.New("unexpected data after the document")
- }
- return nil, fmt.Errorf("xpj: %w", err)
- }
- return root, nil
- }
- func parseValue(dec *json.Decoder) (*Node, error) {
- tok, err := dec.Token()
- if err != nil {
- if err == io.EOF {
- err = io.ErrUnexpectedEOF
- }
- return nil, fmt.Errorf("xpj: %w", err)
- }
- switch t := tok.(type) {
- case nil:
- return NewNull(), nil
- case bool:
- return NewBool(t), nil
- case json.Number:
- return &Node{Kind: Number, Text: string(t)}, nil
- case string:
- return NewString(t), nil
- case json.Delim:
- switch t {
- case '[':
- n := &Node{Kind: Array, Items: []*Node{}}
- for dec.More() {
- v, err := parseValue(dec)
- if err != nil {
- return nil, err
- }
- n.Items = append(n.Items, v)
- }
- _, err := dec.Token() // ]
- return n, wrap(err)
- case '{':
- n := &Node{Kind: Object, Members: []Member{}}
- for dec.More() {
- kt, err := dec.Token()
- if err != nil {
- return nil, wrap(err)
- }
- v, err := parseValue(dec)
- if err != nil {
- return nil, err
- }
- n.Members = append(n.Members, Member{Key: kt.(string), Value: v})
- }
- _, err := dec.Token() // }
- return n, wrap(err)
- }
- }
- return nil, fmt.Errorf("xpj: unexpected token %v", tok)
- }
- func wrap(err error) error {
- if err == nil {
- return nil
- }
- return fmt.Errorf("xpj: %w", err)
- }
- // MarshalJSON writes n in the device's layout: one key or value per line, no
- // indentation, ": " after keys, "," at line ends, empty containers as [] and
- // {}, no trailing newline.
- func (n *Node) MarshalJSON() ([]byte, error) {
- var b bytes.Buffer
- if err := writeValue(&b, n); err != nil {
- return nil, err
- }
- return b.Bytes(), nil
- }
- func writeValue(b *bytes.Buffer, n *Node) error {
- if n == nil {
- return errors.New("xpj: nil node in tree")
- }
- switch n.Kind {
- case Null:
- b.WriteString("null")
- case Bool:
- if n.Bool {
- b.WriteString("true")
- } else {
- b.WriteString("false")
- }
- case Number:
- if !isNumber(n.Text) {
- return fmt.Errorf("xpj: invalid number text %q", n.Text)
- }
- b.WriteString(n.Text)
- case String:
- writeString(b, n.Text)
- case Array:
- if len(n.Items) == 0 {
- b.WriteString("[]")
- return nil
- }
- b.WriteString("[\n")
- for i, it := range n.Items {
- if i > 0 {
- b.WriteString(",\n")
- }
- if err := writeValue(b, it); err != nil {
- return err
- }
- }
- b.WriteString("\n]")
- case Object:
- if len(n.Members) == 0 {
- b.WriteString("{}")
- return nil
- }
- b.WriteString("{\n")
- for i, m := range n.Members {
- if i > 0 {
- b.WriteString(",\n")
- }
- writeString(b, m.Key)
- b.WriteString(": ")
- if err := writeValue(b, m.Value); err != nil {
- return err
- }
- }
- b.WriteString("\n}")
- default:
- return fmt.Errorf("xpj: unknown node kind %d", n.Kind)
- }
- return nil
- }
- // writeString escapes like the reference writer (Python json, ensure_ascii
- // off): quote, backslash and control characters only. '<', '>', '&', '/' and
- // non-ASCII text are written as they are.
- func writeString(b *bytes.Buffer, s string) {
- const hex = "0123456789abcdef"
- b.WriteByte('"')
- for i := 0; i < len(s); i++ {
- c := s[i]
- switch c {
- case '"':
- b.WriteString(`\"`)
- case '\\':
- b.WriteString(`\\`)
- case '\n':
- b.WriteString(`\n`)
- case '\r':
- b.WriteString(`\r`)
- case '\t':
- b.WriteString(`\t`)
- case '\b':
- b.WriteString(`\b`)
- case '\f':
- b.WriteString(`\f`)
- default:
- if c < 0x20 {
- b.WriteString(`\u00`)
- b.WriteByte(hex[c>>4])
- b.WriteByte(hex[c&0xf])
- } else {
- b.WriteByte(c)
- }
- }
- }
- b.WriteByte('"')
- }
- // isNumber reports whether s is a JSON number literal.
- func isNumber(s string) bool {
- i := 0
- if i < len(s) && s[i] == '-' {
- i++
- }
- switch {
- case i < len(s) && s[i] == '0':
- i++
- case i < len(s) && s[i] >= '1' && s[i] <= '9':
- for i < len(s) && s[i] >= '0' && s[i] <= '9' {
- i++
- }
- default:
- return false
- }
- if i < len(s) && s[i] == '.' {
- i++
- start := i
- for i < len(s) && s[i] >= '0' && s[i] <= '9' {
- i++
- }
- if i == start {
- return false
- }
- }
- if i < len(s) && (s[i] == 'e' || s[i] == 'E') {
- i++
- if i < len(s) && (s[i] == '+' || s[i] == '-') {
- i++
- }
- start := i
- for i < len(s) && s[i] >= '0' && s[i] <= '9' {
- i++
- }
- if i == start {
- return false
- }
- }
- return i == len(s)
- }
|