io_serializable 0.1.0
io_serializable
A Crystal library that provides binary serialization and deserialization for Crystal objects. This library extends the IO class to allow objects to be written to and read from binary streams.
Installation
-
Add the dependency to your
shard.yml
:dependencies: io_serializable: github: dsennerlov/io_serializable
-
Run
shards install
Usage
require "io_serializable"
Basic Usage
Include the IO::Serializable
module in your class to make it serializable:
class Person
include IO::Serializable
property name : String
property age : Int32
property is_active : Bool
property salary : Float64
property grade : Char
property optional_field : String?
def initialize(@name = "", @age = 0, @is_active = false, @salary = 0.0, @grade = 'F', @optional_field = nil)
end
end
Serializing to Binary
# Create an instance
person = Person.new(
name: "Alice Cooper",
age: 30,
is_active: true,
salary: 75000.50,
grade: 'A',
optional_field: "Some data"
)
# Serialize to a memory buffer
io = IO::Memory.new
person.to_io(io)
# Get the binary data
binary_data = io.to_slice
Deserializing from Binary
# Assuming you have binary data in a buffer
io = IO::Memory.new(binary_data)
# Deserialize back to an object
restored_person = Person.from_io(io)
# Now you can use the restored object
puts restored_person.name # => "Alice Cooper"
Supported Types
The library supports serialization of the following types:
- Primitive types:
- Integers:
Int8
,Int16
,Int32
,Int64
,UInt8
,UInt16
,UInt32
,UInt64
- Floating point:
Float32
,Float64
Bool
Char
String
- Integers:
- Nilable versions of all the above types
- Nested objects that also include
IO::Serializable
Note:
Symbol
type is not supported for serialization due to limitations in Crystal's Symbol handling.
Nested Objects
You can nest serializable objects:
class Address
include IO::Serializable
property street : String
property city : String
def initialize(@street = "", @city = "")
end
end
class Employee
include IO::Serializable
property name : String
property address : Address
property salary : Float64
def initialize(@name = "", @address = Address.new, @salary = 0.0)
end
end
# Usage
employee = Employee.new(
name: "John Doe",
address: Address.new(street: "123 Main St", city: "Anytown"),
salary: 85000.0
)
# Serialize
io = IO::Memory.new
employee.to_io(io)
# Deserialize
io.rewind
restored_employee = Employee.from_io(io)
File I/O
You can write serialized objects directly to files and read them back:
class Product
include IO::Serializable
property id : Int32
property name : String
property price : Float64
property is_available : Bool
property category : String?
def initialize(
@id = 0,
@name = "",
@price = 0.0,
@is_available = false,
@category = nil
)
end
end
# Create a product
product = Product.new(
id: 123,
name: "Crystal Programming Guide",
price: 29.99,
is_available: true,
category: "Programming"
)
# Write to file
File.open("product.bin", "wb") do |file|
product.to_io(file)
end
# Read from file
restored_product = File.open("product.bin", "rb") do |file|
Product.from_io(file)
end
# Verify the data
puts restored_product.name # => "Crystal Programming Guide"
puts restored_product.price # => 29.99
# Clean up
File.delete("product.bin")
Note: Always use binary mode ("wb"
for writing, "rb"
for reading) when working with serialized data.
Byte Format
You can specify the byte format when serializing:
# Use big endian format
io = IO::Memory.new
person.to_io(io, IO::ByteFormat::BigEndian)
# Use little endian format
io = IO::Memory.new
person.to_io(io, IO::ByteFormat::LittleEndian)
Field Annotations
Skip Annotation
You can use the IO::Field
annotation with the skip
option to exclude specific fields from serialization:
class User
include IO::Serializable
property id : Int32
property username : String
property email : String
# This field will be excluded from serialization
@[IO::Field(skip: true)]
property password : String
# This field will be excluded from serialization
@[IO::Field(skip: true)]
property temporary_token : String?
def initialize(@id = 0, @username = "", @email = "", @password = "", @temporary_token = nil)
end
end
# Usage
user = User.new(
id: 1,
username: "johndoe",
email: "john@example.com",
password: "secret123",
temporary_token: "temp-token-xyz"
)
# Serialize - password and temporary_token will be skipped
io = IO::Memory.new
user.to_io(io)
# Deserialize - password and temporary_token will retain their default values
io.rewind
restored_user = User.from_io(io)
puts restored_user.password # => ""
puts restored_user.temporary_token # => nil
This is useful for:
- Excluding sensitive data (like passwords) from serialization
- Skipping temporary or runtime-only fields that shouldn't be persisted
- Optimizing the binary size by excluding unnecessary fields
TODO
- Add annotiation for BytFormat
- Add support for Arrays
Development
To run the tests:
crystal spec
Contributing
- Fork it (https://github.com/dsennerlov/io_serializable/fork)
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
Contributors
- David Sennerlöv - creator and maintainer
io_serializable
- 0
- 0
- 0
- 0
- 0
- 3 days ago
- March 10, 2025
MIT License
Fri, 28 Mar 2025 07:29:58 GMT