Class: RightSupport::Data::Serializer

Inherits:
Object
  • Object
show all
Defined in:
lib/right_support/data/serializer.rb

Overview

Utility class that implements true, lossless Ruby-to-JSON serialization. With a few small exceptions, this module can #dump any Ruby object in your VM, and later #load the exact same object from its serialized representation. It can also be used with encoding schemes other than JSON, e.g. msgpack.

This class works by transforming Ruby object graphs into an intermediate representation that consists solely of JSON-clean Ruby objects (String, Integer, …). This intermediate format is dubbed “JSONish”, and it can be transformed to JSON and back without losing data or creating ambiguity.

If you use the class-level (::load and ::dump) interface to this class, it always uses JSON serialization by default. If you construct an instance, you can control which encoding scheme to use, as well as which Hash key to use to mark a serialized object.

JSONish Object Representation

Most Ruby simple types (String, Integer, Float, true/false/nil) are represented by their corresponding JSON type. However, some JSONish values have special meaning:

* Strings beginning with a ':' represent a Ruby Symbol
* Strings in the ISO 8601 timestamp format represent a Ruby Time

To avoid ambiguity due to Ruby Strings that happen to look like a JSONish Time or Symbol, some Strings are “object-escaped,” meaning they are represented as a JSON object with _ruby_class:String.

Arbitrary Ruby objects are represented as a Hash that contains a special key ‘_ruby_class’. The other key/value pairs of the hash consist of the object’s instance variables. Any object whose state is solely contained in its instance variables is therefore eligible for serialization.

JSONish also has special-purpose logic for some Ruby built-ins, allowing them to be serialized even though their state is not contained in instance variables. The following are all serializable built-ins:

* Class
* Module
* String (see below).

Capabilities

The serializer can handle any Ruby object that uses instance variables to record state. It cleanly round-trips the following Ruby object types:

* Collections (Hash, Array)
* JSON-clean data (Numeric, String, true, false, nil)
* Symbol
* Time
* Class
* Module

Known Limitations

Cannot record the following kinds of state:

* Class variables of objects
* State that lives outside of instance variables, e.g. IO#fileno

Cannot cleanly round-trip the following:

* Objects that represent callable code: Proc, Lambda, etc.
* High-precision floating point numbers (due to truncation)
* Times with timezone other than UTC or precision greater than 1 sec
* Hash keys that are anything other than a String (depends on JSON parser)
* Hashes that contain a String key whose value is '_ruby_class'

Constant Summary collapse

Encoder =
nil
TIME_FORMAT =

Format string to use when sprintf’ing a JSONish Time

'%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2dZ'
TIME_PATTERN =

Pattern to match Strings against for object-escaping

/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/
CLASS_ESCAPE_KEY =

Special key used as a pseudo-instance-variable for Class and Module

'name'
STRING_ESCAPE_KEY =

Special key used as a pseudo-instance-variable for String

'value'
DEFAULT_OPTIONS =
{
    :marker  => '_ruby_class',
    :encoder => Encoder
}

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Serializer

Instantiate a Serializer instance.

Parameters:

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :encoder (#load, #dump)

    underlying data-encoder to use. Defaults to any available JSON gem.

  • :marker (Object)

    special object key to use when representing a serialized Ruby object. Defaults to ‘_ruby_class’.



123
124
125
126
127
# File 'lib/right_support/data/serializer.rb', line 123

def initialize(options={})
  options = DEFAULT_OPTIONS.merge(options)
  @marker = options[:marker]
  @encoder = options[:encoder]
end

Class Method Details

.dump(object) ⇒ Object



116
117
118
# File 'lib/right_support/data/serializer.rb', line 116

def self.dump(object)
  self.new.dump(object)
end

.load(data) ⇒ Object



112
113
114
# File 'lib/right_support/data/serializer.rb', line 112

def self.load(data)
  self.new.load(data)
end

Instance Method Details

#dump(object) ⇒ String

Returns JSON document representing the serialized object.

Parameters:

  • object (Object)

    any Ruby object

Returns:

  • (String)

    JSON document representing the serialized object



138
139
140
141
# File 'lib/right_support/data/serializer.rb', line 138

def dump(object)
  jsonish = object_to_jsonish(object)
  @encoder.dump(jsonish)
end

#load(data) ⇒ Object

Returns unserialized Ruby object.

Parameters:

  • data (String)

    valid JSON document representing a serialized object

Returns:

  • (Object)

    unserialized Ruby object



131
132
133
134
# File 'lib/right_support/data/serializer.rb', line 131

def load(data)
  jsonish = @encoder.load(data)
  jsonish_to_object(jsonish)
end