This is the first time I am working with YAML files, so the first think I looked at was to find any library that could help me to parse the file.
I have found two libraries, YamlBean and SnakeYAML. I am not sure which one that I am going to use.
Here is an example of the file that I am trying to parse:
users:
user1:
groups:
- Premium
user2:
groups:
- Mod
user3:
groups:
- default
groups:
Mod:
permissions:
test: true
inheritance:
- Premium
default:
permissions:
test.test: true
inheritance:
- Mod
Admin:
permissions:
test.test.test: true
inheritance:
- Mod
The file will change dynamical so I don't know how many users or groups the file would contain.
The information I would like to fetch from this is the user name and the group like this:
user1 Premium
user2 Mod
user3 default
And from the groups only the group names, like this:
Mod
default
Admin
Anyone could get me started here? And what is the best library to use for this?
YamlBean or SnakeYAML?
I guess, I need to save the information in something that I easily could iterate over.
You could also use Jacksons YAML module.
In order to use that, you'll need a few classes. The model classes which will carry the content of your file and the a class that takes care of reading the YAML file.
The root model class could look like this:
public class MyYamlFile {
#JsonProperty
private List<User> users;
#JsonProperty
private List<Group> groups;
// getter methods ommitted
}
The User(*) class:
public class User {
#JsonProperty
private List<String> name;
#JsonProperty
private List<GroupType> groups;
// getter methods ommitted
}
The GroupType could be an Enum containing all possible group types:
public enum GroupType {
Premium, Mod, Default
}
Don't forget that the enum entries are case sensitive. So "premium" won't work.
You can build all your model classes that way. Every sub entry should get an own model class.
Now to the part where you can read that YAML file:
public MyYamlFile readYaml(final File file) {
final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); // jackson databind
return mapper.readValue(file, MyYamlFile.class);
}
As you can see, this part is really neat, because you don't need much. The file instance contains your YAML file. You can create one like this:
File file = new File("path/to/my/yaml/usersAndGroups.yaml");
Instead of File the readValue method also supports InputStream, java.io.Reader, String (with the whole content), java.net.URL and byte array.
You should find something that suits you.
(*) You should consider changing the structure of your YAML file, because I don't think it is possible to use dynamic keys with Jackson (maybe someone knows more about that):
users:
- name: user1
groups:
- Premium
- name: user2
groups:
- Mod
- name: user3
groups:
- Default
groups:
....
I ended up using SnakeYaml and made some split strings to solve my issue.
Loaded the yaml file to Object and then into a Map, then split the result from the Map into String[] and then in a for loop I read out the name from the String[]. I did the same with groups.
I know that there is better solutions out there but this is good enough for this project.
Thanks all for the replies.
Found this helpful link that will parse the input without touching java code, if ever need to change the config
https://stackabuse.com/reading-and-writing-yaml-files-in-java-with-snakeyaml
InputStream inputStream = new FileInputStream(new File("src/main/resources/customer.yaml"));
Yaml yaml = new Yaml();
Map<String, Object> data = yaml.load(inputStream);
System.out.println(data);
YamlBean is included to DMelt Java numeric computational environment (http://jwork.org/dmelt/). You can create Yaml files using jhplot.io.HFileYAML class which creates a key-value map and save as yaml file.
Related
I have a Yaml file that's something like below:
rules:
- p_table:
["p_event/Name",
"p_fault/Name"]
- s_table:
["s_event/Name",
"s_fault/Name"]
- r_table:
["r_event/Name",
"r_fault/Name"]
So, I can already take the .yml file above and parse through it with YamlBeans and print it out with code like below:
System.out.println(map.get("rules"));
This gives this kind of result:
[{p_table=[p_event/Name, p_fault/Name]},
{s_table=[s_event/Name, s_fault/Name]},
{r_table=[r_event/Name, r_fault/Name]}]
What I would like to do is more on this sort of level, where I can store it in a HashMap and actually use the specifics within the map, with something like this:
HashMap<String, ArrayList<Strings>> Policies = (HashMap)(map.get("rules"));
But when I do that I either have an exception thrown or it just returns null, is there a solution for this should I not be using HashMaps... or can I just not translate objects in such a way? I plan on replacing the String with another type from a different library that uses Strings but wanted to start at the bottom and then go up from there.
The obvious solution would be to remove the sequence from the YAML file:
rules:
p_table:
["p_event/Name",
"p_fault/Name"]
s_table:
["s_event/Name",
"s_fault/Name"]
r_table:
["r_event/Name",
"r_fault/Name"]
If you can't change the YAML file, you need to transform the data after loading it.
I want to use a single YAML file which contains several different objects - for different applications. I need to fetch one object to get an instance of MyClass1, ignoring the rest of docs for MyClass2, MyClass3, etc. Some sort of selective de-serializing: now this class, then that one... The structure of MyClass2, MyClass3 is totally unknown to the application working with MyClass1. The file is always a valid YAML, of course.
The YAML may be of any structure we need to implement such a multi-class container. The preferred parsing tool is snakeyaml.
Is it sensible? How can I ignore all but one object?
UPD: replaced all "document" with "object". I think we have to speak about the single YAML document containing several objects of different structure. More of it, the parser knows exactly only 1 structure and wants to ignore the rest.
UDP2: I think it is impossible with snakeyaml. We have to read all objects anyway - and select the needed one later. But maybe I'm wrong.
UPD2: sample config file
---
-
exportConfiguration781:
attachmentFieldName: "name"
baseSftpInboxPath: /home/user/somedir/
somebool: false
days: 9999
expected:
- ABC w/o quotes
- "Cat ABC"
- "Some string"
dateFormat: yyyy-MMdd-HHmm
user: someuser
-
anotherConfiguration:
k1: v1
k2:
- v21
- v22
This is definitely possible with SnakeYAML, albeit not trivial. Here's a general rundown what you need to do:
First, let's have a look what loading with SnakeYAML does. Here's the important part of the YAML class:
private Object loadFromReader(StreamReader sreader, Class<?> type) {
Composer composer = new Composer(new ParserImpl(sreader), resolver, loadingConfig);
constructor.setComposer(composer);
return constructor.getSingleData(type);
}
The composer parses YAML input into Nodes. To do that, it doesn't need any knowledge about the structure of your classes, since every node is either a ScalarNode, a SequenceNode or a MappingNode and they just represent the YAML structure.
The constructor takes a root node generated by the composer and generates native POJOs from it. So what you want to do is to throw away parts of the node graph before they reach the constructor.
The easiest way to do that is probably to derive from Composer and override two methods like this:
public class MyComposer extends Composer {
private final int objIndex;
public MyComposer(Parser parser, Resolver resolver, int objIndex) {
super(parser, resolver);
this.objIndex = objIndex;
}
public MyComposer(Parser parser, Resolver resolver, LoaderOptions loadingConfig, int objIndex) {
super(parser, resolver, loadingConfig);
this.objIndex = objIndex;
}
#Override
public Node getNode() {
return strip(super.getNode());
}
private Node strip(Node input) {
return ((SequenceNode)input).getValue().get(objIndex);
}
}
The strip implementation is just an example. In this case, I assumed your YAML looks like this (object content is arbitrary):
- {first: obj}
- {second: obj}
- {third: obj}
And you simply select the object you actually want to deserialize by its index in the sequence. But you can also have something more complex like a searching algorithm.
Now that you have your own composer, you can do
Constructor constructor = new Constructor();
// assuming we want to get the object at index 1 (i.e. second object)
Composer composer = new MyComposer(new ParserImpl(sreader), new Resolver(), 1);
constructor.setComposer(composer);
MyObject result = (MyObject)constructor.getSingleData(MyObject.class);
The answer of #flyx was very helpful for me, opening the way to workaround the library (in our case - snakeyaml) limitations by overriding some methods. Thanks a lot! It's quite possible there is a final solution in it - but not now. Besides, the simple solution below is robust and should be considered even if we'd found the complete library-intruding solution.
I've decided to solve the task by double distilling, sorry, processing the configuration file. Imagine the latter consisting of several parts and every part is marked by the unique token-delimiter. For the sake of keeping the YAML-likenes, it may be
---
#this is a unique key for the configuration A
<some YAML document>
---
#this is another key for the configuration B
<some YAML document
The first pass is pre-processing. For the given String fileString and String key (and DELIMITER = "\n---\n". for example) we select a substring with the key-defined configuration:
int begIndex;
do {
begIndex= fileString.indexOf(DELIMITER);
if (begIndex == -1) {
break;
}
if (fileString.startsWith(DELIMITER + key, begIndex)) {
fileString = fileString.substring(begIndex + DELIMITER.length() + key.length());
break;
}
// spoil alien delimiter and repeat search
fileString = fileString.replaceFirst(DELIMITER, " ");
} while (true);
int endIndex = fileString.indexOf(DELIMITER);
if (endIndex != -1) {
fileString = fileString.substring(0, endIndex);
}
Now we feed the fileString to the simple YAML parsing
ExportConfiguration configuration = new Yaml(new Constructor(ExportConfiguration.class))
.loadAs(fileString, ExportConfiguration.class);
This time we have a single document that must co-respond to the ExportConfiguration class.
Note 1: The structure and even the very content of the rest of configuration file plays absolutely no role. This was the main idea, to get independent configurations in a single file
Note 2: the rest of configurations may be JSON or XML or whatever. We have a method-preprocessor that returns a String configuration - and the next processor parses it properly.
I've a value object(VO). One of the field/property is 'sourceKey' that holds a string value.
For Example:
String sourceKey1 = "cust12/proj1/site1/images/somefile.JPG"
String sourceKey2 = "cust12/area1/site1/images/somefile.JPG"
Now I need to kinda transform this sourceKey and build up a destination key by first breaking the source key by'/' and then:
- replace cust12 by calling customer service - find customer by Id 12 and replace cust12 by customer name in the dest key.
- similarly to replace proj1 - call the project service , find project by id 1 and replace proj1 by the project name.
- and so on..
So to achieve this in a clean manner, I thought of writing commands - each command for fetching an object by calling the appropriate service(customerService, projectService etc). And then at the client level just parse the sourceString and build up a list of commands to be executed and then finally build up a destination key using the commands list.
Am I thinking in the right direction? Is command pattern the clean/OO way of doing this?
No, the Command Pattern is not suited to this problem. From an OOP perspective, I would start by modeling the source key as an Object rather than a String, to avoid stringly-typed programming.
It looks like a SourceKey object would have dependencies on CustomerService and ProjectService, and would contain five fields, which it can combine into a String as necessary. In other words, try encapsulating the String transformation logic into its own Object.
I want to retrieve all data properties set for an individual of any class using owl api. The code i have used is
OWLNamedIndividual inputNoun = df.getOWLNamedIndividual(IRI.create(prefix + "Cow"));
for (OWLDataProperty prop: inputNoun.getDataPropertiesInSignature())
{
System.out.println("the properties for Cow are " + prop); //line 1
}
This code compiles with success but line 1 print nothing at all. What should be the correct syntax. Have thoroughly googled and couldnt find any thing worth it.
OWLNamedIndividual::getDataPropertiesInSignature() does not return the properties for which the individual has a filler, it returns the properties that appear in the object itself. For an individual this is usually empty. The method is on the OWLObject interface, which covers things like class and property expressions and ontologies, for which it has a more useful output.
If you want the data properties with an actual filler for an individual, use OWLOntology::getDataPropertyAssertionAxioms(OWLIndividual), like this:
OWLNamedIndividual input = ...
Set<OWLDataPropertyAssertionAxiom> properties=ontology.getDataPropertyAssertionAxioms(input);
for (OWLDataPropertyAssertionAxiom ax: properties) {
System.out.println(ax.getProperty());
}
I have a config file:
company=My Company
num_users=3
user={
name="John"
age=24
}
user={
name="Anna"
age=27
}
user={
name="Jack"
age=22
}
I'm trying to parse this config file using Java. I tried java.util.Properties but I don't know how to get each single user data.
I still can get company property value using getProperty method.
Please help me with this.
Thanks!
Nothing wrong with having same property name with different value but getProperty(String key) can not differentiate between them as it will return the first value.
Secondly you can not access nested property directly.Right now here getProperty will return whole String including {} as value because that's what your value contains.You may get value and perform some operations to fetch values from that whole String.Because property file should have only key=value format means left hand side should be key and right hand side should be value.That's it.
If you want to store values as you have specified in your code you should go for JSON format than you can store whole JSON data to file and get it back from the file while you want to use it.
If you use java.util.Properties class to load the config file, you will get the following result:
{company=My Company, age=22, user={, name="Jack", }=, num_users=3}
The reason, you could refer to the javaDoc for "public void load(Reader reader)" method of Properties class.
Since you don't describ the detailed syntax format for your config file,
base on your example input, the following sample code could retrive the name=value correctly:
String reg="(\\w+)\\s*=\\s*((?>\\{[^\\{\\}]*\\})|(?>.*$))";
Pattern pMod = Pattern.compile(reg, Pattern.MULTILINE);
Matcher mMod = pMod.matcher(line);
while (mMod.find()) {
System.out.println(mMod.group(1));
System.out.println(mMod.group(2));
}
The output is:
company
My Company
num_users
3
user
{
name="John"
age=24
}
user
{
name="Anna"
age=27
}
user
{
name="Jack"
age=22
}