How to parse YAML file - java

I am using Jackson's YAML parser and I want to parse a YAML file without having to manually create a Java class that matches the yaml file. All the examples I can find map it to an object such as here: https://www.baeldung.com/jackson-yaml
The yaml file that is given to me will not always be the same so I need to parse it during runtime, is it possible to achieve this with jackson-yaml?

If you don't know the exact format, you're going to have to parse the data to a tree and process it manually, which can be tedious. I'd use Optional for mapping and filtering.
Example:
public static final String YAML = "invoice: 34843\n"
+ "date : 2001-01-23\n"
+ "product:\n"
+ " - sku : BL394D\n"
+ " quantity : 4\n"
+ " description : Basketball\n"
+ " price : 450.00\n"
+ " - sku : BL4438H\n"
+ " quantity : 1\n"
+ " description : Super Hoop\n"
+ " price : 2392.00\n"
+ "tax : 251.42\n"
+ "total: 4443.52\n";
public static void main(String[] args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
JsonNode jsonNode = objectMapper.readTree(YAML);
Optional.of(jsonNode)
.map(j -> j.get("product"))
.filter(ArrayNode.class::isInstance)
.map(ArrayNode.class::cast)
.ifPresent(projectArray -> projectArray.forEach(System.out::println));
}
Output:
{"sku":"BL394D","quantity":4,"description":"Basketball","price":450.0}
{"sku":"BL4438H","quantity":1,"description":"Super Hoop","price":2392.0}

Like when you are parsing JSON, you can parse into a Map:
Example
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
#SuppressWarnings("unchecked")
Map<String, Object> map = mapper.readValue(new File("test.yaml"), Map.class);
System.out.println(map);
test.yaml
orderNo: A001
date: 2019-04-17
customerName: Customer, Joe
orderLines:
- item: No. 9 Sprockets
quantity: 12
unitPrice: 1.23
- item: Widget (10mm)
quantity: 4
unitPrice: 3.45
Output
{orderNo=A001, date=2019-04-17, customerName=Customer, Joe, orderLines=[{item=No. 9 Sprockets, quantity=12, unitPrice=1.23}, {item=Widget (10mm), quantity=4, unitPrice=3.45}]}

Related

how to take json data in java map or array

