如何用Java解析YAML注释?基于jackson-dataformat-yaml的实现方案
Got it, let's figure out how to solve this in Java—since you're already familiar with ruamel.yaml's comment-handling in Python, we can replicate that functionality with the right libraries and approach. The core challenge here is that Jackson's jackson-dataformat-yaml doesn't preserve comments by default, so we'll need to use tools that let us extract and work with those comments, then tie it back to your command-line override needs.
1. Ditch Pure Jackson for a Comment-Aware YAML Library
Jackson's YAML parser relies on SnakeYAML under the hood, but it strips out comments during parsing. Instead, use SnakeYAML Engine (the modern, maintained version of SnakeYAML) which has built-in support for preserving and accessing comments.
First, add the dependency (Maven example):
<dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml-engine</artifactId> <version>2.7</version> </dependency>
2. Parse YAML & Extract Comments
SnakeYAML Engine lets you load YAML into a node-based structure, where you can directly access comments attached to each configuration key. Here's a sample parser that extracts comments for top-level config entries:
import org.yaml.snakeyaml.engine.v2.api.Load; import org.yaml.snakeyaml.engine.v2.api.LoadSettings; import org.yaml.snakeyaml.engine.v2.nodes.MappingNode; import org.yaml.snakeyaml.engine.v2.nodes.Node; import org.yaml.snakeyaml.engine.v2.nodes.ScalarNode; import java.io.FileInputStream; import java.io.InputStream; import java.util.HashMap; import java.util.Map; public class YamlCommentExtractor { public static Map<String, String> extractConfigDescriptions(String yamlPath) throws Exception { Map<String, String> descriptions = new HashMap<>(); // Enable comment processing in load settings LoadSettings settings = LoadSettings.builder() .setProcessComments(true) .build(); Load load = new Load(settings); try (InputStream inputStream = new FileInputStream(yamlPath)) { Node rootNode = load.loadFromInputStream(inputStream); if (rootNode instanceof MappingNode) { MappingNode mappingNode = (MappingNode) rootNode; mappingNode.getValue().forEach(entry -> { Node keyNode = entry.getKeyNode(); if (keyNode instanceof ScalarNode) { String configKey = ((ScalarNode) keyNode).getValue(); // Join multi-line comments into a single description String comment = keyNode.getBlockComments().stream() .map(String::trim) .reduce((a, b) -> a + "\n" + b) .orElse("No description available"); descriptions.put(configKey, comment); } }); } } return descriptions; } public static void main(String[] args) throws Exception { Map<String, String> configDescriptions = extractConfigDescriptions("config.yml"); configDescriptions.forEach((key, desc) -> System.out.printf("Key: %s%nDescription: %s%n%n", key, desc)); } }
This will pull comments like # Define a source directory... and map them to their corresponding config keys, exactly like you did with ruamel.yaml.
3. Combine with Jackson (If You Want to Keep POJO Binding)
If you still want to use Jackson for binding YAML values to your POJOs, you can split the workflow:
- Use SnakeYAML Engine to extract comments into a
Map<String, String> - Use Jackson to load the actual config values into your POJO class
Example:
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; public class ConfigLoader { public static void main(String[] args) throws Exception { // Step 1: Extract comments Map<String, String> configDescriptions = YamlCommentExtractor.extractConfigDescriptions("config.yml"); // Step 2: Load config values into POJO with Jackson YAMLMapper yamlMapper = new YAMLMapper(); AppConfig config = yamlMapper.readValue(new File("config.yml"), AppConfig.class); } } // Your POJO class class AppConfig { private String sourceDir; private int maxRetries; // Getters and setters }
4. Use Comments for Command-Line Overrides
With your configDescriptions map, you can integrate with a command-line library like Picocli or Apache Commons CLI to auto-generate help text from the YAML comments. Here's a quick Picocli example:
import picocli.CommandLine; import java.util.Map; @CommandLine.Command(name = "my-app", description = "My app with YAML config overrides") public class AppRunner implements Runnable { private final Map<String, String> configDescriptions; public AppRunner(Map<String, String> configDescriptions) { this.configDescriptions = configDescriptions; } @CommandLine.Option(names = "--source-dir", description = "${MAP:sourceDir}") private String sourceDir; @CommandLine.Option(names = "--max-retries", description = "${MAP:maxRetries}") private int maxRetries; @Override public void run() { // Your app logic here } public static void main(String[] args) throws Exception { Map<String, String> descriptions = YamlCommentExtractor.extractConfigDescriptions("config.yml"); CommandLine cmd = new CommandLine(new AppRunner(descriptions)); // Replace placeholders with actual YAML comments cmd.setCommandSpec(cmd.getCommandSpec().replacePlaceholders( key -> descriptions.getOrDefault(key, "No description") )); cmd.execute(args); } }
Now when users run my-app --help, they'll see the exact comments from your YAML file as descriptions for each command-line option.
内容的提问来源于stack exchange,提问作者Netherwire

