package

github.com/elbywan/bson.cr

0.3.6 / published Jul 26, 2024 / repository

A pure Crystal Implementation of the BSON Specification.

bson.cr

A pure Crystal implementation of the BSON specification.

Crystal CI GitHub tag (latest SemVer) GitHub

Reliability

This library passes the official corpus tests located in the mongodb/specifications repository.

A few minor modifications have been made to the tests to comply with Crystal specifics.

Installation

  1. Add the dependency to your shard.yml:

    dependencies:
      bson:
        github: elbywan/bson.cr
    
  2. Run shards install

API

Full API documentation is hosted here.

Usage

require "bson"

Constructors

# Create a BSON instance from a NamedTuple…
bson = BSON.new({
  hello: "world"
})

# …or a Hash…
bson = BSON.new({
  "hello" => "world"
})

# …or a hex binary representation…
bytes = "160000000268656c6c6f0006000000776f726c640000".hexbytes
bson = BSON.new(bytes)

# …or an IO…
bson = BSON.new(IO::Memory.new bytes)

# …or JSON data
bson = BSON.from_json(%({
  "hello": "world"
}))

# The BSON binary representation is stored in the data property
puts bson.data.hexstring
# => 160000000268656c6c6f0006000000776f726c640000

Append and fetch values

bson = BSON.new({
  hello: "world"
})

# Append values
bson["name"] = BSON.new({
  first_name: "John",
  last_name: "Doe"
})

# Fetch values
puts bson["name"].as(BSON).to_json
# => {"first_name":"John","last_name":"Doe"}
puts bson["404"]?
# => nil

# Append another BSON
other_bson = BSON.new({ other: "field" })
bson.append(other_bson)
puts bson["other"]
# => field

Iterate

bson = BSON.new({
  one: 1,
  two: 2.0,
  three: 3
})

# Enumerator
bson.each { |(key, value)|
  puts "#{key}, #{value}"
  # => one, 1
  # => two, 2.0
  # => three, 3
}

# Iterator
puts bson.each.map { |(key, value)|
  value.as(Number) + 1
}.to_a
# => [2, 3.0, 4]

Conversions

bson = BSON.new({
  one: 1,
  two: "2",
  binary: Slice[0_u8, 1_u8, 2_u8]
})

pp bson.to_h
# => {"one" => 1, "two" => "2", "binary" => Bytes[0, 1, 2]}

pp bson.each.to_a
# => [{"one", 1, Int32, nil}, {"two", "2", String, nil}, {"binary", Bytes[0, 1, 2], Binary, Generic}]

JSON

# Initialize from data in Relaxed Extended Json format.
# See: https://github.com/mongodb/specifications/blob/master/source/extended-json.rst
bson = BSON.new(%({
  "_id": {
       "$oid": "57e193d7a9cc81b4027498b5"
   },
   "Binary": {
       "$binary": {
           "base64": "o0w498Or7cijeBSpkquNtg==",
           "subType": "03"
       }
   },
   string: "String",
   number: 10.1
}))

# Serialize to Relaxed Extended Json format…
puts bson.to_json
# => {"_id":{"$oid":"57e193d7a9cc81b4027498b5"},"Binary":{"$binary":{"base64":"o0w498Or7cijeBSpkquNtg==","subType":"03"}},"string":"String","number":10.1}

# …or Canonical Extended Json.
puts bson.to_canonical_extjson
# => {"_id":{"$oid":"57e193d7a9cc81b4027498b5"},"Binary":{"$binary":{"base64":"o0w498Or7cijeBSpkquNtg==","subType":"03"}},"string":"String","number":{"$numberDouble":"10.1"}}

Serialization

class Data
  include BSON::Serializable
  include JSON::Serializable

  property field : String
  property counter : Int32

  property nested : Nested

  class Nested
    include BSON::Serializable
    include JSON::Serializable

    property array : Array(String | Int32)
  end
end

data = Data.from_json(%({
  "field": "value",
  "counter": 0,
  "nested": {
    "array": [
      "element",
      1
    ]
  }
}))

puts data.to_json
# => {"field":"value","counter":0,"nested":{"array":["element",1]}}

puts data.to_bson.data.hexstring
# => 52000000026669656c64000600000076616c75650010636f756e7465720000000000036e65737465640027000000046172726179001b00000002300008000000656c656d656e740010310001000000000000

puts Data.from_bson(data.to_bson).to_json
# => {"field":"value","counter":0,"nested":{"array":["element",1]}}

Validating ObjectIds

You can validate that a provided string is a valid MongoDB ObjectId before instantiating it with .new() with:

# => true
p BSON::ObjectId.validate("57e193d7a9cc81b4027498b5")

# => false
p BSON::ObjectId.validate("qwerty")

# => false
p BSON::ObjectId.validate("1234567890abcdefghijklmn")

Decimal128

The Decimal128 code has been hastily copied from the bson-ruby library. It works, but performance is low because it uses an intermediate String representation.

Contributing

  1. Fork it (https://github.com/elbywan/bson/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Contributors

Credit

API

  • Array(T)

    An Array is an ordered, integer-indexed collection of objects of type T.

  • BSON

    BSON is a binary format in which zero or more ordered key/value pairs are stored as a single entity.

  • BigInt

    A BigInt can represent arbitrarily large integers.

  • Bool

    Bool has only two possible values: true and false.

  • Float64
  • Hash(K, V)

    A Hash represents a collection of key-value mappings, similar to a dictionary.

  • Int32
  • Int64
  • NamedTuple(**T)

    A named tuple is a fixed-size, immutable, stack-allocated mapping of a fixed set of keys to values.

  • Nil

    The Nil type has only one possible value: nil.

  • Regex

    A Regex represents a regular expression, a pattern that describes the contents of strings.

  • Slice(T)

    A Slice is a Pointer with an associated size.

  • String

    A String represents an immutable sequence of UTF-8 characters.

  • Time

    Time represents a date-time instant in incremental time observed in a specific time zone.

  • UUID

    Represents a UUID (Universally Unique IDentifier).