bin 1.0.0
bin: ^1.0.0 copied to clipboard
Typed binary data structures for Dart (without code generation).
🗑️ Bin #
Declare typed binary data structures in Dart (without code generation) using the excellent binarize package.
import 'package:bin/bin.dart';
/// A phone number category.
enum PhoneKind { home, mobile, work }
final class AddressBook with Binnable {
final Bin<String> owner = Bin.utf8();
final Bin<Contact?> emergencyContact = Bin.nullableObj(Contact.new);
final Bin<List<Contact>> contacts = Bin.custom(list(obj(Contact.new)), []);
final Bin<Map<String, List<List<int>>>> groups = Bin.custom(
map(string.utf8(), list(list(uint32))),
{},
);
@override
Iterable<Binned> get bins => [owner, emergencyContact, contacts, groups];
}
final class Contact with Binnable { ... }
void main() {
final addressBook = AddressBook()
..owner.value = 'Bob'
..emergencyContact.value = (Contact()
..id.value = 7
..name.value = 'Alice'
..phoneNumbers.value = {PhoneKind.mobile: '1-234-5678'})
..contacts.value = [
Contact()
..id.value = 7
..name.value = 'Alice'
..phoneNumbers.value = {
PhoneKind.home: '1-234-5678',
PhoneKind.mobile: '1-234-5678',
PhoneKind.work: '1-234-5678',
}
..addressLines.value = [
'1000 Home Street',
'Shore Town, TS 12345',
],
Contact()
..id.value = 12
..name.value = 'Eve'
..phoneNumbers.value = {PhoneKind.mobile: '1-234-5678'},
]
..groups.value = {
'family': [
[7],
[12],
],
'work': [
[12],
],
};
final bytes = encode(addressBook);
final decoded = decode(AddressBook.new, bytes);
}
The above was taken from the complete example.
📦 Installation #
dart pub add bin
📖 Usage #
Declaring Types #
Create a class with Binnable, define fields as Bin<T>, and return them in binary order.
final class Contact with Binnable {
final Bin<int> id = Bin.uint32();
final Bin<String> name = Bin.utf8();
final Bin<Map<PhoneKind, String>> phoneNumbers = Bin.custom(
map(enumeration(PhoneKind.values), string.utf8()),
{},
);
@override
Iterable<Binned> get bins => [id, name, phoneNumbers];
}
Bin comes with many constructors that allow you to create different types of primitive values:
final id = Bin.int32();
final weight = Bin.float64();
final name = Bin.utf8();
Bins can be composed to create composite types using lists:
final Bin<List<String>> names = Bin.custom(list(string.utf8()), <String>[]);
Maps can be used as well:
final Bin<Map<String, int>> lookup = Bin.custom(
map(string.utf8(), uint32),
<String, int>{},
);
You may nest lists and maps as much as you like.
Bin.custom enables you to use any Binarize PayloadType<T> as a field type, along with a default value.
You must implement a bins property that returns the fields in binary order.
@override
Iterable<Binned> get bins => [id, name, phoneNumbers];
Warning
The order of bins is the binary schema. Changing the order changes the bytes.
Binarize 101 #
Bin uses Binarize under the hood to perform serialization, and associates each class field with a Binarize PayloadType<T>.
final count = Bin.uint32();
final name = Bin.utf8();
final tags = Bin.custom(list(string.utf8()), <String>[]);
final kind = Bin.custom(enumeration(PhoneKind.values), PhoneKind.home);
Payload types nest inside each other. A payload describes one value, and collection payloads accept other payloads:
final ids = Bin.custom(list(uint32), <int>[]);
final rows = Bin.custom(list(list(uint32)), <List<int>>[]);
final lookup = Bin.custom(
map(string.utf8(), list(uint32)),
<String, List<int>>{},
);
Objects work the same way with obj:
final contact = Bin.obj(Contact.new);
final contacts = Bin.custom(list(obj(Contact.new)), <Contact>[]);
final pages = Bin.custom(list(list(obj(Contact.new))), <List<Contact>>[]);
Common payloads include:
- integers:
int8,int16,int32,int64,uint8,uint16,uint32,uint64 - floats and booleans:
float32,float64,boolean - strings:
string.utf8(),string.ascii(),string.latin1() - collections:
list(type),map(keyType, valueType),RawList(type, amount: n) - nullable values:
nil(type) - enums:
enumeration(MyEnum.values) - bytes and typed data:
Bytes(n),uint8List(),byteData(), and other typed list payloads
Length-prefixed payloads use compact defaults, but most accept a lengthType
when you need larger values:
final longNames = Bin.custom(
list(string.utf8(), lengthType: uint32),
<String>[],
);
Using Types #
Read and write field values through .value.
final contact = Contact()
..id.value = 7
..name.value = 'Alice'
..phoneNumbers.value = {PhoneKind.mobile: '1-234-5678'};
print(contact.name.value); // Alice
Nested objects are created from factories when decoded.
Encoding and Decoding #
Use encode and decode at the byte boundary.
final bytes = encode(contact);
final decoded = decode(Contact.new, bytes);
For streaming or embedded formats, use read(ByteReader) and write(ByteWriter) directly.