title: Schema Metadata sidebar_position: 5 id: schema_metadata license: | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Field configuration is embedded directly in FORY_STRUCT. A field entry may be bare, or it may be a tuple containing the member name and a fory::F(...) builder:
#include "fory/serialization/fory.h" struct DataV2 { uint32_t id; uint64_t timestamp; std::optional<uint32_t> version; }; FORY_STRUCT(DataV2, id, (timestamp, fory::F().tagged()), version);
The configuration is compile-time metadata. It does not allocate codec objects or add virtual dispatch on the serialization path.
fory::F() uses name-mode field identity. Bare fields are also name-mode:
FORY_STRUCT(DataV2, id, (timestamp, fory::F().tagged()), version);
fory::F(id) uses explicit id-based field identity. IDs must be non-negative:
FORY_STRUCT(DataV2, (id, fory::F(0)), (timestamp, fory::F(1).tagged()), (version, fory::F(2)));
Fields without explicit IDs still use their snake_case field names. Explicit IDs sort before name-based fields within the same protocol field group, so a single FORY_STRUCT may mix fory::F(id), fory::F(), and bare fields.
Integer encoding is configured on the field or on a nested value-node spec:
struct Counters { uint32_t fixed_id; uint64_t tagged_time; int64_t signed_score; }; FORY_STRUCT(Counters, (fixed_id, fory::F().fixed()), (tagged_time, fory::F().tagged()), (signed_score, fory::F().varint()));
Supported scalar encoding methods are:
| Method | Meaning |
|---|---|
fixed() | Fixed-width integer encoding where valid |
varint() | Variable-length integer encoding where valid |
tagged() | Tagged integer encoding where valid |
Invalid scalar/type combinations fail at compile time.
Use the fory::T namespace for value-node specs inside containers and wrapper carriers. Untyped specs infer the actual C++ type at that node:
namespace T = fory::T; struct Foo { std::vector<uint32_t> values; std::map<uint32_t, std::vector<int64_t>> nested; }; FORY_STRUCT(Foo, (values, fory::F().list(T::fixed())), (nested, fory::F().map(T::varint(), T::list(T::tagged()))));
Typed specs are optional validators and make the intended node type explicit:
FORY_STRUCT(Foo, (nested, fory::F().map(T::uint32().varint(), T::list(T::int64().tagged()))));
Supported recursive composition methods are:
| Method | Applies to |
|---|---|
list(elem) | std::vector<T> and list-like fields |
set(elem) | std::set<T> and set-like fields |
map(key, value) | std::map<K, V> and map-like fields |
map().key(spec) | Override only the map key |
map().value(spec) | Override only the map value |
inner(child) | Transparent single-child carriers |
Partial map overrides are useful when only one side needs a non-default encoding:
FORY_STRUCT(Foo, (nested, fory::F().map().key(T::varint())), (other, fory::F().map().value(T::list(T::tagged()))));
Use .inner(...) for wrapper-like carriers. The carrier kind still comes from the actual C++ type, and controls nullable/reference behavior:
struct WrapperFields { std::optional<std::vector<uint32_t>> maybe_values; std::shared_ptr<std::vector<int64_t>> shared_values; }; FORY_STRUCT(WrapperFields, (maybe_values, fory::F().inner(T::list(T::varint()))), (shared_values, fory::F().nullable().ref().inner(T::list(T::tagged()))));
.inner(...) is the only public combinator for std::optional<T>, std::shared_ptr<T>, std::unique_ptr<T>, and fory::serialization::SharedWeak<T>.
std::optional<T> is nullable by default. Smart pointers may be marked nullable or reference-tracked in the field spec:
struct Node { std::string name; std::shared_ptr<Node> next; }; FORY_STRUCT(Node, name, (next, fory::F().nullable().ref()));
For polymorphic pointer fields, use .dynamic(true) to always write runtime type information, .dynamic(false) to use the declared type directly, or omit it to let Fory infer the behavior from the C++ type:
struct Zoo { std::shared_ptr<Animal> star; std::shared_ptr<Animal> mascot; }; FORY_STRUCT(Zoo, (star, fory::F().nullable().dynamic(true)), (mascot, fory::F().nullable().dynamic(false)));
FORY_UNION cases must use explicit ids. Name-mode fory::F() is invalid for union metadata:
struct Choice { std::variant<std::string, uint32_t> value; static Choice text(std::string value); static Choice code(uint32_t value); }; FORY_UNION(Choice, (text, std::string, fory::F(1)), (code, uint32_t, fory::F(2).fixed()));
Generated C++ may omit the explicit case type when it can infer the payload type from a non-overloaded one-argument factory:
FORY_UNION(GeneratedChoice, (text, fory::F(1)), (code, fory::F(2).fixed()));
The three-element form is the stable public form for handwritten code.