d_bincode 2.0.0 copy "d_bincode: ^2.0.0" to clipboard
d_bincode: ^2.0.0 copied to clipboard

A bincode like serialization library for Dart. Manually writen, provides encoding for binary data structures, mirroring the bincode specification (Rust) minimalistically.

example/d_bincode_example.dart

import 'dart:typed_data';

import 'package:d_bincode/d_bincode.dart';

class NestedData implements BincodeCodable {
  int nestedId = 0;
  String nestedName = "Default Nested";

  NestedData();
  NestedData.create(this.nestedId, this.nestedName);

  @override
  Uint8List toBincode() {
    final writer = BincodeWriter();
    writer.writeI32(nestedId); // i32
    writer.writeString(nestedName); // String (variable length!)
    return writer.toBytes();
  }

  @override
  void fromBincode(Uint8List bytes, {bool unsafe = false}) {
    final reader = BincodeReader(bytes, unsafe: unsafe);
    nestedId = reader.readI32();
    nestedName = reader.readString();
  }

  @override
  String toString() {
    return 'NestedData(id: $nestedId, name: "$nestedName")';
  }
}

class FixedStruct implements BincodeCodable {
  int valueA = 0; // Example: i32 (4 bytes)
  double valueB = 0.0; // Example: f64 (8 bytes)
  bool flagC = false; // Example: bool (1 byte)

  FixedStruct();
  FixedStruct.create(this.valueA, this.valueB, this.flagC);

  @override
  Uint8List toBincode() {
    final writer = BincodeWriter();
    // ONLY fixed-size writes inside here
    writer.writeI32(valueA);
    writer.writeF64(valueB);
    writer.writeBool(flagC);
    return writer.toBytes();
  }

  @override
  void fromBincode(Uint8List bytes) {
    final reader = BincodeReader(bytes);
    valueA = reader.readI32();
    valueB = reader.readF64();
    flagC = reader.readBool();
  }

  @override
  String toString() => 'Fixed(A:$valueA, B:$valueB, C:$flagC)';
}

// --- Main Data Class (Comprehensive Example) ---
class MyData implements BincodeCodable {
  // --- Primitives ---
  int myU8 = 0;
  int myU16 = 0;
  int myU32 = 0;
  int myU64 = 0;
  int myI8 = 0;
  int myI16 = 0;
  int myI32 = 0;
  int myI64 = 0;
  double myF32 = 0.0;
  double myF64 = 0.0;
  bool myBool = false;

  // --- Optional Primitives ---
  int? myOptionU8;
  int? myOptionU16;
  int? myOptionU32;
  int? myOptionU64;
  int? myOptionI8;
  int? myOptionI16;
  int? myOptionI32;
  int? myOptionI64;
  double? myOptionF32;
  double? myOptionF64;
  bool? myOptionBool;

  // --- Strings ---
  String myString = "";
  String myFixedString = "";
  String myCleanFixedString = "";
  String? myOptionString;
  String? myOptionFixedString;
  String? myCleanOptionFixedString;

  // --- Specific Optional Structures ---
  Float32List? myOptionF32Triple;

  // --- Generic Collections ---
  List<int> myGenericList = [];
  Map<String, bool> myGenericMap = {};

  // --- Specialized Numeric Lists ---
  List<int> myInt8List = [];
  List<int> myInt16List = [];
  List<int> myInt32List = [];
  List<int> myInt64List = [];
  List<int> myUint8List = [];
  List<int> myUint16List = [];
  List<int> myUint32List = [];
  List<int> myUint64List = [];
  List<double> myFloat32List = [];
  List<double> myFloat64List = [];

  // --- Nested Objects ---
  // Example using ForCollection (variable size due to NestedData's string)
  NestedData myNestedCollection = NestedData();
  NestedData? myOptionalNestedCollection;

  // Example using ForFixed (FixedStruct has a truly fixed size: 13 bytes, no length will be added)
  FixedStruct myFixedStructInstance = FixedStruct();
  FixedStruct? myOptionalFixedStructInstance;

  // Example: List of fixed-size objects
  List<FixedStruct> myListOfFixedStructs = [];

