// // DISCLAIMER // // Copyright 2017 ArangoDB GmbH, Cologne, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Copyright holder is ArangoDB GmbH, Cologne, Germany // // Author Ewout Prangsma // package velocypack import ( "fmt" "io" "strconv" ) type DumperOptions struct { // EscapeUnicode turns on escapping multi-byte Unicode characters when dumping them to JSON (creates \uxxxx sequences). EscapeUnicode bool // EscapeForwardSlashes turns on escapping forward slashes when serializing VPack values into JSON. EscapeForwardSlashes bool UnsupportedTypeBehavior UnsupportedTypeBehavior } type UnsupportedTypeBehavior int const ( NullifyUnsupportedType UnsupportedTypeBehavior = iota ConvertUnsupportedType FailOnUnsupportedType ) type Dumper struct { w io.Writer indentation uint options DumperOptions } // NewDumper creates a new dumper around the given writer, with an optional options. func NewDumper(w io.Writer, options *DumperOptions) *Dumper { d := &Dumper{ w: w, } if options != nil { d.options = *options } return d } func (d *Dumper) Append(s Slice) error { w := d.w switch s.Type() { case Null: if _, err := w.Write([]byte("null")); err != nil { return WithStack(err) } return nil case Bool: if v, err := s.GetBool(); err != nil { return WithStack(err) } else if v { if _, err := w.Write([]byte("true")); err != nil { return WithStack(err) } } else { if _, err := w.Write([]byte("false")); err != nil { return WithStack(err) } } return nil case Double: if v, err := s.GetDouble(); err != nil { return WithStack(err) } else if err := d.appendDouble(v); err != nil { return WithStack(err) } return nil case Int, SmallInt: if v, err := s.GetInt(); err != nil { return WithStack(err) } else if err := d.appendInt(v); err != nil { return WithStack(err) } return nil case UInt: if v, err := s.GetUInt(); err != nil { return WithStack(err) } else if err := d.appendUInt(v); err != nil { return WithStack(err) } return nil case String: if v, err := s.GetString(); err != nil { return WithStack(err) } else if err := d.appendString(v); err != nil { return WithStack(err) } return nil case Array: if err := d.appendArray(s); err != nil { return WithStack(err) } return nil case Object: if err := d.appendObject(s); err != nil { return WithStack(err) } return nil default: switch d.options.UnsupportedTypeBehavior { case NullifyUnsupportedType: if _, err := w.Write([]byte("null")); err != nil { return WithStack(err) } case ConvertUnsupportedType: msg := fmt.Sprintf("(non-representable type %s)", s.Type().String()) if err := d.appendString(msg); err != nil { return WithStack(err) } default: return WithStack(NoJSONEquivalentError) } } return nil } var ( doubleQuoteSeq = []byte{'"'} escapeTable = [256]byte{ // 0 1 2 3 4 5 6 7 8 9 A B C D E // F 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't', 'n', 'u', 'f', 'r', 'u', 'u', // 00 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', // 10 0, 0, '"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '/', // 20 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 30~4F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '\\', 0, 0, 0, // 50 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60~FF 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} ) func (d *Dumper) appendUInt(v uint64) error { s := strconv.FormatUint(v, 10) if _, err := d.w.Write([]byte(s)); err != nil { return WithStack(err) } return nil } func (d *Dumper) appendInt(v int64) error { s := strconv.FormatInt(v, 10) if _, err := d.w.Write([]byte(s)); err != nil { return WithStack(err) } return nil } func formatDouble(v float64) string { return strconv.FormatFloat(v, 'g', -1, 64) } func (d *Dumper) appendDouble(v float64) error { s := formatDouble(v) if _, err := d.w.Write([]byte(s)); err != nil { return WithStack(err) } return nil } func (d *Dumper) appendString(v string) error { p := []byte(v) e := len(p) buf := make([]byte, 0, 16) if _, err := d.w.Write(doubleQuoteSeq); err != nil { return WithStack(err) } for i := 0; i < e; i++ { buf = buf[0:0] c := p[i] if (c & 0x80) == 0 { // check for control characters esc := escapeTable[c] if esc != 0 { if c != '/' || d.options.EscapeForwardSlashes { // escape forward slashes only when requested buf = append(buf, '\\') } buf = append(buf, esc) if esc == 'u' { i1 := ((uint(c)) & 0xf0) >> 4 i2 := ((uint(c)) & 0x0f) buf = append(buf, '0', '0', hexChar(i1), hexChar(i2)) } } else { buf = append(buf, c) } } else if (c & 0xe0) == 0xc0 { // two-byte sequence if i+1 >= e { return WithStack(InvalidUtf8SequenceError) } if d.options.EscapeUnicode { value := ((uint(p[i]) & 0x1f) << 6) | (uint(p[i+1]) & 0x3f) buf = dumpUnicodeCharacter(buf, value) } else { buf = append(buf, p[i:i+2]...) } i++ } else if (c & 0xf0) == 0xe0 { // three-byte sequence if i+2 >= e { return WithStack(InvalidUtf8SequenceError) } if d.options.EscapeUnicode { value := (((uint(p[i]) & 0x0f) << 12) | ((uint(p[i+1]) & 0x3f) << 6) | (uint(p[i + +2]) & 0x3f)) buf = dumpUnicodeCharacter(buf, value) } else { buf = append(buf, p[i:i+3]...) } i += 2 } else if (c & 0xf8) == 0xf0 { // four-byte sequence if i+3 >= e { return WithStack(InvalidUtf8SequenceError) } if d.options.EscapeUnicode { value := (((uint(p[i]) & 0x0f) << 18) | ((uint(p[i+1]) & 0x3f) << 12) | ((uint(p[i+2]) & 0x3f) << 6) | (uint(p[i+3]) & 0x3f)) // construct the surrogate pairs value -= 0x10000 high := (((value & 0xffc00) >> 10) + 0xd800) buf = dumpUnicodeCharacter(buf, high) low := (value & 0x3ff) + 0xdc00 buf = dumpUnicodeCharacter(buf, low) } else { buf = append(buf, p[i:i+4]...) } i += 3 } if _, err := d.w.Write(buf); err != nil { return WithStack(err) } } if _, err := d.w.Write(doubleQuoteSeq); err != nil { return WithStack(err) } return nil } func (d *Dumper) appendArray(v Slice) error { w := d.w it, err := NewArrayIterator(v) if err != nil { return WithStack(err) } if _, err := w.Write([]byte{'['}); err != nil { return WithStack(err) } for it.IsValid() { if !it.IsFirst() { if _, err := w.Write([]byte{','}); err != nil { return WithStack(err) } } if value, err := it.Value(); err != nil { return WithStack(err) } else if err := d.Append(value); err != nil { return WithStack(err) } if err := it.Next(); err != nil { return WithStack(err) } } if _, err := w.Write([]byte{']'}); err != nil { return WithStack(err) } return nil } func (d *Dumper) appendObject(v Slice) error { w := d.w it, err := NewObjectIterator(v) if err != nil { return WithStack(err) } if _, err := w.Write([]byte{'{'}); err != nil { return WithStack(err) } for it.IsValid() { if !it.IsFirst() { if _, err := w.Write([]byte{','}); err != nil { return WithStack(err) } } if key, err := it.Key(true); err != nil { return WithStack(err) } else if err := d.Append(key); err != nil { return WithStack(err) } if _, err := w.Write([]byte{':'}); err != nil { return WithStack(err) } if value, err := it.Value(); err != nil { return WithStack(err) } else if err := d.Append(value); err != nil { return WithStack(err) } if err := it.Next(); err != nil { return WithStack(err) } } if _, err := w.Write([]byte{'}'}); err != nil { return WithStack(err) } return nil } func dumpUnicodeCharacter(dst []byte, value uint) []byte { dst = append(dst, '\\', 'u') mask := uint(0xf000) shift := uint(12) for i := 3; i >= 0; i-- { p := (value & mask) >> shift dst = append(dst, hexChar(p)) if i > 0 { mask = mask >> 4 shift -= 4 } } return dst } func hexChar(v uint) byte { v = v & uint(0x0f) if v < 10 { return byte('0' + v) } return byte('A' + v - 10) }