slides
Transit-Ruby
David Chelimsky
Chicago Ruby Meetup
2 September 2014
Chicago Ruby Meetup
2 September 2014
Problem / Goals
- convey values between programs written in different languages
- types
- optional schema
- extensibility
- good performance
- reach (including browser)
Transit
- https://github.com/cognitect/transit-format
- http://cognitect.github.io/transit-tour/
- tag based wire format designed by Rich Hickey
- rides on top of JSON and MessagePack (reach, performance)
- language-specific implementations from Cognitect (https://github.com/cognitect/?query=transit)
- Ruby, JRuby (this week), Java, Clojure, JavaScript, ClojureScript, Python
- additional implementations (in various stages of development)
- Erlang, OCaml, Go, Dart, Scala
Types
- ground types (generally represented as/is in JSON or MessagePack)
- scalar
- null, string, boolean, integer, float, byte array,
- composite
- array, map
- scalar
- built-in extension types
- scalar
- keyword, symbol, arbitrary precision integer and decimal, point in
time, uuid, uri, char, quoted value
- special numbers (NaN, INF, -INF) coming this week
- keyword, symbol, arbitrary precision integer and decimal, point in
time, uuid, uri, char, quoted value
- composite
- set, list, map w/ composite keys, link
- scalar
- custom extensions
- use exactly same mechanism as built-in extension types
transit-ruby
- gem install transit-ruby
- https://github.com/cognitect/transit-ruby
- http://rubydoc.info/gems/transit-ruby
- http://rubygems.org/gems/transit-ruby
require 'transit' ######################## writer = Transit::Writer.new(:json_verbose, STDOUT) writer.write({:answer => 42}) # => "{\"~:answer\":42}" reader = Transit::Reader.new(:json, StringIO.new("{\"~:answer\":42}")) reader.read # => {:answer=>42} ######################## writer = Transit::Writer.new(:json, STDOUT) writer.write({:answer => 42}) # => "[\"^ \",\"~:answer\",42]" reader = Transit::Reader.new(:json, StringIO.new("[\"^ \",\"~:answer\",42]")) reader.read # => {:answer=>42}
Type Mappings
Transit type | Write accepts | Read returns |
---|---|---|
null | nil | nil |
string | String | String |
boolean | true, false | true, false |
integer | Fixnum, Bignum | Fixnum, Bignum |
float | Float | Float |
keyword | Symbol | Symbol |
big decimal | BigDecimal | BigDecimal |
big integer | Fixnum, Bignum | Fixnum, Bignum |
time | DateTime, Date, Time | DateTime |
uri | Addressable::URI, URI | Addressable::URI |
uuid | Transit::UUID | Transit::UUID |
char | Transit::TaggedValue | String |
array | Array | Array |
list | Transit::TaggedValue | Array |
set | Set | Set |
map | Hash | Hash |
bytes | Transit::ByteArray | Transit::ByteArray |
link | Transit::Link | Transit::Link |
Special case type mappings
Transit type | Write accepts | Read returns |
---|---|---|
point in time | DateTime, Date, Time | DateTime |
uri | Addressable::URI, URI | Addressable::URI |
- Ruby Date, Time, and DateTime all have subtly different semantics
- DateTime is richer and parses Strings faster
- Addressable::URI supports
- more protocols than URI
- UTF-8
Special case type mappings: Transit::Types
Transit type | Write accepts | Read returns |
---|---|---|
symbol | Transit::Symbol | Transit::Symbol |
uuid | Transit::UUID | Transit::UUID |
bytes | Transit::ByteArray | Transit::ByteArray |
link | Transit::Link | Transit::Link |
- Transit::Symbol is not a Ruby Symbol
- those map to transit Keywords
- Transit::Link
Special case type mappings: TaggedValues
Transit type | Write accepts | Read returns |
---|---|---|
char | Transit::TaggedValue | String |
list | Transit::TaggedValue | Array |
More on TaggedValues
- everything is a tagged value in the wire format
- implementations handle all the built-in types
- but what about custom types?
- Transit::TaggedValue is the default when a reader encounters a tag it doesn't understand
require 'transit' require 'json' reader = Transit::Reader.new(:json, StringIO.new({"~#unrecognized-tag" => [:some, :data]}.to_json)) reader.read # => #<Transit::TaggedValue:... @tag="unrecognized-tag", @rep=["some", "data"]>
- makes everything round-tripable
- supports dumb middle-men
Extensibility
- define a semantic type
- immutable values
- language independent
- choose a tag
- single capital letter for scalars
- 2 or more chars for composite types
- implement write and read handlers
- only in the languages that need to write or read
- TaggedValues allow you to pass through other implementations without handlers
Example
# semantic type: point # tag: "point" # rep: [int,int] require 'ostruct' Point = Struct.new(:x,:y) do def to_a; [x,y] end end class PointWriteHandler def tag(_) "point" end def rep(p) p.to_a end def string_rep(_) nil end end io = StringIO.new('','w+') writer = Transit::Writer.new(:json_verbose, io, :handlers => {Point => PointWriteHandler.new}) writer.write(Point.new(37,42)) io.string # => "{\"~#point\":[37,42]}\n" class PointReadHandler def from_rep(rep) Point.new(*rep) end end reader = Transit::Reader.new(:json, StringIO.new(io.string), :handlers => {"point" => PointReadHandler.new}) reader.read # => #<struct Point x=37, y=42>