1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
|
# Field Validation
Glaze provides field validation capabilities through compile-time options and customization points.
## Required Fields with `error_on_missing_keys`
By default, Glaze allows JSON objects to have missing fields when parsing. However, you can enable strict validation using the `error_on_missing_keys` option:
```c++
struct config {
std::string host;
int port;
std::optional<std::string> description;
};
std::string json = R"({"host":"localhost"})"; // Missing 'port'
config obj;
// Without error_on_missing_keys (default)
auto ec = glz::read_json(obj, json); // Success, port gets default value
// With error_on_missing_keys
ec = glz::read<glz::opts{.error_on_missing_keys = true}>(obj, json); // Error: missing_key
```
### Default Behavior
When `error_on_missing_keys = true`, field requirements are determined automatically:
- **Non-nullable types** (e.g., `int`, `std::string`, `double`) are **required**
- **Nullable types** (e.g., `std::optional<T>`, `std::unique_ptr<T>`, `std::shared_ptr<T>`, `T*`) are **optional**
```c++
struct user {
std::string username; // Required
int id; // Required
std::optional<std::string> bio; // Optional
};
// Valid: optional field can be missing
std::string json = R"({"username":"alice","id":42})";
user u;
auto ec = glz::read<glz::opts{.error_on_missing_keys = true}>(u, json); // Success
// Invalid: required field missing
json = R"({"username":"alice"})"; // Missing 'id'
ec = glz::read<glz::opts{.error_on_missing_keys = true}>(u, json); // Error: missing_key
```
## Custom Field Requirements with `requires_key`
For fine-grained control over which fields are required, you can provide a `requires_key` function in your type's `glz::meta` specialization. This allows you to:
- Make non-nullable fields optional without wrapping them in `std::optional`
- Exclude internal/reserved fields from validation
- Implement complex business logic for field requirements
### Basic Usage
```c++
struct api_request {
std::string endpoint; // Required
std::string api_key; // Required
int timeout; // Want this to be optional
std::string _internal_id; // Internal field, should not be required
};
template <>
struct glz::meta<api_request> {
static constexpr bool requires_key(std::string_view key, bool is_nullable) {
// Make timeout optional even though it's not nullable
if (key == "timeout") {
return false;
}
// Don't require internal fields
if (key.starts_with("_")) {
return false;
}
// Default: non-nullable fields are required
return !is_nullable;
}
};
// Now parsing works with optional timeout
std::string json = R"({"endpoint":"/users","api_key":"secret123"})";
api_request req;
auto ec = glz::read<glz::opts{.error_on_missing_keys = true}>(req, json); // Success!
// req.timeout will have its default value (0)
```
### Function Signature
```c++
static constexpr bool requires_key(std::string_view key, bool is_nullable)
```
**Parameters:**
- `key`: The name of the field being checked
- `is_nullable`: Whether the field's type is nullable (e.g., `std::optional`, pointers)
**Returns:**
- `true` if the field is required (parsing will fail if it's missing)
- `false` if the field is optional (parsing will succeed even if it's missing)
### Advanced Examples
#### Conditional Requirements
```c++
struct form_data {
std::string name;
std::string email;
std::string phone;
std::string address;
};
template <>
struct glz::meta<form_data> {
// Require only name and at least one contact method (email or phone)
// This is a simplified example - actual implementation would need runtime validation
static constexpr bool requires_key(std::string_view key, bool is_nullable) {
if (key == "name") return true; // Always required
// For this compile-time function, we can only mark fields as optional or required
// Runtime validation would be needed for "at least one of" requirements
return false;
}
};
```
#### Reserved/Internal Fields
```c++
struct database_record {
int id;
std::string name;
int reserved_field1; // Reserved for future use
int reserved_field2; // Reserved for future use
};
template <>
struct glz::meta<database_record> {
static constexpr bool requires_key(std::string_view key, bool is_nullable) {
// Don't require fields starting with "reserved"
if (key.starts_with("reserved")) {
return false;
}
return !is_nullable;
}
};
```
#### Working with Nullable Types
The `requires_key` function works seamlessly with nullable types:
```c++
struct mixed_requirements {
int id; // Required (non-nullable, no special rule)
std::optional<int> optional_id; // Optional (nullable type)
int flexible_field; // Made optional via requires_key
};
template <>
struct glz::meta<mixed_requirements> {
static constexpr bool requires_key(std::string_view key, bool is_nullable) {
// Make flexible_field optional
if (key == "flexible_field") {
return false;
}
// Default behavior: nullable types are optional, others are required
return !is_nullable;
}
};
// Valid: both optional_id and flexible_field can be missing
std::string json = R"({"id":42})";
mixed_requirements obj;
auto ec = glz::read<glz::opts{.error_on_missing_keys = true}>(obj, json); // Success
```
## Use Cases
### 1. Backward Compatibility
When adding new fields to an existing API, make them optional without changing their types:
```c++
struct api_response_v2 {
int status;
std::string message;
int new_field_added_in_v2; // New field, should be optional for v1 clients
};
template <>
struct glz::meta<api_response_v2> {
static constexpr bool requires_key(std::string_view key, bool is_nullable) {
if (key == "new_field_added_in_v2") {
return false;
}
return !is_nullable;
}
};
```
### 2. Configuration Files
Allow optional configuration values without cluttering code with `std::optional`:
```c++
struct app_config {
std::string database_url; // Required
int max_connections; // Optional, has good default
int timeout_ms; // Optional, has good default
};
template <>
struct glz::meta<app_config> {
static constexpr bool requires_key(std::string_view key, bool is_nullable) {
if (key == "database_url") return true;
return false; // Everything else is optional
}
};
```
### 3. Partial Updates
When receiving partial update payloads, only require the ID field:
```c++
struct user_update {
int user_id; // Always required
std::string name; // Optional - only update if provided
std::string email; // Optional - only update if provided
std::string password; // Optional - only update if provided
};
template <>
struct glz::meta<user_update> {
static constexpr bool requires_key(std::string_view key, bool is_nullable) {
return key == "user_id"; // Only ID is required
}
};
```
## Relationship with JSON Schema
The `requires_key` customization point affects both:
1. **Parsing/Validation**: Determines which fields are validated during `glz::read` with `error_on_missing_keys = true`
2. **JSON Schema Generation**: Determines which fields appear in the `"required"` array when using `glz::write_json_schema`
This ensures consistency between your validation logic and your schema documentation.
## Best Practices
1. **Use `std::optional` when field truly represents optional data** - Reserve `requires_key` for cases where you need non-nullable types to be optional
2. **Document your `requires_key` logic** - Future maintainers should understand why certain fields are optional
3. **Keep validation simple** - Complex interdependent requirements may be better suited for runtime validation after parsing
4. **Consider backward compatibility** - Making a previously-optional field required is a breaking change
5. **Test both paths** - Ensure your code handles both the presence and absence of optional fields
## See Also
- [Options](options.md) - Compile-time options including `error_on_missing_keys`
- [JSON Schema](json-schema.md) - Generating JSON schemas from C++ types
- [Nullable Types](nullable-types.md) - Working with optional and pointer types
|