Inline Attributes¶
Inline attributes are a key feature of MetaObjects that dramatically reduce metadata verbosity by allowing attributes to be specified directly within metadata definitions rather than as separate child elements. This feature provides approximately 60% reduction in JSON file size for attribute-heavy metadata.
Overview¶
Traditional metadata systems require verbose nested structures for attributes. MetaObjects' inline attribute syntax provides a clean, readable alternative that maintains full type safety and validation.
Traditional vs Inline Syntax¶
Traditional Verbose Format:
{
"field": {
"name": "email",
"type": "string",
"children": [
{
"attr": {
"name": "required",
"type": "boolean",
"value": true
}
},
{
"attr": {
"name": "maxLength",
"type": "int",
"value": 255
}
},
{
"attr": {
"name": "pattern",
"type": "string",
"value": "^[\\w._%+-]+@[\\w.-]+\\.[A-Za-z]{2,}$"
}
}
]
}
}
Modern Inline Format:
{
"field": {
"name": "email",
"type": "string",
"@required": true,
"@maxLength": 255,
"@pattern": "^[\\w._%+-]+@[\\w.-]+\\.[A-Za-z]{2,}$"
}
}
Reduction: From 22 lines to 8 lines (64% reduction)
Format Support¶
MetaObjects supports inline attributes in both JSON and XML formats with different syntax conventions:
JSON Format (@-prefixed)¶
JSON inline attributes use the @
prefix to distinguish them from standard metadata properties:
{
"metadata": {
"package": "com_example_model",
"children": [
{
"object": {
"name": "User",
"type": "pojo",
"@dbTable": "users",
"@auditable": true,
"children": [
{
"field": {
"name": "id",
"type": "long",
"@required": true,
"@dbColumn": "user_id",
"@isPrimaryKey": true
}
},
{
"field": {
"name": "email",
"type": "string",
"@required": true,
"@maxLength": 255,
"@pattern": "^[\\w._%+-]+@[\\w.-]+\\.[A-Za-z]{2,}$",
"@dbColumn": "email_address",
"@unique": true
}
},
{
"field": {
"name": "status",
"type": "string",
"@required": true,
"@defaultValue": "active",
"@allowedValues": ["active", "inactive", "pending"]
}
}
]
}
}
]
}
}
JSON Syntax Rules¶
- Prefix: All inline attributes must start with
@
- Naming: Attribute names follow standard identifier patterns (no spaces, special chars)
- Values: JSON values (strings, numbers, booleans, arrays)
- Placement: Can appear anywhere within the metadata object definition
XML Format (Direct Attributes)¶
XML inline attributes use standard XML attribute syntax without prefixes:
<?xml version="1.0" encoding="UTF-8"?>
<metadata package="com_example_model">
<children>
<object name="User" type="pojo" dbTable="users" auditable="true">
<children>
<field name="id" type="long"
required="true"
dbColumn="user_id"
isPrimaryKey="true" />
<field name="email" type="string"
required="true"
maxLength="255"
pattern="^[\w._%+-]+@[\w.-]+\.[A-Za-z]{2,}$"
dbColumn="email_address"
unique="true" />
<field name="status" type="string"
required="true"
defaultValue="active"
allowedValues="active,inactive,pending" />
</children>
</object>
</children>
</metadata>
XML Syntax Rules¶
- No Prefix: XML attributes use standard XML syntax
- Value Types: All values are strings that get type-converted
- Arrays: Comma-separated values for array types
- Escaping: Standard XML escaping for special characters
Automatic Type Conversion¶
MetaObjects automatically converts inline attribute values to appropriate typed attributes based on the expected attribute type:
Type Detection System¶
The parser uses intelligent type detection to create the correct attribute types:
// Type-aware parsing system
public Class<?> getExpectedAttributeType(String attributeName) {
switch (attributeName) {
case "required":
case "unique":
case "auditable":
case "isPrimaryKey":
return Boolean.class;
case "maxLength":
case "minLength":
case "displayOrder":
case "retentionDays":
return Integer.class;
case "minValue":
case "maxValue":
return Long.class;
case "scale":
case "precision":
return Double.class;
default:
return String.class;
}
}
Conversion Examples¶
Boolean Conversion:
"@required": true → BooleanAttribute("required", true)
"@unique": false → BooleanAttribute("unique", false)
Integer Conversion:
"@maxLength": 255 → IntAttribute("maxLength", 255)
"@displayOrder": 10 → IntAttribute("displayOrder", 10)
String Conversion:
"@dbColumn": "email_address" → StringAttribute("dbColumn", "email_address")
"@pattern": "^[a-zA-Z]+$" → StringAttribute("pattern", "^[a-zA-Z]+$")
Array Conversion:
"@allowedValues": ["active", "inactive"] → StringArrayAttribute("allowedValues", ["active", "inactive"])
"@tags": ["user", "contact"] → StringArrayAttribute("tags", ["user", "contact"])
XML String-to-Type Conversion¶
XML attributes are parsed as strings and converted to appropriate types:
<!-- Boolean conversion -->
required="true" → BooleanAttribute("required", true)
auditable="false" → BooleanAttribute("auditable", false)
<!-- Numeric conversion -->
maxLength="255" → IntAttribute("maxLength", 255)
scale="2.5" → DoubleAttribute("scale", 2.5)
<!-- Array conversion (comma-separated) -->
allowedValues="active,inactive,pending" → StringArrayAttribute("allowedValues", ["active", "inactive", "pending"])
Parser Implementation¶
BaseMetaDataParser Integration¶
The inline attribute system is implemented in the BaseMetaDataParser
class, providing consistent behavior across JSON and XML parsers:
public abstract class BaseMetaDataParser {
/**
* Parse inline attributes with type-aware conversion
*/
protected void parseInlineAttribute(MetaData md, String attrName, String stringValue) {
// Determine expected type from metadata field information
String attributeSubType = getAttributeSubTypeFromMetaData(md, attrName);
Class<?> expectedType = getExpectedJavaTypeFromMetaData(md, attrName);
// Convert string value to expected type
Object castedValue = convertStringToExpectedType(stringValue, expectedType);
String finalValue = castedValue != null ? castedValue.toString() : null;
// Create typed attribute
createInlineAttributeWithDetectedType(md, attrName, finalValue, attributeSubType);
}
/**
* Create appropriate attribute type based on detected type
*/
private void createInlineAttributeWithDetectedType(MetaData md, String attrName,
String value, String subType) {
MetaAttribute attr = switch (subType) {
case "boolean" -> new BooleanAttribute(attrName);
case "int" -> new IntAttribute(attrName);
case "long" -> new LongAttribute(attrName);
case "double" -> new DoubleAttribute(attrName);
case "stringarray" -> new StringArrayAttribute(attrName);
default -> new StringAttribute(attrName);
};
attr.setValueAsString(value);
md.addChild(attr);
}
}
JSON Parser Implementation¶
The JsonMetaDataParser
extends the base parser with JSON-specific handling:
public class JsonMetaDataParser extends BaseMetaDataParser {
public static final String JSON_ATTR_PREFIX = "@";
protected void parseJsonObject(JsonObject jsonObj, MetaData parent) {
for (Map.Entry<String, JsonElement> entry : jsonObj.entrySet()) {
String key = entry.getKey();
JsonElement value = entry.getValue();
if (key.startsWith(JSON_ATTR_PREFIX)) {
// Handle inline attribute
String attrName = key.substring(1); // Remove @ prefix
parseInlineAttributeFromJson(parent, attrName, value);
} else {
// Handle regular metadata property
parseMetadataProperty(parent, key, value);
}
}
}
private void parseInlineAttributeFromJson(MetaData md, String attrName, JsonElement value) {
String stringValue = convertJsonElementToString(value);
parseInlineAttribute(md, attrName, stringValue);
}
}
XML Parser Implementation¶
The XMLMetaDataParser
handles XML attribute syntax:
public class XMLMetaDataParser extends BaseMetaDataParser {
protected void parseXmlElement(Element element, MetaData parent) {
// Parse XML attributes as inline attributes
NamedNodeMap attributes = element.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node attr = attributes.item(i);
String attrName = attr.getNodeName();
String attrValue = attr.getNodeValue();
// Skip standard metadata attributes (name, type, etc.)
if (!isStandardMetadataAttribute(attrName)) {
parseInlineAttribute(parent, attrName, attrValue);
}
}
// Parse child elements
parseChildElements(element, parent);
}
}
Validation and Error Handling¶
Parse-Time Validation¶
Inline attributes are validated immediately during parsing to provide fast feedback:
public void validateInlineAttributeUsage(MetaData md, String attrName) {
// Check if attribute type is registered
String defaultSubType = registry.getDefaultSubType("attr");
if (defaultSubType == null) {
throw new MetaDataException(
"Inline attributes require attr type default subType to be registered. " +
"Ensure AttributeTypesMetaDataProvider is loaded."
);
}
// Validate attribute naming pattern
if (!attrName.matches("^[a-zA-Z][a-zA-Z0-9_]*$")) {
throw new MetaDataException(
String.format("Invalid inline attribute name '%s'. " +
"Must follow identifier pattern: ^[a-zA-Z][a-zA-Z0-9_]*$", attrName)
);
}
}
Error Context¶
Rich error messages help developers debug inline attribute issues:
try {
parseInlineAttribute(metaData, attrName, value);
} catch (Exception e) {
throw new MetaDataException(
String.format("Failed to parse inline attribute '@%s' with value '%s' on %s '%s': %s",
attrName, value, metaData.getType(), metaData.getName(), e.getMessage()),
e,
Optional.of(MetaDataPath.from(metaData).append("@" + attrName))
);
}
Best Practices¶
1. Use Consistent Naming Conventions¶
// ✅ GOOD - Consistent camelCase naming
{
"@required": true,
"@maxLength": 255,
"@dbColumn": "email_address",
"@displayName": "Email Address"
}
// ❌ AVOID - Inconsistent naming
{
"@Required": true,
"@max_length": 255,
"@db-column": "email_address"
}
2. Group Related Attributes¶
// ✅ GOOD - Logical grouping for readability
{
"field": {
"name": "email",
"type": "string",
// Validation attributes
"@required": true,
"@maxLength": 255,
"@pattern": "^[\\w._%+-]+@[\\w.-]+\\.[A-Za-z]{2,}$",
// Database attributes
"@dbColumn": "email_address",
"@unique": true,
"@indexed": true,
// UI attributes
"@displayName": "Email Address",
"@helpText": "Enter a valid email address",
"@displayOrder": 10
}
}
3. Use Type-Appropriate Values¶
// ✅ GOOD - Proper JSON types
{
"@required": true, // Boolean, not "true"
"@maxLength": 255, // Number, not "255"
"@allowedValues": ["a", "b"] // Array, not "a,b"
}
// ❌ AVOID - String values for non-string types
{
"@required": "true", // Should be boolean
"@maxLength": "255", // Should be number
"@allowedValues": "a,b" // Should be array
}
4. Validate Regex Patterns¶
// ✅ GOOD - Properly escaped regex
{
"@pattern": "^[\\w._%+-]+@[\\w.-]+\\.[A-Za-z]{2,}$"
}
// ❌ AVOID - Unescaped backslashes
{
"@pattern": "^[\w._%+-]+@[\w.-]+\.[A-Za-z]{2,}$"
}
5. Use Comments for Complex Patterns¶
{
"field": {
"name": "phoneNumber",
"type": "string",
// International phone number pattern (E.164 format)
"@pattern": "^\\+[1-9]\\d{1,14}$",
"@maxLength": 15,
// Example: +1234567890
"@defaultValue": "+1234567890"
}
}
Common Attribute Patterns¶
Database Mapping¶
{
"object": {
"name": "User",
"type": "pojo",
"@dbTable": "users",
"@dbSchema": "public",
"@auditable": true
}
}
{
"field": {
"name": "email",
"type": "string",
"@dbColumn": "email_address",
"@dbType": "VARCHAR(255)",
"@nullable": false,
"@unique": true,
"@indexed": true
}
}
Validation Rules¶
{
"field": {
"name": "age",
"type": "int",
"@required": true,
"@minValue": 0,
"@maxValue": 150,
"@defaultValue": 18
}
}
{
"field": {
"name": "password",
"type": "string",
"@required": true,
"@minLength": 8,
"@maxLength": 128,
"@pattern": "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]+$"
}
}
UI Configuration¶
{
"field": {
"name": "email",
"type": "string",
"@displayName": "Email Address",
"@helpText": "Enter your primary email address",
"@placeholder": "user@example.com",
"@uiComponent": "email-input",
"@displayOrder": 10,
"@cssClasses": ["form-control", "required-field"],
"@readonly": false,
"@hidden": false
}
}
Business Rules¶
{
"field": {
"name": "ssn",
"type": "string",
"@required": true,
"@pattern": "^\\d{3}-\\d{2}-\\d{4}$",
"@encrypted": true,
"@piiData": true,
"@retentionDays": 2555,
"@accessLevel": "confidential"
}
}
Performance Implications¶
Parsing Performance¶
Inline attributes provide significant parsing performance benefits:
- Reduced object creation: Fewer nested objects to parse
- Direct assignment: Values assigned directly during parsing
- Smaller memory footprint: Less complex object graph
Runtime Performance¶
- Cached access: Attributes cached using MetaObjects' cache strategy
- Type-safe retrieval: No runtime type conversion needed
- Optimized lookups: Direct hash map access for attribute retrieval
Memory Usage¶
- 60% reduction: In JSON file size for attribute-heavy metadata
- Fewer objects: Less memory pressure during loading
- Efficient storage: Typed attribute values stored directly
Migration from Verbose Format¶
Automated Conversion¶
You can convert existing verbose metadata to inline format:
public class MetadataFormatConverter {
public JsonObject convertToInlineFormat(JsonObject verbose) {
JsonObject result = new JsonObject();
for (Map.Entry<String, JsonElement> entry : verbose.entrySet()) {
String key = entry.getKey();
JsonElement value = entry.getValue();
if ("children".equals(key) && value.isJsonArray()) {
JsonArray children = value.getAsJsonArray();
convertChildrenToInline(result, children);
} else {
result.add(key, value);
}
}
return result;
}
private void convertChildrenToInline(JsonObject parent, JsonArray children) {
JsonArray newChildren = new JsonArray();
for (JsonElement child : children) {
if (isAttributeObject(child)) {
// Convert attr object to inline attribute
convertAttributeToInline(parent, child.getAsJsonObject());
} else {
// Keep non-attribute children
newChildren.add(child);
}
}
if (newChildren.size() > 0) {
parent.add("children", newChildren);
}
}
}
Manual Conversion Steps¶
- Identify attribute children: Look for
{"attr": {...}}
objects - Extract attribute properties: Get name, type, and value
- Convert to inline syntax: Use
@name: value
format - Remove attr objects: Delete the verbose attribute definitions
- Validate result: Ensure parsing works correctly
Troubleshooting¶
Common Issues¶
Issue: Parse error "Invalid inline attribute name"
Solution: Ensure attribute names follow identifier pattern: ^[a-zA-Z][a-zA-Z0-9_]*$
Example: Use "maxLength" not "max-length" or "max_length"
Issue: Type conversion error
Solution: Use appropriate JSON types for values:
- Booleans: true/false (not "true"/"false")
- Numbers: 255 (not "255")
- Arrays: ["a", "b"] (not "a,b")
Issue: Attribute not recognized in XML
Solution: Ensure XML attribute doesn't conflict with standard metadata attributes
(name, type, subType, package). Use custom names like "dbColumn", "maxLength".
Issue: Missing attribute type registration
Solution: Ensure AttributeTypesMetaDataProvider is loaded before parsing.
Check that registry.getDefaultSubType("attr") returns a valid subtype.
Next Steps¶
-
Attribute Framework
Understand the complete attribute type system
-
Constraints
Learn how constraints validate inline attributes
-
JSON Processing
Explore JSON metadata parsing and serialization
-
Examples
See working examples with inline attributes
Inline attributes provide a clean, efficient way to define metadata with rich attribute information while maintaining full type safety and validation capabilities. The 60% reduction in verbosity makes metadata more readable and maintainable without sacrificing any functionality.