  // --- BincodeCodable Implementation ---

  @override
  Uint8List toBincode({bool unchecked = false, int initialCapacity = 1024}) {
    final writer =
        BincodeWriter(initialCapacity: initialCapacity, unchecked: unchecked);

    // --- Write Primitives ---
    writer.writeU8(myU8); // u8
    writer.writeU16(myU16); // u16
    writer.writeU32(myU32); // u32
    writer.writeU64(myU64); // u64
    writer.writeI8(myI8); // i8
    writer.writeI16(myI16); // i16
    writer.writeI32(myI32); // i32
    writer.writeI64(myI64); // i64
    writer.writeF32(myF32); // f32
    writer.writeF64(myF64); // f64
    writer.writeBool(myBool); // bool

    // --- Write Optional Primitives ---
    writer.writeOptionU8(myOptionU8); // Option<u8>
    writer.writeOptionU16(myOptionU16); // Option<u16>
    writer.writeOptionU32(myOptionU32); // Option<u32>
    writer.writeOptionU64(myOptionU64); // Option<u64>
    writer.writeOptionI8(myOptionI8); // Option<i8>
    writer.writeOptionI16(myOptionI16); // Option<i16>
    writer.writeOptionI32(myOptionI32); // Option<i32>
    writer.writeOptionI64(myOptionI64); // Option<i64>
    writer.writeOptionF32(myOptionF32); // Option<f32>
    writer.writeOptionF64(myOptionF64); // Option<f64>
    writer.writeOptionBool(myOptionBool); // Option<bool>

    // --- Write Strings ---
    writer.writeString(myString); // String
    writer.writeFixedString(myFixedString, 32); // [u8; 32]
    writer.writeFixedString(myCleanFixedString, 16); // [u8; 16]
    writer.writeOptionString(myOptionString); // Option<String>
    writer.writeOptionFixedString(myOptionFixedString, 20); // Option<[u8; 20]>
    writer.writeOptionFixedString(
        myCleanOptionFixedString, 24); // Option<[u8; 24]>

    // --- Write Specific Optional Structures ---
    writer.writeOptionF32Triple(myOptionF32Triple); // Option<[f32; 3]>

    // --- Write Generic Collections ---
    writer.writeList<int>(
        myGenericList, (value) => writer.writeU32(value)); // Vec<T>
    writer.writeMap<String, bool>(
        // HashMap<T, E>
        myGenericMap,
        (key) => writer.writeString(key),
        (value) => writer.writeBool(value));

    // --- Write Specialized Numeric Lists ---
    writer.writeInt8List(myInt8List); // Vec<i8>
    writer.writeInt16List(myInt16List); // Vec<i16>
    writer.writeInt32List(myInt32List); // Vec<i32>
    writer.writeInt64List(myInt64List); // Vec<i64>
    writer.writeUint8List(myUint8List); // Vec<u8>
    writer.writeUint16List(myUint16List); // Vec<u16>
    writer.writeUint32List(myUint32List); // Vec<u32>
    writer.writeUint64List(myUint64List); // Vec<u64>
    writer.writeFloat32List(myFloat32List); // Vec<f32>
    writer.writeFloat64List(myFloat64List); // Vec<f64>

    // --- Write Nested Objects ---

    // Use ForCollection for NestedData (variable size)
    writer.writeNestedValueForCollection(
        myNestedCollection); // NestedData - Vec<T>
    writer.writeOptionNestedValueForCollection(
        myOptionalNestedCollection); // Option<NestedData> - Option<Vec<T>>

    // Use ForFixed for FixedStruct
    writer.writeNestedValueForFixed(myFixedStructInstance); // FixedStruct - T
    writer.writeOptionNestedValueForFixed(
        myOptionalFixedStructInstance); // Option<FixedStruct> - Option<T>

    // List of Fixed: Writes list length, then for each element write raw element_bytes
    writer.writeList<FixedStruct>(myListOfFixedStructs, (item) {
      // u64 length before
      writer.writeNestedValueForFixed(item); // T element
    });

    return writer.toBytes();
  }