I have the following data as json and I want to take it in java
"endPoints": {
"northAmerica": "https://ad-api.com",
"europe": "https://ad-api-eu.com",
"farEast": "https://ad-api-fe.com"
}
I have tried the below code but not working.
Map<String, Object> endPoints = objectMapper.readValue(JsonParser
.parseString(additionalInfo().get("endPoints").toString())
.getAsJsonObject(), new TypeReference<Map<String, Object>>() {
});
anyone can help me how to do it?
First you need to make your data is a valid json format,then you can use ObjectMapper to do it
public static void testJsonConvert() throws JsonProcessingException {
String data = "{\n" +
" \"endPoints\":{\n" +
" \"northAmerica\":\"https://ad-api.com\",\n" +
" \"europe\":\"https://ad-api-eu.com\",\n" +
" \"farEast\":\"https://ad-api-fe.com\"\n" +
" }\n" +
"}";
Map<String, Object> map = new ObjectMapper().readValue(data, HashMap.class);
System.out.println(map);
}
Test result:
Any JSON string can be mapped to HashMap data structure as Key and Value pair.
There is an answer already in the thread.
But If you want a map of endpoints, like North America & Europe, you need to go a level deeper.
ObjectMapper from Jackson Core library will help.
First, get a HashMap of the data, then again get the endpoint from the HashMap.
String data = "{\n" +
" \"endPoints\":{\n" +
" \"northAmerica\":\"https://ad-api.com\",\n" +
" \"europe\":\"https://ad-api-eu.com\",\n" +
" \"farEast\":\"https://ad-api-fe.com\"\n" +
" }\n" +
"}";
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = mapper.readValue(data, HashMap.class);
Map<String, Object> endPointMap = (Map<String, Object>) map.get("endPoints");
System.out.println(endPointMap);
result:
{northAmerica=https://ad-api.com, europe=https://ad-api-eu.com, farEast=https://ad-api-fe.com}

write map<Object, List<Object> to text file java

I have an HashMap<Person,List<Pet> that needs to be saved into the textfile.
I wrote this method:
public void writeToNotepad(){
try(PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(textFileName + ".txt")))) {
if (map.size() < 1)
return;
Set<Map.Entry<Person, List<Pet>>> mapEntry = map.entrySet();
for (Map.Entry<Person, List<Pet>> mapEn :
mapEntry) {
Person p = mapEn.getKey();
Iterator<Pet> iter = mapEn.getValue().iterator();
while (iter.hasNext()){
Pet pet = iter.next();
pw.println(p.getName() + " " + p .getAge() + " " + p.getSex()
+ " " + pet.getName()
+ " " + pet.getType()
+ " " + pet.getAge());
}
}
pw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
but it saves map key multiple times(i think it's because of iterator)
here is output:
JOHN 10 MALE Dog 3
AISEC 12 MALE Loo Cat 12
AISEC 12 MALE Kitty Cat 4
As you see, Aisec repeats 2 times.
Sooner i need to read from this text file to fill map.
Is this good way to write to file, or i can use better?
...and what if the name contains two words with a blank in between??
Why do you need to invent your own format for serialization and de-serialization? why not use an industry standard like json, where you can utilize any of many libraries that can do the serialization and de-serialization. Do you need me to show you how?
EDIT:
OK, it turned out that using json is not as straightforward as I initially thought. Don't get me wrong, it is still better than custom format in the sense that it is bug-proof and supports edge cases like the one I described above.
The obstacle with json is that the key to every object and property has to be a String. So when the key is a user defined type (like Person in your case) it didn’t get serialized properly - there is a need for a transitional data structure before the serialization to json can be performed.
So this is what I did: for each entry in your dictionary, I create a map that holds two entries: a "person" entry with the json String representation of the Person object, and a "pets" entry with the json String representation of the list of pets. So the final String to be serialized is actually a List of Maps.
To give you an idea: every map entry looks like this as json:
{
"person":{"name":"AISEC","age":12,"sex":"MALE"},
"pets":[
{"name":"Loo","age":12,"type":"Cat"},
{"name":"Kitty","age":4,"type":"Cat"}
]
}
The deserialization is simply the reverse operation.
I am using Jackson library as json parser and writer
This is the serialization method. it returns a String that can be written to a file:
public String dictToJson(Map<Person, List<Pet>> map) throws IOException
{
ObjectMapper mapper = new ObjectMapper();
// serialized dictionary is list of maps
List<Map<String, String>> jsonDictionary = new ArrayList<>();
for (Map.Entry<Person, List<Pet>> mapEntry : map.entrySet()) {
// each map entry becomes its own map instance with two entries
Map<String, String> jsonEntry = new HashMap<>();
// write person key as "person" with json string represetation of person object
jsonEntry.put("person", mapper.writeValueAsString(mapEntry.getKey()));
// write pets value as "pets" key with json string represetation of pets list
jsonEntry.put("pets", mapper.writeValueAsString(mapEntry.getValue()));
jsonDictionary.add(jsonEntry);
}
return mapper.writeValueAsString(jsonDictionary);
}
The de-serialization method accpets String (whole content of file):
public Map<Person, List<Pet>> jsonToDict(String json) throws IOException
{
Map<Person, List<Pet>> map = new HashMap<>();
ObjectMapper mapper = new ObjectMapper();
// read json String into list of maps
List<Map<String, String>> jsonDictionary =
mapper.readValue(json, new TypeReference<List<Map<String, Object>>>(){});
// each list item is a map with two entries
for (Map<String, String> jsonEntry : jsonDictionary) {
map.put(
mapper.readValue(jsonEntry.get("person"), Person.class),
mapper.readValue(jsonEntry.get("pets"), new TypeReference<List<Pet>>(){}));
}
return map;
}
usage and test method:
public static void main(String[] args)
{
Map<Person, List<Pet>> map ; {
map = new HashMap<>();
map.put(new Person("JOHN", 10, "MALE"),
Arrays.asList(new Pet[]{new Pet("Spot", 3, "Dog")}));
map.put(new Person("AISEC", 12, "MALE"),
Arrays.asList(new Pet[]{new Pet("Loo", 12, "Cat"), new Pet("Kitty", 4, "Cat")}));
}
try {
// serialize map into String
String serializedDict = dictToJson(map);
// write to file ...
System.out.println(serializedDict);
map.clear();
// de-serialize from String to map
map = jsonToDict(serializedDict);
// check if map was built ok
System.out.println(dictToJson(map));
} catch (Exception e) {
e.printStackTrace();
}
}
I overlooked the read+write part.
First some words about that thing: what you are actually looking for is a human readable way of serializing your data. There are various ways to get there: A) you define your own text format B) you use existing technologies, like JSON or XML.
My recommendation: learn how to use libraries that transform your data in JSON strings; then write those; and read them back using a JSON parser.
Further reading on that: writing and parsing
The key part is: no matter what you, you really have to think through what you are doing here. You have to define a reasonable format for your data; and then you have to write the code to create/parse that content.
But then, on the second part of your question; the current output that comes up:
Here; in your inner loop:
pw.println(p.getName() + " " + p .getAge() + " " + p.getSex()
+ " " + pet.getName()
+ " " + pet.getType()
+ " " + pet.getAge());
You are making a println call that prints all that information. Within your inner loop that loops on the entries of the inner list.
And you are really surprised that an inner loop that runs within an outer loop leads to this result?!
So, the answer is: you have to step back. You put code down that will print
Owner NAME AGE pet1 details
Owner NAME AGE pet2 details
and so on. You have to rework that code; and first you have to clarify the desired output format. For example, that could be:
Owner NAME AGE pet1 details | pet 2 details | and so forth
You could get there by creating a helper method like
public String toString(List<Pet> pets) {
Now you simply iterate your pet owners; and for each owner you print down the owner details; and then you use that method for the pets string.
Beyond that: you could look into overriding
public String toString()
on your Person and your Pet class. In other words: dont ask your objects for their details to build a string from that. Instead: tell the object to provide a string representation of itself.
And then doing a full printout would boil down to:
foreach mapEn
System.out.println("Owner: " + mapEn.getKey().toString() + " pets: " + mapEn.getValue().toString());

json-simple versus jackson for parsing json when do not have obj type

To convert json String to pojo using jackson API can use :
String jsonInString = "{\"age\":33,\"messages\":[\"msg 1\",\"msg 2\"],\"name\":\"mkyong\"}";
User user1 = mapper.readValue(jsonInString, User.class);
This requires that create class User that matches structure of json String.
Using json-simple API can use instead :
JSONObject json = (JSONObject)new JSONParser().parse(jsonInString);
Using json-simple do not need to include a pojo that matches json format. Can similar be used in jackson ? json-simple is less verbose as do not have to create the class that matches json structure.
Jackson can deserialize a json String into a general-purpose Map:
Map<String, Object> m = new ObjectMapper().readValue(jsonInString, Map.class);
for (Map.Entry<String, Object> entry : m.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue() + "(" + entry.getValue().getClass().getName() + ")");
}
output:
age -> 33(java.lang.Integer)
messages -> [msg 1, msg 2](java.util.ArrayList)
name -> mkyong(java.lang.String)
You can use similar API
JsonNode node = mapper.readTree(jsonInString);

