Files
tidb/types/json/json.go
Ewan Chou 62433663e5 util/types: change types path (#5007)
* util/types: change types path
2017-11-04 10:37:14 -05:00

159 lines
4.4 KiB
Go

// Copyright 2017 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.
package json
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"unsafe"
"github.com/juju/errors"
"github.com/pingcap/tidb/mysql"
"github.com/pingcap/tidb/terror"
"github.com/pingcap/tidb/util/hack"
)
// TypeCode indicates JSON type.
type TypeCode byte
const (
// TypeCodeObject indicates the JSON is an object.
TypeCodeObject TypeCode = 0x01
// TypeCodeArray indicates the JSON is an array.
TypeCodeArray TypeCode = 0x03
// TypeCodeLiteral indicates the JSON is a literal.
TypeCodeLiteral TypeCode = 0x04
// TypeCodeInt64 indicates the JSON is a signed integer.
TypeCodeInt64 TypeCode = 0x09
// TypeCodeUint64 indicates the JSON is a unsigned integer.
TypeCodeUint64 TypeCode = 0x0a
// TypeCodeFloat64 indicates the JSON is a double float number.
TypeCodeFloat64 TypeCode = 0x0b
// TypeCodeString indicates the JSON is a string.
TypeCodeString TypeCode = 0x0c
)
const (
// LiteralNil represents JSON null.
LiteralNil byte = 0x00
// LiteralTrue represents JSON true.
LiteralTrue byte = 0x01
// LiteralFalse represents JSON false.
LiteralFalse byte = 0x02
)
const unknownTypeCodeErrorMsg = "unknown type code: %d"
const unknownTypeErrorMsg = "unknown type: %s"
// JSON is for MySQL JSON type.
type JSON struct {
TypeCode TypeCode
I64 int64
Str string
Object map[string]JSON
Array []JSON
}
// CreateJSON creates a JSON from in. Panic if any error occurs.
func CreateJSON(in interface{}) JSON {
j, err := normalize(in)
if err != nil {
panic(err)
}
return j
}
// ParseFromString parses a json from string.
func ParseFromString(s string) (j JSON, err error) {
// TODO: implement the decoder directly. It's important for keeping
// keys in object have same order with the original string.
if len(s) == 0 {
err = ErrInvalidJSONText.GenByArgs("The document is empty")
return
}
if err = j.UnmarshalJSON(hack.Slice(s)); err != nil {
err = ErrInvalidJSONText.GenByArgs(err)
}
return
}
// MarshalJSON implements Marshaler interface.
func (j JSON) MarshalJSON() ([]byte, error) {
switch j.TypeCode {
case TypeCodeObject:
return json.Marshal(j.Object)
case TypeCodeArray:
return json.Marshal(j.Array)
case TypeCodeLiteral:
switch byte(j.I64) {
case LiteralNil:
return []byte("null"), nil
case LiteralTrue:
return []byte("true"), nil
default:
return []byte("false"), nil
}
case TypeCodeInt64:
return json.Marshal(j.I64)
case TypeCodeUint64:
u64 := *(*uint64)(unsafe.Pointer(&j.I64))
return json.Marshal(u64)
case TypeCodeFloat64:
f64 := *(*float64)(unsafe.Pointer(&j.I64))
return json.Marshal(f64)
case TypeCodeString:
return json.Marshal(j.Str)
default:
msg := fmt.Sprintf(unknownTypeCodeErrorMsg, j.TypeCode)
panic(msg)
}
}
// UnmarshalJSON implements Unmarshaler interface.
func (j *JSON) UnmarshalJSON(data []byte) (err error) {
var decoder = json.NewDecoder(bytes.NewReader(data))
decoder.UseNumber()
var in interface{}
if err = decoder.Decode(&in); err == nil {
*j, err = normalize(in)
}
return
}
// String implements fmt.Stringer interface.
func (j JSON) String() string {
bytes, err := json.Marshal(j)
terror.Log(errors.Trace(err))
return strings.TrimSpace(hack.String(bytes))
}
var (
// ErrInvalidJSONText means invalid JSON text.
ErrInvalidJSONText = terror.ClassJSON.New(mysql.ErrInvalidJSONText, mysql.MySQLErrName[mysql.ErrInvalidJSONText])
// ErrInvalidJSONPath means invalid JSON path.
ErrInvalidJSONPath = terror.ClassJSON.New(mysql.ErrInvalidJSONPath, mysql.MySQLErrName[mysql.ErrInvalidJSONPath])
// ErrInvalidJSONData means invalid JSON data.
ErrInvalidJSONData = terror.ClassJSON.New(mysql.ErrInvalidJSONData, mysql.MySQLErrName[mysql.ErrInvalidJSONData])
)
func init() {
terror.ErrClassToMySQLCodes[terror.ClassJSON] = map[terror.ErrCode]uint16{
mysql.ErrInvalidJSONText: mysql.ErrInvalidJSONText,
mysql.ErrInvalidJSONPath: mysql.ErrInvalidJSONPath,
mysql.ErrInvalidJSONData: mysql.ErrInvalidJSONData,
}
}