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

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

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.

Field Identity

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.

Scalar Encoding

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:

MethodMeaning
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.

Nested Specs

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:

MethodApplies 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()))));

Carrier Inner Specs

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>.

Nullability, Reference Tracking, And Dynamic Fields

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)));

Unions

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.