Using Jackson to manually parse JSON

Is it possible to use Jackson library to manually parse JSON?
I.e. I don't want to use ObjectMapper and convert JSON to some object, but rather I want select some individual properties from JSON, like in XPath:
For example this is my JSON:
{
"person": {
"name": "Eric",
"surname": "Ericsson",
"address" {
"city": "LA",
"street": "..."
}
}
}
And all what I want is just to get Name and the City, for this cases I don't want introduce 2 new Java classes (Person and Address) and use them with ObjectMapper, but I'm just want to read this values like in xPath:
Pseudocode:
String name = myJson.get("person").get("name")
String city = myJson.get("person").get("address").get("city")
You can use the Jackson tree model and JsonNode#at(...) method which takes the Json Pointer expression as a parameter.
Here is an example:
public class JacksonJsonPointer {
static final String JSON = "{"
+ " \"person\": {"
+ " \"name\": \"Eric\","
+ " \"surname\": \"Ericsson\","
+ " \"address\": {"
+ " \"city\": \"LA\","
+ " \"street\": \"...\""
+ " }"
+ " }"
+ "}";
public static void main(String[] args) throws IOException {
final ObjectMapper mapper = new ObjectMapper();
final JsonNode json = mapper.readTree(JSON);
System.out.println(json.at("/person/name"));
System.out.println(json.at("/person/address/city"));
}
}
Output:
"Eric"
"LA"
Yes Using Json parser you can parse your Json, Below is a sample example you can find more in jackson documentation
JsonParser jsonParser = new JsonFactory().createJsonParser(jsonStr);
while(jsonParser.nextToken() != JsonToken.END_OBJECT){
String name = jsonParser.getCurrentName();
if("name".equals(name)) {
jsonParser.nextToken();
System.out.println(jsonParser.getText());
}
if("surname".equals(name)) {
jsonParser.nextToken();
System.out.println(jsonParser.getText());
}
if("city".equals(name)) {
jsonParser.nextToken();
System.out.println(jsonParser.getText());
}
}

