- Refine `decode_struct` to handle data parsing and error messages - Enhance `should_skip_field_decode` to cover more skip attribute variations - Update constants and tests related to struct names - Adjust `decode_value` to better handle optional types and existing keys - Modify `decode_struct` to skip optional fields during decoding
238 lines
4.9 KiB
Markdown
238 lines
4.9 KiB
Markdown
# HeroEncoder - Simple Struct Serialization
|
|
|
|
HeroEncoder provides bidirectional conversion between V structs and HeroScript format.
|
|
|
|
## Design: Single Level Deep Only
|
|
|
|
This module is designed for **simple, flat structs** only:
|
|
|
|
✅ **Supported:**
|
|
|
|
- Basic types: `int`, `string`, `bool`, `f32`, `f64`, `u8`, `u16`, `u32`, `u64`, `i8`, `i16`, `i32`, `i64`
|
|
- Arrays of basic types: `[]string`, `[]int`, etc.
|
|
- Time handling: `ourtime.OurTime`
|
|
- Embedded structs (for inheritance)
|
|
|
|
❌ **Not Supported:**
|
|
|
|
- Nested structs (non-embedded fields)
|
|
- Arrays of structs
|
|
- Complex nested structures
|
|
|
|
Use `ourdb` or `json` for complex data structures.
|
|
|
|
## HeroScript Format
|
|
|
|
```heroscript
|
|
!!define.typename param1:value1 param2:'value with spaces' list:item1,item2,item3
|
|
```
|
|
|
|
or
|
|
|
|
```heroscript
|
|
!!configure.typename param1:value1 param2:'value with spaces'
|
|
```
|
|
|
|
## Basic Usage
|
|
|
|
### Simple Struct
|
|
|
|
```v
|
|
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
|
|
|
import incubaid.herolib.data.encoderhero
|
|
import incubaid.herolib.data.ourtime
|
|
|
|
struct Person {
|
|
mut:
|
|
name string
|
|
age int = 20
|
|
active bool
|
|
birthday ourtime.OurTime
|
|
}
|
|
|
|
mut person := Person{
|
|
name: 'Bob'
|
|
age: 25
|
|
active: true
|
|
birthday: ourtime.new('2000-01-15')!
|
|
}
|
|
|
|
// Encode to heroscript
|
|
heroscript := encoderhero.encode[Person](person)!
|
|
println(heroscript)
|
|
// Output: !!define.person name:Bob age:25 active:true birthday:'2000-01-15 00:00'
|
|
|
|
// Decode back
|
|
person2 := encoderhero.decode[Person](heroscript)!
|
|
assert person2.name == person.name
|
|
assert person2.age == person.age
|
|
```
|
|
|
|
### Embedded Structs (Inheritance)
|
|
|
|
```v
|
|
import incubaid.herolib.data.encoderhero
|
|
import incubaid.herolib.data.ourtime
|
|
|
|
struct Base {
|
|
id int
|
|
created ourtime.OurTime
|
|
}
|
|
|
|
struct User {
|
|
Base // Embedded struct - fields are flattened
|
|
mut:
|
|
name string
|
|
email string
|
|
}
|
|
|
|
user := User{
|
|
id: 123
|
|
created: ourtime.now()
|
|
name: 'Alice'
|
|
email: 'alice@example.com'
|
|
}
|
|
|
|
heroscript := encoderhero.encode[User](user)!
|
|
// Output: !!define.user id:123 created:'2024-01-15 10:30' name:Alice email:alice@example.com
|
|
|
|
decoded := encoderhero.decode[User](heroscript)!
|
|
assert decoded.id == user.id
|
|
assert decoded.name == user.name
|
|
```
|
|
|
|
### Arrays of Basic Types
|
|
|
|
```v
|
|
struct Config {
|
|
mut:
|
|
hosts []string
|
|
ports []int
|
|
name string
|
|
}
|
|
|
|
config := Config{
|
|
name: 'prod'
|
|
hosts: ['server1.com', 'server2.com', 'server3.com']
|
|
ports: [8080, 8081, 8082]
|
|
}
|
|
|
|
heroscript := encoderhero.encode[Config](config)!
|
|
// Output: !!define.config name:prod hosts:server1.com,server2.com,server3.com ports:8080,8081,8082
|
|
|
|
decoded := encoderhero.decode[Config](heroscript)!
|
|
assert decoded.hosts.len == 3
|
|
assert decoded.ports[0] == 8080
|
|
```
|
|
|
|
### Skip Attributes
|
|
|
|
Use `@[skip]` to exclude fields from encoding/decoding:
|
|
|
|
```v
|
|
struct MyStruct {
|
|
id int
|
|
name string
|
|
runtime_cache ?&Cache @[skip] // Won't be encoded/decoded
|
|
}
|
|
```
|
|
|
|
## Common Use Cases
|
|
|
|
### Configuration Files
|
|
|
|
```v
|
|
struct PostgresqlClient {
|
|
pub mut:
|
|
name string = 'default'
|
|
user string = 'root'
|
|
port int = 5432
|
|
host string = 'localhost'
|
|
password string
|
|
dbname string = 'postgres'
|
|
}
|
|
|
|
// Load from heroscript
|
|
script := '!!postgresql_client.configure name:production user:app_user port:5433'
|
|
client := encoderhero.decode[PostgresqlClient](script)!
|
|
```
|
|
|
|
### Data Exchange
|
|
|
|
```v
|
|
struct ApiResponse {
|
|
mut:
|
|
status int
|
|
message string
|
|
timestamp ourtime.OurTime
|
|
errors []string
|
|
}
|
|
|
|
response := ApiResponse{
|
|
status: 200
|
|
message: 'Success'
|
|
timestamp: ourtime.now()
|
|
errors: []
|
|
}
|
|
|
|
// Send as heroscript
|
|
script := encoderhero.encode[ApiResponse](response)!
|
|
```
|
|
|
|
## Time Handling with OurTime
|
|
|
|
Always use `incubaid.herolib.data.ourtime.OurTime` for time fields:
|
|
|
|
```v
|
|
import incubaid.herolib.data.ourtime
|
|
|
|
struct Event {
|
|
mut:
|
|
name string
|
|
start_time ourtime.OurTime
|
|
end_time ourtime.OurTime
|
|
}
|
|
|
|
event := Event{
|
|
name: 'Meeting'
|
|
start_time: ourtime.new('2024-06-15 14:00')!
|
|
end_time: ourtime.new('2024-06-15 15:30')!
|
|
}
|
|
|
|
script := encoderhero.encode[Event](event)!
|
|
decoded := encoderhero.decode[Event](script)!
|
|
|
|
// OurTime provides flexible formatting
|
|
println(decoded.start_time.str()) // '2024-06-15 14:00'
|
|
println(decoded.start_time.day()) // '2024-06-15'
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
```v
|
|
// Decoding with error handling
|
|
decoded := encoderhero.decode[MyStruct](heroscript) or {
|
|
eprintln('Failed to decode: ${err}')
|
|
MyStruct{} // Return default
|
|
}
|
|
|
|
// Encoding should not fail for simple structs
|
|
encoded := encoderhero.encode[MyStruct](my_struct)!
|
|
```
|
|
|
|
## Limitations
|
|
|
|
**For Complex Data Structures, Use:**
|
|
|
|
- `incubaid.herolib.data.ourdb` - For nested data storage
|
|
- V's built-in `json` module - For JSON serialization
|
|
- Custom serialization - For specific needs
|
|
|
|
**This module is optimized for:**
|
|
|
|
- Configuration files
|
|
- Simple data exchange
|
|
- Flat data structures
|
|
- Heroscript integration
|