  @override
  void fromBincode(Uint8List bytes, {bool unsafe = false}) {
    final reader = BincodeReader(bytes, unsafe: unsafe);

    // --- Read Primitives ---
    myU8 = reader.readU8();
    myU16 = reader.readU16();
    myU32 = reader.readU32();
    myU64 = reader.readU64();
    myI8 = reader.readI8();
    myI16 = reader.readI16();
    myI32 = reader.readI32();
    myI64 = reader.readI64();
    myF32 = reader.readF32();
    myF64 = reader.readF64();
    myBool = reader.readBool();

    // --- Read Optional Primitives ---
    myOptionU8 = reader.readOptionU8();
    myOptionU16 = reader.readOptionU16();
    myOptionU32 = reader.readOptionU32();
    myOptionU64 = reader.readOptionU64();
    myOptionI8 = reader.readOptionI8();
    myOptionI16 = reader.readOptionI16();
    myOptionI32 = reader.readOptionI32();
    myOptionI64 = reader.readOptionI64();
    myOptionF32 = reader.readOptionF32();
    myOptionF64 = reader.readOptionF64();
    myOptionBool = reader.readOptionBool();

    // --- Read Strings ---
    myString = reader.readString();
    myFixedString = reader.readFixedString(32);
    myCleanFixedString = reader.readCleanFixedString(16);
    myOptionString = reader.readOptionString();
    myOptionFixedString = reader.readOptionFixedString(20);
    myCleanOptionFixedString = reader.readCleanOptionFixedString(24);

    // --- Read Specific Optional Structures ---
    myOptionF32Triple = reader.readOptionF32Triple();

    // --- Read Generic Collections ---
    myGenericList = reader.readList<int>(() => reader.readU32());
    myGenericMap = reader.readMap<String, bool>(
        () => reader.readString(), () => reader.readBool());

    // --- Read Specialized Numeric Lists ---
    myInt8List = reader.readInt8List();
    myInt16List = reader.readInt16List();
    myInt32List = reader.readInt32List();
    myInt64List = reader.readInt64List();
    myUint8List = reader.readUint8List();
    myUint16List = reader.readUint16List();
    myUint32List = reader.readUint32List();
    myUint64List = reader.readUint64List();
    myFloat32List = reader.readFloat32List();
    myFloat64List = reader.readFloat64List();

    // --- Read Nested Objects ---
    // Use ForCollection for NestedData (variable size)
    myNestedCollection =
        reader.readNestedObjectForCollection<NestedData>(NestedData());
    myOptionalNestedCollection = reader
        .readOptionNestedObjectForCollection<NestedData>(() => NestedData());

    // Use ForFixed for FixedStruct (fixed size)
    myFixedStructInstance =
        reader.readNestedObjectForFixed<FixedStruct>(FixedStruct());
    myOptionalFixedStructInstance =
        reader.readOptionNestedObjectForFixed<FixedStruct>(() => FixedStruct());

    // List of Fixed: Read list length, then loop, reading fixed raw bytes for each element
    myListOfFixedStructs = reader.readList<FixedStruct>(() {
      // Use ForFixed to read raw bytes (size calculated from FixedStruct())
      return reader.readNestedObjectForFixed<FixedStruct>(FixedStruct());
    });

    if (reader.remainingBytes() > 0 && !unsafe) {
      print(
          "Warning: ${reader.remainingBytes()} bytes remaining after decoding MyData");
    }
  }

