nson
NSON is short for Network Serialization Object Notation, a binary encoded serialization of JSON-like documents. Similar to JSON, NSON supports embedding maps and arrays within other maps and arrays. Unlike JSON, NSON also includes comprehensive integer types (i8/u8, i16/u16, i32/u32, i64/u64), floating-point types (f32/f64), binary, timestamp, and id types.
NSON borrows from BSON and can be thought of as a streamlined version of BSON, removing some of the less common or mongodb-proprietary types. NSON provides fine-grained integer types (8-bit, 16-bit, 32-bit, and 64-bit, both signed and unsigned) to optimize storage space and bandwidth usage - especially useful for IoT devices and embedded systems.
Key features:
- ð Type-rich: Comprehensive integer types from 8-bit to 64-bit
- ðĶ Space-efficient: Choose the smallest type for your data range
- ðŊ Type-safe: Type-safe getter methods for all types
- ð§ Convenient: Easy-to-use macros for JSON-like syntax
- ðŠķ Lightweight:
no_stdsupport for embedded systems - ⥠Fast: Zero-copy parsing and efficient encoding
Table of Contents
- Quick Start
- Macros
- Data Types
- Usage Examples
- Type Selection Guide
- IoT Applications
- API Reference
- Performance Tips
- Testing
Quick Start
use ;
Macros
NSON provides convenient macros for creating data structures with JSON-like syntax:
Basic Macro Usage
use ;
// Create a Map
let config = m! ;
// Create an Array
let numbers = a!;
let mixed = a!;
Auto-Detection Feature
Key feature: Macros automatically detect nested [...] and {...} syntax, so you don't need to explicitly nest macros!
use m;
// â
Recommended: Simple and clean (auto-detection)
let document = m! ;
// Also works: Explicit macro usage
use a;
let document2 = m! ;
// Both are equivalent!
assert_eq!;
The auto-detection works at all nesting levels:
// Complex nested structure - all automatic!
let gateway = m! ;
Data Types
NSON supports a comprehensive set of data types optimized for different use cases:
| Type | Rust Type | Bytes | Range | Use Cases |
|---|---|---|---|---|
| I8 | i8 |
1 | -128 to 127 | Temperature offset, signal strength |
| U8 | u8 |
1 | 0 to 255 | Percentages, brightness, small counts |
| I16 | i16 |
2 | -32,768 to 32,767 | TemperatureÃ100, coordinates |
| U16 | u16 |
2 | 0 to 65,535 | Port numbers, product IDs, color temp |
| I32 | i32 |
4 | -2ÂģÂđ to 2ÂģÂđ-1 | Standard integers, counters |
| U32 | u32 |
4 | 0 to 2ÂģÂē-1 | Unsigned counts, IDs |
| I64 | i64 |
8 | -2âķÂģ to 2âķÂģ-1 | Large integers, Unix timestamps |
| U64 | u64 |
8 | 0 to 2âķâī-1 | Large unsigned numbers |
| F32 | f32 |
4 | IEEE 754 | General floating-point |
| F64 | f64 |
8 | IEEE 754 | High-precision floating-point |
| Bool | bool |
1 | true/false | Boolean values |
| String | String |
4+len | UTF-8 | Text data |
| Binary | Vec<u8> |
4+len | Byte array | Binary data |
| Array | Array |
4+data+1 | Ordered list | Collections |
| Map | Map |
4+data+1 | Key-value pairs | Objects/dictionaries |
| TimeStamp | TimeStamp |
8 | Unix timestamp | Timestamps |
| Id | Id |
12 | 12-byte ID | Unique identifiers |
| Null | - | 0 | null | Null value |
Usage Examples
Working with Different Integer Types
use m;
// Use appropriate integer types to save space
let device_config = m! ;
// Type-safe access with getter methods
let brightness = device_config.get_u8.unwrap;
let temp = device_config.get_i16.unwrap;
println!;
Encoding and Decoding
use Map;
// Create data
let data = m! ;
// Encode to bytes
let bytes = data.to_bytes.unwrap;
println!;
// Decode from bytes
let decoded = from_bytes.unwrap;
assert_eq!;
// Access values
let value = decoded.get_u8.unwrap;
let name = decoded.get_str.unwrap;
Using Serde
use ;
let config = DeviceConfig ;
// Serialize
let bytes = to_bytes.unwrap;
// Deserialize
let decoded: DeviceConfig = from_bytes.unwrap;
assert_eq!;
Type Selection Guide
Choose the Smallest Appropriate Type
Choosing the right data type significantly reduces storage space and bandwidth:
use m;
// â
Good: Using appropriate types
let efficient = m! ;
// â Bad: Using oversized types
let inefficient = m! ;
println!;
println!;
// Output: Efficient: ~20 bytes, Inefficient: ~35 bytes (57% larger!)
Type Selection Rules
| Data Range | Recommended Type | Examples |
|---|---|---|
| 0-100 | u8 |
Percentages, battery level |
| 0-255 | u8 |
RGB colors, brightness |
| -100 to +100 | i8 |
Small offsets, signal strength (dBm) |
| 0-1,000 | u16 |
Small port numbers, small IDs |
| -1,000 to +1,000 | i16 |
Fixed-point à 100 |
| 0-65,535 | u16 |
Port numbers, product IDs |
| Other small integers | i32/u32 |
Standard integers |
| Large numbers | i64/u64 |
Timestamps, large counts |
Fixed-Point vs Floating-Point
For fixed-precision values (temperature, prices), use fixed-point arithmetic:
// â
Fixed-point: 2 bytes, exact
let temp_fixed = m! ;
// â Floating-point: 4 bytes, precision issues
let temp_float = m! ;
// Helper functions for fixed-point
IoT Applications
Temperature and Humidity Sensor
use ;
let sensor_reading = m! ;
// Encode for transmission
let bytes = sensor_reading.to_bytes.unwrap;
println!; // Very compact!
// Decode on receiver
let decoded = from_bytes.unwrap;
let temp = decoded.get_i16.unwrap as f32 / 100.0;
let humidity = decoded.get_u8.unwrap;
println!;
Smart Light Control
use m;
let light_command = m! ;
let bytes = light_command.to_bytes.unwrap;
println!;
Matter Protocol Device Attributes
use m;
let device_attrs = m! ;
Batch Processing
use ;
// Collect multiple sensor readings
let batch = a!;
let bytes = batch.to_bytes.unwrap;
println!;
API Reference
Map Methods
use Map;
// Create
let mut map = new;
let map = with_capacity; // Pre-allocate
// Insert
map.insert;
map.insert;
// Get (generic)
let value = map.get; // Option<&Value>
// Type-safe getters
let num = map.get_u8.unwrap; // u8
let num = map.get_i8.unwrap; // i8
let num = map.get_u16.unwrap; // u16
let num = map.get_i16.unwrap; // i16
let num = map.get_u32.unwrap; // u32
let num = map.get_i32.unwrap; // i32
let num = map.get_u64.unwrap; // u64
let num = map.get_i64.unwrap; // i64
let num = map.get_f32.unwrap; // f32
let num = map.get_f64.unwrap; // f64
let text = map.get_str.unwrap; // &str
let flag = map.get_bool.unwrap; // bool
let bin = map.get_binary.unwrap; // &Binary
// Check
map.contains_key; // bool
map.is_null; // bool
map.len; // usize
map.is_empty; // bool
// Iterate
for in &map
for key in map.keys
for value in map.values
// Encode/Decode
let bytes = map.to_bytes.unwrap;
let map = from_bytes.unwrap;
Array Methods
use Array;
// Create
let arr = new;
let arr = from_vec;
// Access
let value = arr.get; // Option<&Value>
let len = arr.len;
// Iterate
for value in &arr
// Encode/Decode
let bytes = arr.to_bytes.unwrap;
let arr = from_bytes.unwrap;
Performance Tips
- Pre-allocate capacity: Use
Map::with_capacity(n)when you know the size
let mut map = with_capacity; // Avoid reallocations
- Use smaller types:
u8instead ofu32saves 75% space
m! // 1 byte vs 4 bytes
- Fixed-point arithmetic:
i16instead off32saves 50% space
m! // 2 bytes vs 4 bytes
- Batch operations: Process multiple items together
let batch: = sensors.iter
.map
.collect;
- Reuse buffers: Reuse Vec for encoding
buffer.clear;
buffer.extend_from_slice;
Testing
Run the test suite:
Run examples:
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
More Resources
- API Documentation
- Crates.io
- Examples - More complete examples in the repository