Announcing YASL: Yet Another Serialization Library!
I know what you're thinking: "What?! Another serialization library!?! Why does it sound suspiciously like YAML? Isn't YAML enough?"
Good questions!
The short answer is YAML isn't available in Opal Ruby inside web browsers on the client-side, and Ruby Marshal raises errors by design whenever your objects reference unserializable objects like Proc.
The long answer is I needed a library that was written in Pure Ruby to ensure that it worked the same exact way in Opal, JRuby, and standard MRI Ruby so that I could use in network calls made by Glimmer applications, whether Glimmer DSL for SWT (JRuby), Glimmer DSL for Opal (Opal), or Glimmer DSL for Tk (MRI). Also, it had to silently ignore unserializable objects. Last but not least, developers are busy solving business domain problems, so the library has to require zero configuration. In other words, I don't want to fuss around with as_json methods or serializer configuration classes to manually specify attributes for JSON serialization. I just want serialization to work by passing objects in, period.
YASL took me exactly one week to write test-first till I reached the initial release. Not bad, right?
Don't get me wrong! When working strictly in standard MRI Ruby, I just use YAML. However, when building cross-Ruby apps in Glimmer that need to work in both desktop and web, YASL is the way to go!
Here is a quick intro taken straight out of the README. Enjoy!
A pure Ruby serialization library that works across different Ruby implementations like Opal and JRuby as an alternative to YAML/Marshal.
- Portablity across different Ruby implementations, especially Opal and JRuby.
- Zero required configuration. Developers are too busy solving business domain problems to worry about low-level serialization details.
- Silently ignore non-serializable objects (unlike Marshal), such as
Proc
,Binding
, andIO
. - No special performance requirements. No high throughput usage. Average Internet speeds.
- Ensure system safety through secure deserialization.
- JSON encoding is good enough. No need for premature optimization.
To serialize, use the YASL#dump(object)
method.
Keep in mind that YASL::UNSERIALIZABLE_DATA_TYPES
classes are unserializable, and will serialize as nil
(feel free to add more classes that you would like filtered out):
Proc
, Binding
, IO
, File::Stat
, Dir
, BasicSocket
, MatchData
, Method
, UnboundMethod
, Thread
, ThreadGroup
, Continuation
Example (from samples/dump_basic.rb):
require 'yasl'
require 'date'
class Car
attr_accessor :make,
:model,
:year,
:registration_time,
:registration_date,
:registration_date_time,
:complex_number,
:complex_polar_number,
:rational_number
end
car = Car.new
car.make = 'Mitsubishi'
car.model = 'Eclipse'
car.year = '2002'
car.registration_time = Time.new(2003, 10, 19, 10, 39, 37.092, '-03:00')
car.registration_date = Date.new(2003, 10, 19)
car.registration_date_time = DateTime.new(2003, 10, 19, 10, 39, 37.092, '-03:00')
car.complex_number = Complex(2,37)
car.complex_polar_number = Complex.polar(-23,28)
car.rational_number = Rational(22/7)
dump = YASL.dump(car)
puts dump.inspect
# => "{\"_class\":\"Car\",\"_id\":1,\"_instance_variables\":{\"make\":\"Mitsubishi\",\"model\":\"Eclipse\",\"year\":\"2002\",\"registration_time\":{\"_class\":\"Time\",\"_data\":[0,2452932,49177,\"12644383719423828125/137438953472\",-10800,2299161.0]},\"registration_date\":{\"_class\":\"Date\",\"_data\":[0,2452932,0,0,0,2299161.0]},\"registration_date_time\":{\"_class\":\"DateTime\",\"_data\":[0,2452932,49177,92000000,-10800,2299161.0]},\"complex_number\":{\"_class\":\"Complex\",\"_data\":\"2+37i\"},\"complex_polar_number\":{\"_class\":\"Complex\",\"_data\":\"22.13993492521203-6.230833131080988i\"},\"rational_number\":{\"_class\":\"Rational\",\"_data\":\"3/1\"}}}"
YASL automatically detects cycles when serializing bidirectional object references.
Example (from samples/dump_cycle.rb):
require 'yasl'
require 'date'
require 'set'
class Car
attr_accessor :make,
:model,
:year,
:owner
end
class Person
class << self
def reset_count!
@count = 0
end
def increment_count!
@count ||= 0
@count += 1
end
def reset_class_count!
@@class_count = 0
end
def increment_class_count!
@@class_count = 0 unless defined?(@@class_count)
@@class_count += 1
end
end
attr_accessor :name, :dob, :cars
def initialize
self.class.increment_count!
self.class.increment_class_count!
end
end
person = Person.new
person.name = 'Sean Hux'
person.dob = Time.new(2017, 10, 17, 10, 3, 4)
car = Car.new
car.make = 'Mitsubishi'
car.model = 'Eclipse'
car.year = '2002'
car.owner = person
person.cars = [car]
dump = YASL.dump(car)
puts dump.inspect
# => "{\"_class\":\"Car\",\"_id\":1,\"_instance_variables\":{\"make\":\"Mitsubishi\",\"model\":\"Eclipse\",\"owner\":{\"_class\":\"Person\",\"_id\":1,\"_instance_variables\":{\"cars\":{\"_class\":\"Array\",\"_data\":[{\"_class\":\"Car\",\"_id\":1}]},\"dob\":{\"_class\":\"Time\",\"_data\":[0,2458044,50584,0,-14400,2299161.0]},\"name\":\"Sean Hux\"}},\"year\":\"2002\"}}"
To deserialize, use the YASL#load(data, whitelist_classes: [])
method. The value of whitelist_classes
must mention all classes expected to appear in the serialized data to load. This is required to ensure software security by not allowing arbitrary unexpected classes to be deserialized.
By default, only YASL::RUBY_BASIC_DATA_TYPES
classes are deserialized:
NilClass
, String
, Integer
, Float
, TrueClass
, FalseClass
, Time
, Date
, Complex
, Rational
, Regexp
, Symbol
, Set
, Range
, Array
, Hash
Example (from samples/load_basic.rb):
require 'yasl'
require 'date'
class Car
attr_accessor :make,
:model,
:year,
:registration_time,
:registration_date,
:registration_date_time,
:complex_number,
:complex_polar_number,
:rational_number
end
car = Car.new
car.make = 'Mitsubishi'
car.model = 'Eclipse'
car.year = '2002'
car.registration_time = Time.new(2003, 10, 19, 10, 39, 37.092, '-03:00')
car.registration_date = Date.new(2003, 10, 19)
car.registration_date_time = DateTime.new(2003, 10, 19, 10, 39, 37.092, '-03:00')
car.complex_number = Complex(2,37)
car.complex_polar_number = Complex.polar(-23,28)
car.rational_number = Rational(22/7)
dump = YASL.dump(car)
car2 = YASL.load(dump, whitelist_classes: [Car])
puts car2.make
# => Mitsubishi
puts car2.model
# => Eclipse
puts car2.year
# => 2002
puts car2.registration_time
# => 2003-10-19 10:39:37 -0300
puts car2.registration_date
# => 2003-10-19
puts car2.registration_date_time
# => 2003-10-19T10:39:37-03:00
puts car2.complex_number
# => 2+37i
puts car2.complex_polar_number
# => 22.13993492521203-6.230833131080988i
puts car2.rational_number
# => 3/1
YASL automatically restores cycles when deserializing bidirectional object references.
Example (from samples/load_cycle.rb):
require 'yasl'
require 'date'
require 'set'
class Car
attr_accessor :make,
:model,
:year,
:owner
end
class Person
class << self
def reset_count!
@count = 0
end
def increment_count!
@count ||= 0
@count += 1
end
def reset_class_count!
@@class_count = 0
end
def increment_class_count!
@@class_count = 0 unless defined?(@@class_count)
@@class_count += 1
end
end
attr_accessor :name, :dob, :cars
def initialize
self.class.increment_count!
self.class.increment_class_count!
end
end
person = Person.new
person.name = 'Sean Hux'
person.dob = Time.new(2017, 10, 17, 10, 3, 4)
car = Car.new
car.make = 'Mitsubishi'
car.model = 'Eclipse'
car.year = '2002'
car.owner = person
person.cars = [car]
dump = YASL.dump(car)
car2 = YASL.load(dump, whitelist_classes: [Car, Person])
puts car2.make
# => Mitsubishi
puts car2.model
# => Eclipse
puts car2.year
# => 2002
puts car2.owner
# => #<Person:0x00007ffdf008dc20>
puts car2.owner.name
# => Sean Hux
puts car2.owner.dob
# => 2017-10-17 10:03:04 -0400
puts car2.owner.cars.inspect
# => [#<Car:0x00007ffdf008e120 @make="Mitsubishi", @model="Eclipse", @year="2002", @owner=#<Person:0x00007ffdf008dc20 @name="Sean Hux", @dob=2017-10-17 10:03:04 -0400, @cars=[...]>>]
puts car2.inspect
# => #<Car:0x00007ffdf008e120 @make="Mitsubishi", @model="Eclipse", @year="2002", @owner=#<Person:0x00007ffdf008dc20 @name="Sean Hux", @dob=2017-10-17 10:03:04 -0400, @cars=[#<Car:0x00007ffdf008e120 ...>]>>
Copyright (c) 2020 Andy Maleh.
No comments:
Post a Comment