When building flexible, configuration-driven applications—such as rule engines, dynamic form processors, or metadata-driven reporting systems—it's often necessary to read from or write to object fields by name at runtime. This requirement arises when field identifiers are stored externally (e.g., in a database or configuration file) and cannnot be hard-coded.
The following implementation provides a lightweight, robust approach using standard Java reflection for direct field manipulation and com.fasterxml.jackson.databind.ObjectMapper (a more portable and secure alternative to FastJSON) for generic property extraction via serialization/deserialization.
Core Utility: Generic Property Extraction
public class PropertyAccessor { private static final ObjectMapper mapper = new ObjectMapper();
/**
* Serializes an object to a JsonNode and retrieves the string value of a field.
* Returns empty string if field is missing or null.
*/
public static String getStringValue(Object obj, String fieldName) {
if (obj == null || fieldName == null) return "";
try {
JsonNode node = mapper.valueToTree(obj);
JsonNode target = node.get(fieldName);
return (target != null && !target.isNull()) ? target.asText() : "";
} catch (Exception e) {
// Log error in production (e.g., SLF4J)
return "";
}
}
/**
* Safely sets a field value using reflection.
* Supports private fields and basic type coercion (String → primitive wrappers).
*/
public static boolean setFieldValue(Object instance, String fieldName, Object value) {
if (instance == null || fieldName == null) return false;
try {
Class> clazz = instance.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
Class> targetType = field.getType();
Object coercedValue = coerceValue(value, targetType);
field.set(instance, coercedValue);
return true;
} catch (NoSuchFieldException | IllegalAccessException e) {
return false;
}
}
private static Object coerceValue(Object input, Class> targetType) {
if (input == null || targetType.isInstance(input)) return input;
if (targetType == Integer.class || targetType == int.class) {
return input instanceof Number ? ((Number) input).intValue() : Integer.parseInt(input.toString());
}
if (targetType == Boolean.class || targetType == boolean.class) {
return Boolean.parseBoolean(input.toString());
}
if (targetType == Double.class || targetType == double.class) {
return input instanceof Number ? ((Number) input).doubleValue() : Double.parseDouble(input.toString());
}
return input.toString();
}
}
</div>### Configuration-Aware Resolver
This resolver interprets dot-notation paths (e.g., `user.profile.name`) and supports conditional expressions using a simple ternary syntax (`status=="active"?1:0`). It delegates actual field access to `PropertyAccessor`.
<div>```
import java.util.*;
import java.util.stream.Collectors;
public class ConfigurablePropertyResolver {
/**
* Resolves a property expression like "User.name" or "status==\"ON\"?1:0"
* @param context Object serving as the evaluation context
* @param expression Expression string (may contain dots or ternary logic)
* @return Evaluated string result
*/
public static String resolve(Object context, String expression) {
if (expression == null || context == null) return "";
// Handle ternary: status=="ON"?1:0
if (expression.contains("?") && expression.contains(":")) {
int qIndex = expression.indexOf('?');
int cIndex = expression.indexOf(':');
if (qIndex > 0 && cIndex > qIndex) {
String conditionPart = expression.substring(0, qIndex).trim();
String truePart = expression.substring(qIndex + 1, cIndex).trim();
String falsePart = expression.substring(cIndex + 1).trim();
if (conditionPart.contains("==")) {
String[] parts = conditionPart.split("==", 2);
String field = parts[0].trim();
String expected = parts[1].trim().replaceAll("[\"'\\s]", "");
String actual = PropertyAccessor.getStringValue(context, field);
return Objects.equals(actual, expected) ? truePart : falsePart;
}
}
}
// Handle dot-chained field access (e.g., "address.city")
String[] segments = expression.split("\\.");
Object current = context;
for (String segment : segments) {
if (current == null) break;
current = PropertyAccessor.getStringValue(current, segment);
if (segment.equals(segments[segments.length - 1])) {
return (String) current;
}
}
return "";
}
/**
* Binds multiple expressions separated by semicolons into a concatenated result.
* Useful for composite display labels or derived keys.
*/
public static String resolveMultiple(Object context, String expressions) {
if (expressions == null) return "";
return Arrays.stream(expressions.split(";"))
.map(expr -> resolve(context, expr.trim()))
.filter(Objects::nonNull)
.collect(Collectors.joining(""));
}
}
// Read field by name
String name = PropertyAccessor.getStringValue(person, "name"); // "Alice"
// Write field by name
PropertyAccessor.setFieldValue(person, "age", "33");
// Resolve conditional expression
String statusLabel = ConfigurablePropertyResolver.resolve(person, "name==\"Alice\"?\"VIP\":\"USER\""); // "VIP"
// Resolve chained path (if Person has an Address field)
// String city = ConfigurablePropertyResolver.resolve(person, "address.city");
// Resolve multiple expressions
String summary = ConfigurablePropertyResolver.resolveMultiple(person,
"name; ;age; ;status==\"active\"?\"✓\":\"✗\""); // "Alice 33 ✗"
}
}
</div>