How to Implement Data Desensitization with YAML and Java Maps
This article explains a step‑by‑step approach to mask sensitive fields in API responses by defining desensitization rules in YAML, loading them into Java Maps, and applying recursive logic to traverse nested structures and replace data using regular expressions.
Introduction
When a project requires masking specific fields in a transaction interface response without using AOP annotations, a straightforward method is to store field‑to‑rule mappings in a Map, iterate over the result, and apply the appropriate rule.
Understanding YAML Format Specification
Because the returned result may contain nested Map s, we store desensitization rules in a YAML file to ensure consistent maintenance and avoid errors.
YAML (YAML Ain’t Markup Language) is a lightweight data‑serialization format designed for human readability and easy parsing, compared with JSON, XML, and Properties files.
Compared with JSON: YAML syntax is more flexible and concise.
Compared with XML: YAML avoids verbose tags and angle brackets.
Compared with Properties: YAML supports complex structures like nested key‑value pairs and lists.
1. Basic Syntax
Use indentation to represent hierarchy (spaces or tabs, but not mixed).
Use a colon : to denote key‑value pairs.
Use a dash - to denote list items.
# Using indentation to represent hierarchy
server:
port: 8080
# Using colon for key‑value
name: John Smith
age: 30
# Using dash for list items
hobbies:
- reading
- hiking
- swimming2. Comments
Use # for comments, which can appear at the beginning or end of a line.
# This is a comment
name: John Smith # Inline comment3. Strings
Strings can be single‑quoted, double‑quoted, or unquoted.
Double quotes allow escape sequences such as \n for newlines and \u for Unicode.
# Double‑quoted string
name: "John Smith"
# Single‑quoted string
nickname: 'Johnny'4. Key‑Value Pairs
Key and value are separated by a colon and a space.
Keys can be strings or scalars; values can be strings, scalars, lists, or nested maps.
name: John Smith
age: 30
address:
city: San Francisco
state: California
zip: 941075. Lists
Use a dash - to denote list items, which can be strings, scalars, or nested structures.
hobbies:
- reading
- hiking
- swimming
people:
- name: John Smith
age: 30
- name: Jane Doe
age: 256. References
Use & to define an anchor and * to reference it.
address: &myaddress
city: San Francisco
state: California
shippingAddress: *myaddress7. Multi‑Line Text Blocks
Use | to preserve line breaks.
Use > to fold line breaks into a single line.
# Preserving line breaks
description: |
This is a
multi‑line
string.
# Folding line breaks
summary: >
This is a summary
that may contain
line breaks.8. Data Types
YAML supports strings, integers, floats, booleans, dates, etc.
Special tags like !!str, !!int can explicitly specify types.
age: !!int 30
weight: !!float 65.5
isMale: !!bool true
created: !!timestamp '2022-01-01 12:00:00'9. Multi‑File
Use --- to separate multiple YAML documents.
# First file
name: John Smith
age: 30
---
# Second file
hobbies:
- reading
- hiking
- swimmingDefining Desensitization Rule Format
For simple structures, the rule format is TransactionID->Field->Rule:
TransactionID:
fieldName:
rule: '/^(1[3-9][0-9])\d{4}(\d{4}$)/'For nested lists, the format becomes TransactionID->Field(List)->Field->Rule:
TransactionID:
listField:
subField:
rule: '/^(1[3-9][0-9])\d{4}(\d{4}$)/'Using this hierarchy, we can retrieve a rule with Map.get("Key").
Desensitization Logic Implementation
Reading YAML Configuration
1. Create desensitize.yml with rules, e.g.:
Y3800:
phone:
rule: "(\\d{3})\\d{4}(\\d{4})"
format: "$1****$2"
idCard:
rule: "(?<=\\w{6})\\w(?=\\w{4})"
format: "*"
Y3801:
idCard:
rule: "(?<=\\w{3})\\w(?=\\w{4})"
format: "+"
list:
phone:
rule: "(\\d{3})\\d{4}(\\d{4})"
format: "$1++++$2"2. Define a utility class DataDesensitizationUtils and implement a method to load the YAML file into a Map<String, Object>:
public static Map<String, Object> loadYaml(String yamlFile) {
Yaml yaml = new Yaml();
try (InputStream in = DataDesensitizationUtils.class.getResourceAsStream(yamlFile)) {
return yaml.loadAs(in, Map.class);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}Retrieving Nested Rules by Key Path
Recursive method getNestedMapValues(Map, String... keys) traverses nested maps until the innermost map is found.
public static Map<String, Object> getNestedMapValues(Map<String, Object> map, String... keys) {
if (keys.length == 0 || !map.containsKey(keys[0])) return null;
Object nested = map.get(keys[0]);
if (keys.length == 1) {
return nested instanceof Map ? (Map<String, Object>) nested : null;
}
return nested instanceof Map ? getNestedMapValues((Map<String, Object>) nested, Arrays.copyOfRange(keys, 1, keys.length)) : null;
}Applying Desensitization
Method desensitizeLogic(String data, Map<String, Object> map) uses the rule’s regex and format to mask the data.
private static String desensitizeLogic(String data, Map<String, Object> map) {
if (map.containsKey("rule")) {
String rule = (String) map.get("rule");
String sign = map.getOrDefault("format", "*").toString();
return data.replaceAll(rule, sign);
}
return data;
}Recursive Traversal of Entity Data
The core method parseData(Object entity, String servNo, String path) walks through maps and lists, builds the current key path, fetches the corresponding rule via getNestedMap, and replaces the value while logging the process.
public static void parseData(Object entity, String servNo, String path) {
if (entity instanceof Map) {
for (Map.Entry<String, Object> entry : ((Map<String, Object>) entity).entrySet()) {
String currentPath = path.isEmpty() ? entry.getKey() : path + "," + entry.getKey();
if (entry.getValue() instanceof Map) {
parseData(entry.getValue(), servNo, currentPath);
} else if (entry.getValue() instanceof List) {
for (Object item : (List) entry.getValue()) {
if (item instanceof Map) parseData(item, servNo, currentPath);
}
} else {
String[] keyPaths = (servNo + "," + currentPath).split(",");
Map<String, Object> ruleMap = getNestedMap(keyPaths);
if (ruleMap != null) {
log.info("--- Transaction [{}], Field [{}] start desensitization ---", servNo, currentPath.replace(",", "->"));
log.info("Original: [{}:{}]", entry.getKey(), entry.getValue());
log.info("Rule: {}", ruleMap);
String desensitized = desensitizeLogic(entry.getValue().toString(), ruleMap);
entry.setValue(desensitized);
log.info("Desensitized: [{}:{}]", entry.getKey(), entry.getValue());
log.info("--- Transaction [{}], Field [{}] desensitization end ---", servNo, currentPath.replace(",", "->"));
}
}
}
}
}Testing
A Demo class builds sample data, loads the YAML rules, and calls parseData. Console output shows the original and masked values for fields like idCard and phone.
-----------------交易【Y3801】,字段【idCard】开始脱敏-----------------
原始值:【idCard:130428197001180384】
脱敏规则:{rule=(?<=\w{3})\w(?=\w{4}), format=+}
脱敏值:【idCard:130+++++++++++0384】
-----------------交易【Y3801】,字段【idCard】脱敏结束-----------------
...Complete Utility Class
The final DataDesensitizationUtils class combines YAML loading, nested map retrieval, recursive traversal, and masking logic into a reusable component for data privacy handling in backend services.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