Convert JSON to hash map with Jackson- Java

Before this is marked as a duplicate please read the question (I did look at similar ones). Thank you.
For simplicity, assume I have JSON like this:
{
"clients" : [
{
"name" : "client 1",
"id" : 1
},
{
"name" : "client 2",
"id" : 2
}
],
"other" : {
"something" : ""
}
...
}
So I want to create a hash map of only the clients and their fields. The basic question is how would I go about doing this using Jackson methods for a single JSON array like clients? I've tried to look online but all of the examples that I have seen either don't use Jackson or only are for a single JSON object like so:
HashMap<String, String>[] values = new ObjectMapper().readValue(jsonString, new TypeReference<HashMap<String, String>[]>() {});
I've also seen Gson examples and I know I can do some string parsing magic:
jsonSting = jsonString.substring(jsonString.indexOf("["), (jsonString.indexOf("]")+1))
to get it in a format that I can use, but I want to try it with Jackson to avoid importing another library. Any ideas?
Rephrasing the question:
So if I only had a list of clients like so:
jsonString = [{"name" : "client 1","id" : 1},{"name" : "client 2","id" : 2}]
then I could just do:
HashMap[] values = new ObjectMapper().readValue(jsonString, new TypeReference[]>() {});
to get what I want. I am basically asking if there is a way using Jackson methods to get the jsonString above from the large JSON section on top. I know I can easily do it with this example with string parsing but there will be more complex situations in the future and string parsing is not really considered best practice
You can extract a part of the JSON tree using the Jackson tree model API and then convert it to an array of maps.
Here is an example:
public class JacksonReadPart {
public static final String JSON = "{\n" +
" \"clients\" : [\n" +
" {\n" +
" \"name\" : \"client 1\",\n" +
" \"id\" : 1\n" +
" },\n" +
" {\n" +
" \"name\" : \"client 2\",\n" +
" \"id\" : 2\n" +
" }\n" +
"],\n" +
" \"other\" : {\n" +
" \"something\" : \"\"\n" +
" }\n" +
"\n" +
"}";
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(JSON).path("clients");
// non type safe
Map<String, Object>[] clients = mapper.treeToValue(node, Map[].class);
System.out.println(Arrays.toString(clients));
// type safe
JsonParser parser = mapper.treeAsTokens(node);
clients = parser.readValueAs(new TypeReference<Map<String, Object>[]>() {});
System.out.println(Arrays.toString(clients));
}
}
Output:
[{name=client 1, id=1}, {name=client 2, id=2}]
[{name=client 1, id=1}, {name=client 2, id=2}]

Categories

Resources