'How to parse part of .yaml file to object in SnakeYAML?
I have a yaml file, for example:
# this is the part I don't care about
config:
key-1: val-1
other-config:
lang: en
year: 1906
# below is the only part I care about
interesting-setup:
port: 1234
validation: false
parts:
- on-start: backup
on-stop: say-goodbye
Also I have a POJO class that is suitable for the interesting-setup part
public class InterestingSetup {
int port;
boolean validation;
List<Map<String, String>> parts;
}
I want to load just the interesting-setup part (similarly as @ConfigurationProperties("interesting-setup") in Spring)
Currently I'm doing it like this:
Map<String, Object> yamlConfig = yaml.load(yamlFile); # loading the whole file to Map with Object values
Object interestingObject = yamlConfig.get("interesting-setup"); # loading 'interesting-setup' part as an object
Map<String, Object> interestingMap = (Map<String, Object>); # Casting object to Map<String, Object>
String yamlDumped = yaml.dump(interestingMap); # Serialization to String
InterestingSetup finalObject = yaml.load(yamlDumped); # Getting final object from String
The crucial part is when I have an Object (Map<String, Object>) and want to cast it to my final class. To do that - I need to serialize it to String, so the process looks like this:
File -> Map<String, Object> -> Object -> Map<String, Object> -> String -> FinalClass
and I'd like to avoid deserialization and again serialization of the same data.
So can I somehow use Yaml to map the Map<String, Object> to another class? I cannot see this in an API?
Solution 1:[1]
AFAIK, the SnakeYAML library doesn't provide a straight way to do that.
You may try tweaking it and defining a container base class with only the fields you required to support. Consider for example the following POJO:
public class Container {
private InterestingSetup interestingSetup;
public InterestingSetup getInterestingSetup() {
return interestingSetup;
}
public void setInterestingSetup(InterestingSetup interestingSetup) {
this.interestingSetup = interestingSetup;
}
@Override
public String toString() {
return "Container{" +
"interestingSetup=" + interestingSetup +
'}';
}
}
Where, InterestingSetup is your own class:
import java.util.List;
import java.util.Map;
public class InterestingSetup {
private int port;
private boolean validation;
private List<Map<String, String>> parts;
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public boolean isValidation() {
return validation;
}
public void setValidation(boolean validation) {
this.validation = validation;
}
public List<Map<String, String>> getParts() {
return parts;
}
public void setParts(List<Map<String, String>> parts) {
this.parts = parts;
}
@Override
public String toString() {
return "InterestingSetup{" +
"port=" + port +
", validation=" + validation +
", parts=" + parts +
'}';
}
}
With those beans in place, the following code would work as you required:
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.representer.Representer;
public class Main {
public static void main(String... args) throws UnsupportedEncodingException {
String yamlString =
"# this is the part I don't care about\n" +
"config:\n" +
" key-1: val-1\n" +
"other-config:\n" +
" lang: en\n" +
" year: 1906\n" +
"# below is the only part I care about\n" +
"interesting-setup:\n" +
" port: 1234\n" +
" validation: false\n" +
" parts:\n" +
" - on-start: backup\n" +
" on-stop: say-goodbye";
// Skip unknown properties
Representer representer = new Representer();
representer.getPropertyUtils().setSkipMissingProperties(true);
// Define the target object type
Constructor constructor = new Constructor(Container.class);
TypeDescription containerTypeDescription = new TypeDescription(Container.class);
// Define how the interesting-setup property should be processed
containerTypeDescription.substituteProperty("interesting-setup", InterestingSetup.class,
"getInterestingSetup", "setInterestingSetup");
constructor.addTypeDescription(containerTypeDescription);
// Finally, parse the YAML
Yaml yaml = new Yaml(constructor, representer);
InputStream inputStream = new ByteArrayInputStream(yamlString.getBytes(StandardCharsets.UTF_8));;
Container container = yaml.load(inputStream);
System.out.println(container.getInterestingSetup());
}
}
Perhaps, a more simple solution will consists on using some method that allows you, given a bunch of fields and their corresponding values, to set the appropriate information in the InterestedSetup bean. You can use the Reflection API for that. The populate method in the BeansUtils class from Apache Commons can also be handy as well:
Map<String, Object> yamlConfig = yaml.load(yamlFile);
Object interestingObject = yamlConfig.get("interesting-setup");
Map<String, Object> interestingMap = (Map<String, Object>);
InterestingSetup finalObject = BeanUtils.populate(interestingMap);
As an alternate approach, you can use Jackson to process the YAML file. The code will be similar to this:
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
// As the helper object Container doesn't contain all the properties
// it is necessary to indicate that fact to the library to avoid
// errors
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Container container = mapper.readValue(yamlString, Container.class);
System.out.println(container.getInterestingSetup());
The Container class is the same presented above with the addition of a @JsonProperty annotation in order to successfully handle the interesting-setup field:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
// Instead of mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
// you can annotate the class with @JsonIgnoreProperties(ignoreUnknown = true)
// to avoid errors related to unknown properties
public class Container {
@JsonProperty("interesting-setup")
private InterestingSetup interestingSetup;
public InterestingSetup getInterestingSetup() {
return interestingSetup;
}
public void setInterestingSetup(InterestingSetup interestingSetup) {
this.interestingSetup = interestingSetup;
}
@Override
public String toString() {
return "Container{" +
"interestingSetup=" + interestingSetup +
'}';
}
}
The required artifacts can be downloaded from Maven as the following dependency:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.13.1</version>
</dependency>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