  @override
  String toString() {
    return """
MyData {
  Primitives: u8=$myU8, u16=$myU16, u32=$myU32, u64=$myU64, i8=$myI8, i16=$myI16, i32=$myI32, i64=$myI64, f32=$myF32, f64=$myF64, bool=$myBool,
  Optionals: optU8=$myOptionU8, optU16=$myOptionU16, optU32=$myOptionU32, optU64=$myOptionU64, optI8=$myOptionI8, optI16=$myOptionI16, optI32=$myOptionI32, optI64=$myOptionI64, optF32=$myOptionF32, optF64=$myOptionF64, optBool=$myOptionBool,
  Strings: str="$myString", fixedStr="$myFixedString", cleanFixedStr="$myCleanFixedString", optStr=$myOptionString, optFixedStr=$myOptionFixedString, optCleanFixedStr=$myCleanOptionFixedString,
  Specific: optF32Triple=$myOptionF32Triple,
  Generic: genericList=[${myGenericList.length} items], genericMap={${myGenericMap.length} entries},
  NumericLists: i8=[${myInt8List.length}], i16=[${myInt16List.length}], i32=[${myInt32List.length}], i64=[${myInt64List.length}], u8=[${myUint8List.length}], u16=[${myUint16List.length}], u32=[${myUint32List.length}], u64=[${myUint64List.length}], f32=[${myFloat32List.length}], f64=[${myFloat64List.length}],
  NestedDynamic: nestedCollection=$myNestedCollection, optNestedCollection=$myOptionalNestedCollection,
  NestedFixed: fixedStructInstance=$myFixedStructInstance, optFixedStructInstance=$myOptionalFixedStructInstance,
  ListOfFixed: [${myListOfFixedStructs.length} items] ${myListOfFixedStructs.isNotEmpty ? myListOfFixedStructs[0] : ''}...
}
""";
  }
}

