summaryrefslogtreecommitdiff
path: root/src/mongo/gotools/common/bsonutil/converter.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/gotools/common/bsonutil/converter.go')
-rw-r--r--src/mongo/gotools/common/bsonutil/converter.go388
1 files changed, 388 insertions, 0 deletions
diff --git a/src/mongo/gotools/common/bsonutil/converter.go b/src/mongo/gotools/common/bsonutil/converter.go
new file mode 100644
index 00000000000..02d091e21a4
--- /dev/null
+++ b/src/mongo/gotools/common/bsonutil/converter.go
@@ -0,0 +1,388 @@
+package bsonutil
+
+import (
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "github.com/mongodb/mongo-tools/common/json"
+ "github.com/mongodb/mongo-tools/common/util"
+ "gopkg.in/mgo.v2"
+ "gopkg.in/mgo.v2/bson"
+ "time"
+)
+
+// ConvertJSONValueToBSON walks through a document or an array and
+// replaces any extended JSON value with its corresponding BSON type.
+func ConvertJSONValueToBSON(x interface{}) (interface{}, error) {
+ switch v := x.(type) {
+ case nil:
+ return nil, nil
+ case bool:
+ return v, nil
+ case map[string]interface{}: // document
+ for key, jsonValue := range v {
+ bsonValue, err := ParseJSONValue(jsonValue)
+ if err != nil {
+ return nil, err
+ }
+ v[key] = bsonValue
+ }
+ return v, nil
+ case bson.D:
+ for i := range v {
+ var err error
+ v[i].Value, err = ParseJSONValue(v[i].Value)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return v, nil
+
+ case []interface{}: // array
+ for i, jsonValue := range v {
+ bsonValue, err := ParseJSONValue(jsonValue)
+ if err != nil {
+ return nil, err
+ }
+ v[i] = bsonValue
+ }
+ return v, nil
+
+ case string, float64, int32, int64:
+ return v, nil // require no conversion
+
+ case json.ObjectId: // ObjectId
+ s := string(v)
+ if !bson.IsObjectIdHex(s) {
+ return nil, errors.New("expected ObjectId to contain 24 hexadecimal characters")
+ }
+ return bson.ObjectIdHex(s), nil
+
+ case json.Decimal128:
+ return v.Decimal128, nil
+
+ case json.Date: // Date
+ n := int64(v)
+ return time.Unix(n/1e3, n%1e3*1e6), nil
+
+ case json.ISODate: // ISODate
+ n := string(v)
+ return util.FormatDate(n)
+
+ case json.NumberLong: // NumberLong
+ return int64(v), nil
+
+ case json.NumberInt: // NumberInt
+ return int32(v), nil
+
+ case json.NumberFloat: // NumberFloat
+ return float64(v), nil
+ case json.BinData: // BinData
+ data, err := base64.StdEncoding.DecodeString(v.Base64)
+ if err != nil {
+ return nil, err
+ }
+ return bson.Binary{v.Type, data}, nil
+
+ case json.DBRef: // DBRef
+ var err error
+ v.Id, err = ParseJSONValue(v.Id)
+ if err != nil {
+ return nil, err
+ }
+ return mgo.DBRef{v.Collection, v.Id, v.Database}, nil
+
+ case json.DBPointer: // DBPointer, for backwards compatibility
+ return bson.DBPointer{v.Namespace, v.Id}, nil
+
+ case json.RegExp: // RegExp
+ return bson.RegEx{v.Pattern, v.Options}, nil
+
+ case json.Timestamp: // Timestamp
+ ts := (int64(v.Seconds) << 32) | int64(v.Increment)
+ return bson.MongoTimestamp(ts), nil
+
+ case json.JavaScript: // Javascript
+ return bson.JavaScript{v.Code, v.Scope}, nil
+
+ case json.MinKey: // MinKey
+ return bson.MinKey, nil
+
+ case json.MaxKey: // MaxKey
+ return bson.MaxKey, nil
+
+ case json.Undefined: // undefined
+ return bson.Undefined, nil
+
+ default:
+ return nil, fmt.Errorf("conversion of JSON value '%v' of type '%T' not supported", v, v)
+ }
+}
+
+func convertKeys(v bson.M) (bson.M, error) {
+ for key, value := range v {
+ jsonValue, err := ConvertBSONValueToJSON(value)
+ if err != nil {
+ return nil, err
+ }
+ v[key] = jsonValue
+ }
+ return v, nil
+}
+
+func getConvertedKeys(v bson.M) (bson.M, error) {
+ out := bson.M{}
+ for key, value := range v {
+ jsonValue, err := GetBSONValueAsJSON(value)
+ if err != nil {
+ return nil, err
+ }
+ out[key] = jsonValue
+ }
+ return out, nil
+}
+
+// ConvertBSONValueToJSON walks through a document or an array and
+// converts any BSON value to its corresponding extended JSON type.
+// It returns the converted JSON document and any error encountered.
+func ConvertBSONValueToJSON(x interface{}) (interface{}, error) {
+ switch v := x.(type) {
+ case nil:
+ return nil, nil
+ case bool:
+ return v, nil
+
+ case *bson.M: // document
+ doc, err := convertKeys(*v)
+ if err != nil {
+ return nil, err
+ }
+ return doc, err
+ case bson.M: // document
+ return convertKeys(v)
+ case map[string]interface{}:
+ return convertKeys(v)
+ case bson.D:
+ for i, value := range v {
+ jsonValue, err := ConvertBSONValueToJSON(value.Value)
+ if err != nil {
+ return nil, err
+ }
+ v[i].Value = jsonValue
+ }
+ return MarshalD(v), nil
+ case MarshalD:
+ return v, nil
+ case []interface{}: // array
+ for i, value := range v {
+ jsonValue, err := ConvertBSONValueToJSON(value)
+ if err != nil {
+ return nil, err
+ }
+ v[i] = jsonValue
+ }
+ return v, nil
+
+ case string:
+ return v, nil // require no conversion
+
+ case int:
+ return json.NumberInt(v), nil
+
+ case bson.ObjectId: // ObjectId
+ return json.ObjectId(v.Hex()), nil
+
+ case bson.Decimal128:
+ return json.Decimal128{v}, nil
+
+ case time.Time: // Date
+ return json.Date(v.Unix()*1000 + int64(v.Nanosecond()/1e6)), nil
+
+ case int64: // NumberLong
+ return json.NumberLong(v), nil
+
+ case int32: // NumberInt
+ return json.NumberInt(v), nil
+
+ case float64:
+ return json.NumberFloat(v), nil
+
+ case float32:
+ return json.NumberFloat(float64(v)), nil
+
+ case []byte: // BinData (with generic type)
+ data := base64.StdEncoding.EncodeToString(v)
+ return json.BinData{0x00, data}, nil
+
+ case bson.Binary: // BinData
+ data := base64.StdEncoding.EncodeToString(v.Data)
+ return json.BinData{v.Kind, data}, nil
+
+ case mgo.DBRef: // DBRef
+ return json.DBRef{v.Collection, v.Id, v.Database}, nil
+
+ case bson.DBPointer: // DBPointer
+ return json.DBPointer{v.Namespace, v.Id}, nil
+
+ case bson.RegEx: // RegExp
+ return json.RegExp{v.Pattern, v.Options}, nil
+
+ case bson.MongoTimestamp: // Timestamp
+ timestamp := int64(v)
+ return json.Timestamp{
+ Seconds: uint32(timestamp >> 32),
+ Increment: uint32(timestamp),
+ }, nil
+
+ case bson.JavaScript: // JavaScript
+ var scope interface{}
+ var err error
+ if v.Scope != nil {
+ scope, err = ConvertBSONValueToJSON(v.Scope)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return json.JavaScript{v.Code, scope}, nil
+
+ default:
+ switch x {
+ case bson.MinKey: // MinKey
+ return json.MinKey{}, nil
+
+ case bson.MaxKey: // MaxKey
+ return json.MaxKey{}, nil
+
+ case bson.Undefined: // undefined
+ return json.Undefined{}, nil
+ }
+ }
+
+ return nil, fmt.Errorf("conversion of BSON value '%v' of type '%T' not supported", x, x)
+}
+
+// GetBSONValueAsJSON is equivalent to ConvertBSONValueToJSON, but does not mutate its argument.
+func GetBSONValueAsJSON(x interface{}) (interface{}, error) {
+ switch v := x.(type) {
+ case nil:
+ return nil, nil
+ case bool:
+ return v, nil
+
+ case *bson.M: // document
+ doc, err := getConvertedKeys(*v)
+ if err != nil {
+ return nil, err
+ }
+ return doc, err
+ case bson.M: // document
+ return getConvertedKeys(v)
+ case map[string]interface{}:
+ return getConvertedKeys(v)
+ case bson.D:
+ out := bson.D{}
+ for _, value := range v {
+ jsonValue, err := GetBSONValueAsJSON(value.Value)
+ if err != nil {
+ return nil, err
+ }
+ out = append(out, bson.DocElem{
+ Name: value.Name,
+ Value: jsonValue,
+ })
+ }
+ return MarshalD(out), nil
+ case MarshalD:
+ out, err := GetBSONValueAsJSON(bson.D(v))
+ if err != nil {
+ return nil, err
+ }
+ return MarshalD(out.(bson.D)), nil
+ case []interface{}: // array
+ out := []interface{}{}
+ for _, value := range v {
+ jsonValue, err := GetBSONValueAsJSON(value)
+ if err != nil {
+ return nil, err
+ }
+ out = append(out, jsonValue)
+ }
+ return out, nil
+
+ case string:
+ return v, nil // require no conversion
+
+ case int:
+ return json.NumberInt(v), nil
+
+ case bson.ObjectId: // ObjectId
+ return json.ObjectId(v.Hex()), nil
+
+ case bson.Decimal128:
+ return json.Decimal128{v}, nil
+
+ case time.Time: // Date
+ return json.Date(v.Unix()*1000 + int64(v.Nanosecond()/1e6)), nil
+
+ case int64: // NumberLong
+ return json.NumberLong(v), nil
+
+ case int32: // NumberInt
+ return json.NumberInt(v), nil
+
+ case float64:
+ return json.NumberFloat(v), nil
+
+ case float32:
+ return json.NumberFloat(float64(v)), nil
+
+ case []byte: // BinData (with generic type)
+ data := base64.StdEncoding.EncodeToString(v)
+ return json.BinData{0x00, data}, nil
+
+ case bson.Binary: // BinData
+ data := base64.StdEncoding.EncodeToString(v.Data)
+ return json.BinData{v.Kind, data}, nil
+
+ case mgo.DBRef: // DBRef
+ return json.DBRef{v.Collection, v.Id, v.Database}, nil
+
+ case bson.DBPointer: // DBPointer
+ return json.DBPointer{v.Namespace, v.Id}, nil
+
+ case bson.RegEx: // RegExp
+ return json.RegExp{v.Pattern, v.Options}, nil
+
+ case bson.MongoTimestamp: // Timestamp
+ timestamp := int64(v)
+ return json.Timestamp{
+ Seconds: uint32(timestamp >> 32),
+ Increment: uint32(timestamp),
+ }, nil
+
+ case bson.JavaScript: // JavaScript
+ var scope interface{}
+ var err error
+ if v.Scope != nil {
+ scope, err = GetBSONValueAsJSON(v.Scope)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return json.JavaScript{v.Code, scope}, nil
+
+ default:
+ switch x {
+ case bson.MinKey: // MinKey
+ return json.MinKey{}, nil
+
+ case bson.MaxKey: // MaxKey
+ return json.MaxKey{}, nil
+
+ case bson.Undefined: // undefined
+ return json.Undefined{}, nil
+ }
+ }
+
+ return nil, fmt.Errorf("conversion of BSON value '%v' of type '%T' not supported", x, x)
+}