Optional Nullable Support
The liblab SDK generator provides sophisticated support for handling optional and nullable fields across multiple programming languages. This feature allows for precise control over field presence, absence, and null values in your API models, ensuring type safety and clear semantics in the supported languages.
Understanding the Four Field Types
When working with OpenAPI specifications, fields can be categorized into four distinct types based on their required
status and nullable
property:
- Required Non-Nullable: Field must be present and cannot be null
- Required Nullable: Field must be present but can be null
- Optional Non-Nullable: Field can be omitted but if present, cannot be null
- Optional Nullable: Field can be omitted or present with any value (including null)
OpenAPI Schema Definition
Here's how these field types are defined in your OpenAPI specification:
{
"schemas": {
"ModelWithOptionalNullableDefaultValues": {
"type": "object",
"required": [
"requiredString",
"requiredInteger",
"requiredNullableString",
"requiredNullableInteger"
],
"properties": {
"requiredString": {
"type": "string"
},
"requiredInteger": {
"type": "integer",
"format": "int64"
},
"requiredNullableString": {
"type": "string",
"nullable": true
},
"requiredNullableInteger": {
"type": "integer",
"format": "int64",
"nullable": true
},
"optionalString": {
"type": "string"
},
"optionalInteger": {
"type": "integer",
"format": "int64"
},
"optionalNullableString": {
"type": "string",
"nullable": true
},
"optionalNullableInteger": {
"type": "integer",
"format": "int64",
"nullable": true
}
}
}
}
}
Language-Specific Implementations
- Java
- C#
Java Implementation
The Java SDK uses JsonNullable
wrapper types for optional fields, providing clear distinction between undefined, null, and present values.
Generated Model
@Data
@Builder
@With
@ToString
@EqualsAndHashCode
@Jacksonized
public class ModelWithOptionalNullableDefaultValues {
// Required Non-Nullable
@NonNull
private String requiredString;
@NonNull
private Long requiredInteger;
// Required Nullable
@JsonInclude(JsonInclude.Include.ALWAYS)
private String requiredNullableString;
@JsonInclude(JsonInclude.Include.ALWAYS)
private Long requiredNullableInteger;
// Optional Non-Nullable (using JsonNullable wrapper)
@JsonProperty("optionalString")
private JsonNullable<String> optionalString;
@JsonProperty("optionalInteger")
private JsonNullable<Long> optionalInteger;
// Optional Nullable (using JsonNullable wrapper)
@JsonProperty("optionalNullableString")
private JsonNullable<String> optionalNullableString;
@JsonProperty("optionalNullableInteger")
private JsonNullable<Long> optionalNullableInteger;
// Convenience getters that return unwrapped values
@JsonIgnore
public String getOptionalString() {
return optionalString.orElse(null);
}
@JsonIgnore
public Long getOptionalInteger() {
return optionalInteger.orElse(null);
}
@JsonIgnore
public String getOptionalNullableString() {
return optionalNullableString.orElse(null);
}
@JsonIgnore
public Long getOptionalNullableInteger() {
return optionalNullableInteger.orElse(null);
}
}
Usage Examples
// Example 1: Creating an object with all field types
ModelWithOptionalNullableDefaultValues model =
ModelWithOptionalNullableDefaultValues.builder()
.requiredString("requiredString") // Required Non-Nullable
.requiredInteger(8L) // Required Non-Nullable
.requiredNullableString("not null") // Required Nullable
.requiredNullableInteger(null) // Required Nullable (can be null)
.optionalString("optionalString") // Optional Non-Nullable
.optionalInteger(9L) // Optional Non-Nullable
.optionalNullableString("optional") // Optional Nullable
.optionalNullableInteger(null) // Optional Nullable (can be null)
.build();
// Example 2: Optional fields omitted entirely
ModelWithOptionalNullableDefaultValues minimalModel =
ModelWithOptionalNullableDefaultValues.builder()
.requiredString("requiredString")
.requiredInteger(8L)
.requiredNullableString(null) // Still required, but can be null
.requiredNullableInteger(null) // Still required, but can be null
// Optional fields omitted - they will be omitted from JSON when serialized and sent to API
.build();
// Example 3: Optional fields with mixed values
ModelWithOptionalNullableDefaultValues mixedModel =
ModelWithOptionalNullableDefaultValues.builder()
.requiredString("requiredString")
.requiredInteger(8L)
.requiredNullableString("not null")
.requiredNullableInteger(null)
.optionalString("present") // Optional non-nullable with value
.optionalNullableString(null) // Optional nullable set to null
// optionalInteger and optionalNullableInteger omitted
.build();
// What the server receives for Example 1 (all fields set):
// {
// "requiredString": "requiredString",
// "requiredInteger": 8,
// "requiredNullableString": "not null",
// "requiredNullableInteger": null,
// "optionalString": "optionalString",
// "optionalInteger": 9,
// "optionalNullableString": "optional",
// "optionalNullableInteger": null
// }
// What the server receives for Example 2 (minimal model):
// {
// "requiredString": "requiredString",
// "requiredInteger": 8,
// "requiredNullableString": null,
// "requiredNullableInteger": null
// // Note: optional fields are completely omitted from JSON
// }
// What the server receives for Example 3 (mixed model):
// {
// "requiredString": "requiredString",
// "requiredInteger": 8,
// "requiredNullableString": "not null",
// "requiredNullableInteger": null,
// "optionalString": "present",
// "optionalNullableString": null
// // Note: optionalInteger and optionalNullableInteger are omitted
// }
Key Features
- Clear Semantics:
JsonNullable
clearly distinguishes between undefined, null, and present values - Validation: Builder pattern validates required fields and prevents invalid null assignments
- JSON Serialization: Proper handling of optional fields in JSON serialization/deserialization
C# Implementation
The C# SDK uses Optional<T>
wrapper types for optional fields, providing clear distinction between undefined, null, and present values.
To enable this feature in C#, set useOptionalWrapper: true
in your liblab configuration file under the csharp
language options. This flag is enabled by default when using liblab init
with C#.
For more details, see C# specific options.
Generated Model
public record ModelWithOptionalNullableDefaultValues(
[property: JsonPropertyName("requiredString")] string RequiredString,
[property: JsonPropertyName("requiredInteger")] long RequiredInteger,
[property: JsonPropertyName("requiredNullableString")] string? RequiredNullableString,
[property: JsonPropertyName("requiredNullableInteger")] long? RequiredNullableInteger,
[property:
JsonPropertyName("optionalString"),
JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)
]
Optional<string> OptionalString = default,
[property:
JsonPropertyName("optionalInteger"),
JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)
]
Optional<long> OptionalInteger = default,
[property:
JsonPropertyName("optionalNullableString"),
JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)
]
Optional<string?> OptionalNullableString = default,
[property:
JsonPropertyName("optionalNullableInteger"),
JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)
]
Optional<long?> OptionalNullableInteger = default
);
Usage Examples
// Example 1: All fields set
var fullModel = new ModelWithOptionalNullableDefaultValues(
"requiredString", // Required Non-Nullable
8, // Required Non-Nullable
"requiredNullableString", // Required Nullable
2, // Required Nullable
"optionalString", // Optional Non-Nullable
9, // Optional Non-Nullable
"optionalNullableString", // Optional Nullable
8 // Optional Nullable
);
// Example 2: Optional fields omitted
var minimalModel = new ModelWithOptionalNullableDefaultValues(
"requiredString",
8,
"requiredNullableString",
2
// Optional fields omitted - they will be omitted from JSON when serialized and sent to API
);
// Example 3: Mixed optional field values
var mixedModel = new ModelWithOptionalNullableDefaultValues(
"requiredString",
8,
"requiredNullableString",
2,
Optional<string>.Of("present"), // Optional non-nullable with value
Optional<long>.NotProvided(), // Optional non-nullable omitted
Optional<string?>.Of(null), // Optional nullable set to null
Optional<long?>.NotProvided() // Optional nullable omitted
);
// What the server receives for Example 1 (all fields set):
// {
// "requiredString": "requiredString",
// "requiredInteger": 8,
// "requiredNullableString": "requiredNullableString",
// "requiredNullableInteger": 2,
// "optionalString": "optionalString",
// "optionalInteger": 9,
// "optionalNullableString": "optionalNullableString",
// "optionalNullableInteger": 8
// }
// What the server receives for Example 2 (minimal model):
// {
// "requiredString": "requiredString",
// "requiredInteger": 8,
// "requiredNullableString": "requiredNullableString",
// "requiredNullableInteger": 2
// // Note: optional fields are completely omitted from JSON
// }
// What the server receives for Example 3 (mixed model):
// {
// "requiredString": "requiredString",
// "requiredInteger": 8,
// "requiredNullableString": "requiredNullableString",
// "requiredNullableInteger": 2,
// "optionalString": "present",
// "optionalNullableString": null
// // Note: optionalInteger and optionalNullableInteger are omitted
// }
Key Features
- Clear Semantics:
Optional<T>
clearly distinguishes between undefined, null, and present values - Validation: Ensures required fields are present and prevents invalid null assignments
- JSON Serialization: Proper handling of optional fields in JSON serialization/deserialization
- Flexible Construction: Can use direct values or explicit
Optional<T>.Of()
andOptional<T>.NotProvided()
methods
Key Benefits
- Cross-Language Consistency: Similar patterns and behaviors across all supported languages
- Clear Semantics: Explicit distinction between undefined, null, and present values
- API Contract Compliance: Ensures generated SDKs properly handle all OpenAPI field types
- Developer Experience: Intuitive APIs that match each language's idioms and conventions
Best Practices
- Always specify required fields: Mark fields as required in your OpenAPI spec when they must be present
- Use nullable appropriately: Only mark fields as nullable when they can legitimately be null
- Test edge cases: Verify behavior with undefined, null, and present values
- Document field requirements: Clearly document which fields are optional vs required in your API documentation
- Validate on the server: Always validate field presence and nullability on your API server, regardless of client-side handling