Grouping by a Map value in Java 8 using streams - java

I have a List of Map that i want to group it by the key nom using java streams.
[
{
"dateDebut": "2018-07-01T00:00:00.000+0000",
"nom": "Julien Mannone",
"etat": "Impayé"
},
{
"dateDebut": "2018-08-01T00:00:00.000+0000",
"nom": "Julien Mannone",
"etat": "Impayé"
},
{
"dateDebut": "2018-10-01T00:00:00.000+0000",
"nom": "Mathiew Matic",
"etat": "payé"
},
{
"dateDebut": "2018-10-01T00:00:00.000+0000",
"nom": "Ash Moon",
"etat": "payé"
}
]
so i want as a result something like this
{
"Julien Mannone":[
{
"dateDebut":"2018-07-01T00:00:00.000+0000",
"etat":"Impayé"
},
{
"dateDebut":"2018-08-01T00:00:00.000+0000",
"etat":"Impayé"
}
],
"Mathiew Matic":[
{
"dateDebut":"2018-10-01T00:00:00.000+0000",
"etat":"payé"
}
],
"Ash Moon":[
{
"dateDebut":"2018-10-01T00:00:00.000+0000",
"etat":"payé"
}
]
}
As a beginner in using streams I have made some research I found some codes like that
Map<String, List<Map>> afterFormatting =
beforeFormatting.stream()
.flatMap(m -> m.entrySet().stream())
.collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toList())));
but that doesn't do the job for me

Seems like you're simply looking for:
Map<String, List<Map<String, String>>> afterFormatting =
beforeFormatting.stream()
.collect(Collectors.groupingBy(map -> map.get("nom")));
or if you don't want each Map<String, String> in the result set to contain the "nom" entry then you can do as follows:
Map<String, List<Map<String, String>>> afterFormatting =
beforeFormatting.stream()
.collect(Collectors.groupingBy(map -> map.get("nom"),
Collectors.mapping(map -> {
Map<String, String> temp = new HashMap<>(map);
temp.remove("nom");
return temp;
}, Collectors.toList())));