// --- Example Usage ---
void main() async {
  // 1. Create
  final originalData = MyData()
    // Primitives
    ..myU8 = 255
    ..myU16 = 65535
    ..myU32 = 0xFFFFFFFF
    ..myU64 = 0xFFFFFFFFFFFFFFFF
    ..myI8 = -128
    ..myI16 = -32768
    ..myI32 = -123456
    ..myI64 = -0x8000000000000000
    ..myF32 = -1.2345e-10
    ..myF64 = 3.1415926535
    ..myBool = true
    // Optionals (Some set, None null)
    ..myOptionU8 = 100
    ..myOptionI16 = -30000
    ..myOptionF64 = null
    ..myOptionBool = false
    // Strings
    ..myString = "Hello Bincode!"
    ..myFixedString = "This will be truncated or padded"
    ..myCleanFixedString = "Nulls\x00Padded\x00"
    ..myOptionString = "I might be here"
    ..myOptionFixedString = null
    ..myCleanOptionFixedString = "Clean\x00Optional\x00Fixed"
    // Specific
    ..myOptionF32Triple = Float32List.fromList([1.0, 2.5, -3.0])
    // Generic
    ..myGenericList = [10, 20, 30]
    ..myGenericMap = {"enabled": true, "visible": false}
    // Specialized Lists
    ..myInt8List = [-1, 0, 1]
    ..myInt32List = Int32List.fromList([-1, 0, 1, 1000])
    ..myUint8List = Uint8List.fromList([0xCA, 0xFE, 0xBA, 0xBE])
    ..myFloat64List = [1.1, 2.2, 3.3]
    // Dynamic nested data
    ..myNestedCollection = NestedData.create(101, "Dynamic Nested")
    ..myOptionalNestedCollection =
        NestedData.create(102, "Optional Dynamic Present")
    // Fixed nested data
    ..myFixedStructInstance = FixedStruct.create(999, 1.618, true)
    ..myOptionalFixedStructInstance = FixedStruct.create(-1, -2.718, false)
    // List of fixed structs
    ..myListOfFixedStructs = [
      FixedStruct.create(201, 20.1, false),
      FixedStruct.create(202, 20.2, true),
      FixedStruct.create(203, 20.3, false),
    ];

  // 2. Serialize the data to bytes
  print("Original Data: $originalData");
  Uint8List bincodeBytes = originalData.toBincode();
  print("\nSerialized ${bincodeBytes.length} bytes: $bincodeBytes");

  print("\nGoing to Rust, Dart or anyother other language.....");

  // 3. Deserialize the bytes back into a new instance
  final decodedData = MyData();
  decodedData.fromBincode(bincodeBytes);
  print("\nDecoded Data: $decodedData");

  // 4. Verify

  // Primitives
  assert(decodedData.myU8 == originalData.myU8);
  assert(decodedData.myU16 == originalData.myU16);
  assert(decodedData.myU32 == originalData.myU32);
  assert(decodedData.myU64 == originalData.myU64);
  assert(decodedData.myI8 == originalData.myI8);
  assert(decodedData.myI16 == originalData.myI16);
  assert(decodedData.myI32 == originalData.myI32);
  assert(decodedData.myI64 == originalData.myI64);
  assert((decodedData.myF32 - originalData.myF32).abs() <
      1e-15); // floating point range
  assert((decodedData.myF64 - originalData.myF64).abs() <
      1e-9); // floating point range
  assert(decodedData.myBool == originalData.myBool);
  // Optionals
  assert(decodedData.myOptionU8 == originalData.myOptionU8);
  assert(decodedData.myOptionI16 == originalData.myOptionI16);
  assert(decodedData.myOptionF64 == originalData.myOptionF64);
  assert(decodedData.myOptionBool == originalData.myOptionBool);
  // Strings
  assert(decodedData.myString == originalData.myString);
  assert(
      decodedData.myFixedString.startsWith("This will be truncated or padded"));
  assert(decodedData.myCleanFixedString == "NullsPadded");
  assert(decodedData.myOptionString == originalData.myOptionString);
  assert(decodedData.myOptionFixedString == originalData.myOptionFixedString);
  assert(decodedData.myCleanOptionFixedString == "CleanOptionalFixed");
  // Specific Optional
  assert(decodedData.myOptionF32Triple != null &&
      originalData.myOptionF32Triple != null);
  assert(decodedData.myOptionF32Triple!.length == 3);
  assert(decodedData.myOptionF32Triple![1] == 2.5);
  // Generic Collections
  assert(decodedData.myGenericList.length == 3 &&
      decodedData.myGenericList[1] == 20);
  assert(decodedData.myGenericMap["enabled"] == true &&
      decodedData.myGenericMap["visible"] == false);
  // Specialized Lists
  assert(decodedData.myInt8List.length == 3 && decodedData.myInt8List[0] == -1);
  assert(decodedData.myInt32List.length == 4 &&
      decodedData.myInt32List[3] == 1000);
  assert(decodedData.myUint8List.length == 4 &&
      decodedData.myUint8List[1] == 0xFE);
  assert(decodedData.myFloat64List.length == 3 &&
      (decodedData.myFloat64List[1] - 2.2).abs() < 1e-9);
  // Verify dynamic nested
  assert(decodedData.myNestedCollection.nestedId == 101 &&
      decodedData.myNestedCollection.nestedName == "Dynamic Nested");
  assert(decodedData.myOptionalNestedCollection != null);
  assert(decodedData.myOptionalNestedCollection!.nestedId == 102 &&
      decodedData.myOptionalNestedCollection!.nestedName ==
          "Optional Dynamic Present");
  // Verify fixed nested
  assert(decodedData.myFixedStructInstance.valueA == 999);
  assert(decodedData.myFixedStructInstance.valueB == 1.618);
  assert(decodedData.myFixedStructInstance.flagC == true);
  assert(decodedData.myOptionalFixedStructInstance != null);
  assert(decodedData.myOptionalFixedStructInstance!.valueA == -1);
  assert(decodedData.myOptionalFixedStructInstance!.valueB == -2.718);
  assert(decodedData.myOptionalFixedStructInstance!.flagC == false);
  // Verify list of fixed
  assert(decodedData.myListOfFixedStructs.length == 3);
  assert(decodedData.myListOfFixedStructs[0].valueA == 201 &&
      decodedData.myListOfFixedStructs[0].flagC == false);
  assert(decodedData.myListOfFixedStructs[1].valueA == 202 &&
      decodedData.myListOfFixedStructs[1].flagC == true);
  assert(decodedData.myListOfFixedStructs[2].valueA == 203 &&
      decodedData.myListOfFixedStructs[2].flagC == false);

  print("\nVerification successful!");
}
0
likes
160
points
92
downloads

Publisher

unverified uploader

Weekly Downloads

A bincode like serialization library for Dart. Manually writen, provides encoding for binary data structures, mirroring the bincode specification (Rust) minimalistically.

Repository (GitHub)
View/report issues

Topics

#serialization #bincode #binary #dart

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

euc

More

Packages that depend on d_bincode