blob: 7d27553d7c48ec450b36d777a12339a53ef68135 [file] [log] [blame]
Joe Tsai879b18d2018-08-03 17:22:24 -07001// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package json
6
7import (
8 "io"
9 "math"
10 "regexp"
11 "strconv"
12
13 "google.golang.org/proto/internal/errors"
14)
15
16// marshalNumber encodes v as a Number.
17func (p *encoder) marshalNumber(v Value) error {
18 var err error
19 p.out, err = appendNumber(p.out, v)
20 return err
21}
22func appendNumber(out []byte, v Value) ([]byte, error) {
23 if v.Type() != Number {
24 return nil, errors.New("invalid type %v, expected number", v.Type())
25 }
26 if len(v.raw) > 0 {
27 return append(out, v.raw...), nil
28 }
29 n := v.Number()
30 if math.IsInf(n, 0) || math.IsNaN(n) {
31 return nil, errors.New("invalid number value: %v", n)
32 }
33
34 // JSON number formatting logic based on encoding/json.
35 // See floatEncoder.encode for reference.
36 bits := 64
37 if float64(float32(n)) == n {
38 bits = 32
39 }
40 fmt := byte('f')
41 if abs := math.Abs(n); abs != 0 {
42 if bits == 64 && (abs < 1e-6 || abs >= 1e21) || bits == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) {
43 fmt = 'e'
44 }
45 }
46 out = strconv.AppendFloat(out, n, fmt, -1, bits)
47 if fmt == 'e' {
48 n := len(out)
49 if n >= 4 && out[n-4] == 'e' && out[n-3] == '-' && out[n-2] == '0' {
50 out[n-2] = out[n-1]
51 out = out[:n-1]
52 }
53 }
54 return out, nil
55}
56
57// Exact expression to match a JSON floating-point number.
58// JSON's grammar for floats is more restrictive than Go's grammar.
59var floatRegexp = regexp.MustCompile("^-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?")
60
61// unmarshalNumber decodes a Number from the input.
62func (p *decoder) unmarshalNumber() (Value, error) {
63 v, n, err := consumeNumber(p.in)
64 p.consume(n)
65 return v, err
66}
67func consumeNumber(in []byte) (Value, int, error) {
68 if len(in) == 0 {
69 return Value{}, 0, io.ErrUnexpectedEOF
70 }
71 if n := matchWithDelim(floatRegexp, in); n > 0 {
72 v, err := strconv.ParseFloat(string(in[:n]), 64)
73 if err != nil {
74 return Value{}, 0, err
75 }
76 return rawValueOf(v, in[:n:n]), n, nil
77 }
78 return Value{}, 0, newSyntaxError("invalid %q as number", errRegexp.Find(in))
79}