If I understand you correct you have a maps like
{
"dateDebut": "2018-07-01T00:00:00.000+0000",
"nom": "Julien Mannone",
"etat": "Impayé"
},
but then, when you call
.flatMap(m -> m.entrySet().stream())
You get a stream of all entry sets for all maps.
But actually, your maps are not maps itself. They are rather POJO objects.
I suggest creating a class like
class Value {
String dateDebut, String nom, Etring etat;
// constructor
}
then convert each map to this class:
beforeFormatting.stream()
.map(m-> new Value(m.get("dateDebut"), m.get("nom"),m.get("etat"))
So now, you have a stream<Value> and you can easily group by in by "nom"
.collect(groupingBy(Value::getNom)

You just need to map stream to change the format to needed, and then collect:
list.stream().map(it -> {
Map<String, Map<String, String>> newMap = new HashMap<>();
String nom = it.get("nom");
it.remove("nom");
newMap.put(nom, it);
return newMap;
}
).collect(Collectors.toList())
Testable code:
Map<String, String> m = new HashMap<>();
m.put("dateDebut", "2018-07-01T00:00:00.000+0000");
m.put("nom", "Julien Mannone");
m.put("etat", "Impayé");
Map<String, String> m2 = new HashMap<>();
m2.put("dateDebut", "2018-10-01T00:00:00.000+0000");
m2.put("nom", "Mathiew Matic");
m2.put("etat", "payé");
Map<String, String> m3 = new HashMap<>();
m3.put("dateDebut", "2018-07-01T00:00:00.000+0000");
m3.put("nom", "Ash Moon");
m3.put("etat", "payé");
List<Map<String, String>> list = new ArrayList<>();
list.add(m);
list.add(m2);
list.add(m3);
List<Map<String, Map<String, String>>> res = list.stream().map(it -> {
Map<String, Map<String, String>> newMap = new HashMap<>();
String nom = it.get("nom");
it.remove("nom");
newMap.put(nom, it);
return newMap;
}
).collect(Collectors.toList());
System.out.println(res);

Related

Using streams to extract specific entries of a List of Maps in to a new Map

Given a org.bson.Document
{
"doneDate":"",
"todoEstimates":"",
"forecastDate":"",
"cardType":{
"projectData":[
{
"color":"#ffcd03",
"boardId":"30022"
},
{
"color":"#ffcd03",
"boardId":"1559427"
}
],
"cardFields":[
{
"fieldName":"id",
"fieldLabel":"Unique ID",
"fieldType":"Integer",
"itemType":"Long",
"isRequired":"NO",
"isReadOnly":"Yes",
"isDisabled":"NO",
"inputMethod":"System Generated",
"defaultValue":null,
"isUserType":"No"
},
{
"fieldName":"name",
"fieldLabel":"Title",
"fieldType":"Single-Line Text",
"itemType":"String",
"isRequired":"Yes",
"isReadOnly":"NO",
"isDisabled":"NO",
"inputMethod":"Manual Entry",
"defaultValue":null,
"isUserType":"No"
}
]
}
How do I extract the values of fieldName and fieldLabel via streams into the following?
{
"id": "Unique ID",
"name:" "Title",
...
}
I tried the following but I get stuck at the part where I get value of the cardFields list.
document.entrySet().stream().filter(e -> e.getKey().equals("cardType"))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
.entrySet().stream().filter(e -> e.getKey().equals("cardFields"))
.map(e -> (Map)e.getValue()).toList();
Here is a working solution with streams:
Map<String, Object> fields = ((List<Map<String, Object>>) ((Map<String, Object>) document.entrySet()
.stream()
.filter(entry -> entry.getKey().equals("cardType"))
.findFirst()
.orElseThrow(() -> new RuntimeException("card type not found"))
.getValue())
.entrySet()
.stream()
.filter(entry -> entry.getKey().equals("cardFields"))
.findFirst()
.orElseThrow(() -> new RuntimeException("card fields not found"))
.getValue())
.stream()
.collect(Collectors.toMap(el -> el.get("fieldName").toString(), element -> element.get("fieldLabel")));
Document result = new Document(fields);
System.out.println(result.toJson());
That's probably the worst code i have written - absolutely unreadable and you can't debug it. I would suggest that you do not use stream for this particular task, it isn't the right tool for it. So here is another working solution using Map.get(key):
Map<String, Object> cardType = (Map<String, Object>) document.get("cardType");
List<Map<String, Object>> cardFields = (List<Map<String, Object>>) cardType.get("cardFields");
Document result = new Document();
cardFields.forEach(cardField -> result.put((String) cardField.get("fieldName"), cardField.get("fieldLabel")));
System.out.println(result.toJson());
This is shorter, readable, you can debug it if needed and probably it's more performant. I'd say it's much better overall.
You may be able to parse your document like this:
Document cardType = document.get("cardType", Document.class);
final Class<? extends List> listOfMaps = new ArrayList<Map<String, String>>().getClass();
List<Map<String, String>> fields = cardType.get("cardFields", listOfMaps);
fields.stream().map(f -> {
System.out.println(f.get("fieldName") + ": " + f.get("fieldLabel"));
// here you can construct your new object
}).collect(Collectors.toList());
If you don't mind casting a lot, you could try following:
List cardFields = (List) ((Map) document.get("cardType")).get("cardFields");
Map<String, String> map = (Map) cardFields.stream()
.collect(Collectors.toMap(cf -> ((Document) cf).getString("fieldName"),
cv -> ((Document) cv).getString("fieldLabel")));
System.out.println(map);
or you can emit omit the casting with the following:
List<Document> carFields = document.get("cardType", Document.class)
.getList("cardFields", Document.class);
Map<String, String> map = carFields.stream()
.collect(Collectors.toMap(k -> k.getString("fieldName"),
v -> v.getString("fieldLabel")));
System.out.println(map);
Here is the complete example running with java 17:
import org.bson.Document;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Bson {
private String json=
"""
{
"doneDate": "",
"todoEstimates": "",
"forecastDate": "",
"cardType": {
"projectData": [
{
"color": "#ffcd03",
"boardId": "30022"
},
{
"color": "#ffcd03",
"boardId": "1559427"
}
],
"cardFields": [
{
"fieldName": "id",
"fieldLabel": "Unique ID",
"fieldType": "Integer",
"itemType": "Long",
"isRequired": "NO",
"isReadOnly": "Yes",
"isDisabled": "NO",
"inputMethod": "System Generated",
"defaultValue": null,
"isUserType": "No"
},
{
"fieldName": "name",
"fieldLabel": "Title",
"fieldType": "Single-Line Text",
"itemType": "String",
"isRequired": "Yes",
"isReadOnly": "NO",
"isDisabled": "NO",
"inputMethod": "Manual Entry",
"defaultValue": null,
"isUserType": "No"
}
]
}
}
""";
public static void main(String[] args) {
Bson bson = new Bson();
Document document = Document.parse(bson.json);
List cardType = (List) ((Map) document.get("cardType")).get("cardFields");
Map<String, String> map = (Map) cardType.stream()
.collect(Collectors.toMap(cf -> ((Document) cf).getString("fieldName"),
cv -> ((Document) cv).getString("fieldLabel")));
System.out.println(map);
List<Document> carFields = document.get("cardType", Document.class).getList("cardFields", Document.class);
Map<String, String> map1 = carFields.stream()
.collect(Collectors.toMap(k -> k.getString("fieldName"), v -> v.getString("fieldLabel")));
System.out.println(map1);
}
}

How to convert a list of java objects to Map<String, Map<String, String>>

I have a list of java objects as below:
[
{
id: "frwfhfijvfhviufhbviufg",
country_code: "DE",
message_key: "key1",
translation: "This is the deutsch translation"
},
{
id: "dfregregtegetgetgttegt",
country_code: "GB",
message_key: "key1",
translation: "This is the uk translation"
},
{
id: "frffgfbgbgbgbgbgbgbgbg",
country_code: "DE",
message_key: "key2",
translation: "This is the again deutch translation"
}
]
How can I convert this into a Map<String, Map<String, String>> like below:
{
"DE": {
"key1": "This is the deutsch translation",
"key2": "This is the again deutch translation"
},
"GB": {
"key1": "This is the uk translation"
}
}
I am new to java and below is my code but the code is not correct:
Map<String, Translations> distinctTranslations = customTranslationsEntities
.stream().collect(Collectors.groupingBy(
CustomTranslationsEntity::getCountryCode,
Collectors.toMap(
CustomTranslationsEntity::getMessageKey,
CustomTranslationsEntity::getTranslation),
)))
where Translations is proto buffer message like below:
message Translations {
map<string, string> translations = 1;
}
Here map<string, string> translations means map like "key1", "This is the deutsch translation"...like this.
The output should be Map<String, Map<String,String>>:
Map<String, Map<String,String>>
distinctTranslations = customTranslationsEntities
.stream()
.collect(Collectors.groupingBy(CustomTranslationsEntity::getCountryCode,
Collectors.toMap(
CustomTranslationsEntity::getMessageKey,
CustomTranslationsEntity::getTranslation,
(v1,v2)->v1)));
I added a merge function, in case there are duplicate keys.
If you want to do it without using streams then
private List<MyObject> list = // Your object List
private Map<String, Map<String, String>> map = new HashMap<>();
for(MyObject object : list){
Map<String, String> localMap;
localMap = map.getOrDefault(object.country_code, new HashMap<>());
localMap.put(object.message_key, object.translation);
if(!map.containsKey(object.country_code)){
map.put(object.country_code, localMap);
}
}

Parsing a dynamically defined nested map

I have a JSON object that is dynamically defined.
{
"lvars": {
"task1": {
"assigned" : true,
"params": {//any key value maps here}, {//any key value maps}
},
"task2": {
"assigned" : false,
"params": {//any key value maps here}, {//any key value maps}
....
},
"mvars": {
"Id": {
"type": "String",
"value": ""
},
}
}
Now in java when I am deserializing it into code it becomes somewhat like this,
Map<String, Map<String, Map<String, Map<String, Map<String, Object>>>>> m = ObjectMapper
.fromJson(getValues(), new TypeReference<>() {});
In this case "lvars" and "mvars" are fixed keys and evrything else is variable. How can I avoid the nested map of maps declaration in Java.
Just do:
Map<String,Object> data = ObjectMapper.fromJson(getValues(), new TypeReference<>() {});
Map<String, Map<String, Map<String,Object>>> dynamic_map_0 = (Map<String, Map<String, Map<String, Object>>>) data.get("lvars");
Map<String, Map<String, Map<String,Object>>> dynamic_map_1 = (Map<String, Map<String, Map<String, Object>>>) data.get("mvars");
or simpler:
JSONObject obj = new JSONObject(inputAsString);
JSONObject dynamic0 = obj.getJSONObject("lvars");
JSONObject dynamic1 = obj.getJSONObject("mvars");
You can create a class called NestedMap which is a data structure that contains references to other NestedMap objects, like so:
class NestedMap<K, Object> {
Map<K, NestedMap<K>> data;
Object object; // object to resort to if reached the end of "map-chain"
...
}
This makes the nesting a bit less messy.

Is there a direct way to convert Map<String, List<String>> into a JSON String (using javax.json library)

Lets assume I have the following Map
Map<String, List<String>> map = new HashMap<>();
List<String> list = new ArrayList<>();
list.add("First in list");
list.add("Second in list");
map.put("First in map", list);
So how can I convert it in a direct way to a JSON String using javax.json library?
I just tried the following
Map<String, List<String>> map = new HashMap<>();
List<String> list = new ArrayList<>();
list.add("First in list");
list.add("Second in list");
List<String> list1 = new ArrayList<>();
list1.add("First in secondlist");
list1.add("Second in secondlist");
map.put("First in map", list);
map.put("Second in map", list1);
final JsonArrayBuilder outerJsonArray = Json.createArrayBuilder();
map.forEach((key, innerList) -> {
JsonArrayBuilder innerJsonArray = Json.createArrayBuilder();
innerList.forEach(item -> innerJsonArray.add(item));
outerJsonArray.add(Json.createObjectBuilder().add(key, innerJsonArray));
});
JsonArray usersJson = outerJsonArray.build();
System.out.println(usersJson.toString());
And the resulting JSON array is (missing the insertion order)
[
{
"Second in map": [
"First in secondlist",
"Second in secondlist"
]
},
{
"First in map": [
"First in list",
"Second in list"
]
}
]

iterate a List<HashMap<String, String>> values using java

I have a json response like this
{
"queryPath": "/api/",
"nId": "f084f5ad24fcfaa9e9faea0",
"statusCode": 707
"statusMessage": "Success",
"results": {
"data": [
{
"id": "10248522500798",
"capabilities": [
"men",
"women"
],
"name": "errt2"
},
{
"id": "418143778",
"capabilities": [
"dog",
"cat"
],
"name": "Livin"
}
]
}
}
Here am adding results.data to a list as follows
private List<HashMap<String, String>> episodes = new ArrayList<HashMap<String, String>>();
episodes =helper.getJSONValue(response, "results.data");
public <T>T getJSONValue(Response res, String path ){
String json = res.asString();
JsonPath jpath = new JsonPath(json);
return jpath.get(path);
}
so episodes contains all data i mean all results.data
While i debuging am getting this way
[{id=10248522500798, name=errt2, capabilities=[men, women]}, {id=418143778, name=Livin, capabilities=[dog, cat]}]
Here i have capabilities [men, women] and [dog, cat].i need to check capability contains men or dog.
How can i do that?
If i were you i haven't done this..
Use gson and map your json into a java model. It's way better. Afterwards you can access all your model parts with getters and setters.
MyType target2 = gson.fromJson(json, MyType.class); // deserializes json into target2
As you see it's very simple :)
But if you want to iterate a list that contains a map you can use code block below:
List<Map<String, String>> test = new ArrayList<Map<String, String>>();
for( Map<String, String> map : test ){
for( Entry<String, String> entry : map.entrySet() ){
System.out.println( entry.getKey() + " : " + entry.getValue() );
}
}
With the code above you can get all the entry's keys and values and check them.
Edit:
You have to change your List to List<Map<String,Object>> after that:
List<Map<String, Object>> test = new ArrayList<Map<String, Object>>();
for( Map<String, Object> map : test ){
for( Entry<String, Object> entry : map.entrySet() ){
if( entry.getKey().equalsIgnoreCase( "capabilities" ) ){
List<String> myCapabilities = ( List )entry.getValue();
if( myCapabilities.contains( "dog" ) && myCapabilities.contains( "cat" ) ){
// BLA BLA
}
}
}
}
It's a nasty way.. I recommend you to use gson..

Categories