A practical guide to transforming values during decode/encode.
Formulas let you convert between raw on‑wire values and engineering values. You can express them as inline lambdas or as classes implementing java.util.function.Function.
- Decode formula: raw -> target field type
- Encode formula: target field type -> raw
Note: Lambda formulas require annotation processing for compile-time code generation. The
fastprotobundle includes this by default. For Android projects, see Android guide for setup details.
- Pick the field type and its data annotation (e.g.,
@UInt32Type). - Decide if a lambda is sufficient, or a reusable class is better.
- Provide the correct input/output types for each direction.
- Test round‑trip: value -> encode -> decode -> value.
- Keep formulas pure (no side effects, deterministic).
- Decode:
Function<RawType, FieldType>- Example:
@UInt32Typeraw isLong; field may beDouble(engineering).
- Example:
- Encode:
Function<FieldType, RawType>- Example: reverse the above conversion.
- Lambda:
@DecodingFormula(lambda = "...")/@EncodingFormula(lambda = "...")— included infastprotobundle - Class:
@DecodingFormula(MyFunc.class)/@EncodingFormula(MyFunc.class)where class implementsFunction<In, Out> - Precedence: Class‑based formulas win if both lambda and class are present.
Lambda expressions are supported out of the box when using the fastproto bundle:
<dependency>
<groupId>org.indunet</groupId>
<artifactId>fastproto</artifactId>
<version>4.1.0</version>
</dependency>For Android projects, see android.md for separate configuration.
- Formulas are compiled/bound during graph resolve and invoked per field during decode/encode.
- Throwing exceptions inside formulas will surface as
DecodingException/EncodingExceptionat call sites.
import org.indunet.fastproto.annotation.*;
public class Weather {
@UInt32Type(offset = 14)
@DecodingFormula(lambda = "x -> x * 0.1") // raw uint32 -> Pa * 0.1 -> engineering
@EncodingFormula(lambda = "x -> (long) (x * 10)") // engineering -> raw
double pressure;
}import java.util.function.Function;
public class PressureDecode implements Function<Long, Double> {
@Override public Double apply(Long raw) { return raw * 0.1; }
}
public class PressureEncode implements Function<Double, Long> {
@Override public Long apply(Double eng) { return (long) (eng * 10); }
}public class Weather {
@UInt32Type(offset = 14)
@DecodingFormula(PressureDecode.class)
@EncodingFormula(PressureEncode.class)
double pressure;
}- You may specify only one side if the other is identity, e.g., only
@DecodingFormula.
- Constants & context: Prefer constructor parameters or class constants inside class‑based formulas; avoid hidden globals.
- Composition: Split complex logic into smaller
Functionclasses and delegate. - Performance: Lambdas are compiled once and reused; keep formulas simple and avoid heavy allocations in hot paths.
- Validation: Add unit tests with known vectors; ensure symmetry if both directions are specified.
- Ensure types match the codec direction (e.g.,
UInt32raw isLong). - Document non‑obvious conversions (units, offsets) near the field.
- If results are off by a factor, verify integer vs floating arithmetic and cast points.
- If exceptions bubble up, catch and wrap with clear messages in class‑based formulas to ease debugging.