How to convert list of map to array of hash - java

I am using Java for my application. Here is the my input data.
[{
"id": 1,
"firstname": "one",
"lastname": "1"
},
{
"id": 2,
"firstname": "two",
"lastname": "2"
},
{
"id": 3,
"firstname": "three",
"lastname": "3"
}
]
I want to convert the above input to like below output. How can I achieve the below output in an efficient manner?
{
["id", "firstname", "lastname"], [1, "one", "1"], [2, "two", "2"], [3, "three", "3"]
}
Update:
I have tried the below. But it resulted as below
Expected:
result => {[lastname, id, firstname]=[[1, 1, one], [2, 2, two], [3, 3, three]]}
Actual:
result => {[lastname, id, firstname], [1, 1, one], [2, 2, two], [3, 3, three]}
Code:
Map<String, Object> one = Map.of("id", 1, "firstname", "one", "lastname", "1");
Map<String, Object> two = Map.of("id", 2, "firstname", "two", "lastname", "2");
Map<String, Object> three = Map.of("id", 3, "firstname", "three", "lastname", "3");
ArrayList<Map<String,Object>> list = new ArrayList<>();
list.add(one);
list.add(two);
list.add(three);
MultiValueMap<Object, Object> result = new LinkedMultiValueMap<>();
Set<String> strings = list.get(0).keySet();
ArrayList<Object> objects = new ArrayList<>();
for(Map<String,Object> map: list) {
objects.add(map.values());
}
result.put(strings, objects);

The input JSON may be read into a list of maps data sharing the same key sets and this list is to be converted into a list of object arrays, with the first element of this list being the keys of a map.
So, at first the array of field names should be created, converted to Stream, and then merged with Stream<Object[]> retrieved from the values of each map in data list:
// using Jackson JSON to read the input
ObjectMapper mapper = new ObjectMapper();
String input = "[{\"id\":1, \"firstname\":\"First\", \"lastname\": \"Last\"}]";
List<Map<String, Object>> data = mapper.readValue(input, new TypeReference<>() {});
List<Object[]> output = Stream.concat(
Stream.<Object[]>of(data.get(0).keySet().toArray()),
data.stream().map(m -> m.values().toArray())
)
.collect(Collectors.toList());
System.out.printf("Result: {%n\t%s%n}%n",
output.stream().map(Arrays::toString).collect(Collectors.joining(", ")));
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(output));
Output:
Result: {
[id, firstname, lastname], [1, First, Last]
}
[ [ "id", "firstname", "lastname" ], [ 1, "First", "Last" ] ]
Or similarly the result could be a list of raw collections based on maps' keySet() and values, which could be created like this:
List<Collection> result = Stream.concat(
Stream.of(data.get(0).keySet()),
data.stream().map(Map::values)
).collect(Collectors.toList());

Related

Group by multiple fields in stream java 8

I have a class:
public class PublicationDTO {
final String publicationName;
final String publicationID;
final String locale;
final Integer views;
final Integer shares;
}
A need to get sum of views and shares. For test i created a list:
PublicationDTO publicationDTO1 = new PublicationDTO("Name1", "name1", "CA", 5, 6);
PublicationDTO publicationDTO2 = new PublicationDTO("Name2", "name2", "US", 6, 3);
PublicationDTO publicationDTO3 = new PublicationDTO("Name1", "name1", "CA", 10, 1);
PublicationDTO publicationDTO4 = new PublicationDTO("Name2", "name2", "CA", 2, 3);
List<PublicationDTO> publicationDTOS = List.of(publicationDTO1, publicationDTO2, publicationDTO3, publicationDTO4);
I want to group objects in list by publicationName, publicationId and locale and get result list like:
List.of(new PublicationDTO("Name1", "name1", "CA", 15, 7),
new PublicationDTO("Name2", "name2", "CA", 2, 3),
new PublicationDTO("Name2", "name2", "US", 6, 3));
I found a solution like:
List<PublicationDTO> collect = publicationDTOS.stream()
.collect(groupingBy(PublicationDTO::getPublicationID))
.values().stream()
.map(dtos -> dtos.stream()
.reduce((f1, f2) -> new PublicationDTO(f1.publicationName, f1.publicationID, f1.locale, f1.views + f2.views, f1.shares + f2.shares)))
.map(Optional::get)
.collect(toList());
but the result not grouped by locale and I'm not sure if it works by publicationId. Please let me know how to properly use collectors in such case?
You are only grouping by getPublicationID:
.collect(groupingBy(PublicationDTO::getPublicationID))
Since those fields you are interested in for grouping are all strings you could concatenate them and use that as a grouping classifier:
.collect(Collectors.groupingBy(p -> p.getPublicationName() + p.getPublicationID() + p.getLocale()))

Check if each map of the list contains all key-values combination from two sources via Stream api

I have a small service which takes two arrays person and document and then return some array with combined data from them
If request like this:
{
"documents": [
{
"id": "A",
},
{
"id": "B",
}
],
"persons": [
{
"lastName": "C",
},
{
"lastName": "D",
}
]
}
Then the response has an array like this:
{
"documents": [
{
"id": "A",
"lastName": "C"
},
{
"id": "A",
"lastName": "D"
},
{
"id": "B",
"lastName": "C"
},
{
"id": "B",
"lastName": "D"
}
]
}
If i get List<Map<String,String>> of resulting array how do i check if each map has each Key-Value combinations via Stream Api. I managed to do this with brute loops but struggling with Stream Api
Update: my method eventually looks like this
private void checkResultList(List<Map<String, String>> resultList) {
List<String> persons = Arrays.asList("C", "D");
List<String> documents = Arrays.asList("A", "B");
List<Map<String, String>> expectedList = new ArrayList<>();
List<Map<String, String>> actualList = new ArrayList<>();
for (String name : persons){
for (String id : documents){
Map<String,String> expectedElement = new HashMap<>();
expectedElement.put("lastName", name);
expectedElement.put("id", id);
expectedList.add(expectedElement);
}
}
resultList.stream().forEach(i -> {
Map<String,String> actualElement = new HashMap<>();
actualElement.put("lastName", i.get("lastName"));
actualElement.put("id", i.get("id"));
actualList.add(actualElement);
});
Assertions.assertEquals(actualList, expectedList);
}
Update 2
So far i managed to implement it like this. But i still have two steps. And can't find out if it can be written any shorter.
private void checkResultList(List<Map<String, String>> resultList) {
List<String> persons = Arrays.asList("C", "D");
List<String> documents = Arrays.asList("A", "B");
List<Map<String, String>> expectedList = new ArrayList<>();
List<Map<String, String>> actualList = new ArrayList<>();
persons.forEach(i -> documents.forEach(k -> {
HashMap<String, String> e = new HashMap<>();
e.put("id", k);
e.put("name", i);
expectedList.add(e);
}));
resultList.stream().forEach(i -> {
Map<String,String> actualElement = new HashMap<>();
actualElement.put("lastName", i.get("lastName"));
actualElement.put("id", i.get("id"));
actualList.add(actualElement);
});
Assertions.assertEquals(actualList, expectedList);
}
I just learned this.. How to flat map of values to another map grouped by key
List<Map<String, String>> expectedList =
documents
.stream()
.flatMap(d -> persons.stream().map(p -> Map.of("id", d, "lastName", p)))
.collect(toList());

Find the missing elements of a list not in the values of a hashmap

I want to get a list of hashmap that grouped by year and find the customer_id that doesn't contain a group based on a list of customer_id.
This is an example of the dataset.
List tagList = new ArrayList<>();
# Customer
HashMap<String, Object> customerMap = new HashMap<>();
## feeding data example
customerMap.put("date", "2018");
customerMap.put("name", "John");
customerMap.put("custemer_no", "1a");
tagList.add(customerMap);
customer_id_list = ['1a', '2b', '3c']
customer_list = [
{
"date": "2019",
"name": "John",
"customer_id": "1a"
},
{
"date": "2019",
"name": "David",
"customer_id": "2b"
},
{
"date": "2020",
"name": "John",
"customer_id": "1a"
},
{
"date": "2020",
"name": "Alex",
"customer_id": "3c"
},
{
"date": "2021",
"name": "John",
"customer_id": "1a"
}
]
This is a sample output that I want.
missing_customer_list = [
{
"date": "2019",
"name": "Alex",
"customer_id": "3c"
},
{
"date": "2020",
"name": "David",
"customer_id": "2b"
},
{
"date": "2021",
"name": "David",
"customer_id": "2b"
},
{
"date": "2021",
"name": "Alex",
"customer_id": "3c"
}
]
Do you have any ideas how I can get this sample output using stream comprehension?
If I cannot filter the list directly using stream comprehension, using a for loop is fine too.
I found the way how to group by year for now, but don't know how to handle the rest of the filtering..
Please help me! thanks in advance
List<Customer> result = customer_list.stream()
.collect(Collectors.groupingBy(Customer::getDate))
UPDATE
Referring to the current answers,
I ve got stuck at the point where I couldn't create the new instance of customer.
So, Im planning to use for-loops to find the missing_customer_id during iteratation of customer_id_list and filtered_customer_list.
Once I get the missing_customer_id in a list, I will try to re-create the Customers manually and add them into a new list to print
There are many different ways to do this. These solutions can become inefficient for large lists. Because, calling .contains() on a List is a linear-time operation, meaning doing so n times is quadratic.
List<String> customerIdList = Arrays.asList("a", "b", "c");
List<Customer> customerList = new ArrayList<>();
customerList.add(new Customer("2018", "a"));
customerList.add(new Customer("2018", "b"));
customerList.add(new Customer("2019", "b"));
customerList.add(new Customer("2019", "c"));
customerList.add(new Customer("2020", "a"));
customerList.add(new Customer("2020", "c"));
customerList.add(new Customer("2021", "a"));
Map<String, List<Customer>> collect = customerList.stream()
.collect(Collectors.groupingBy(Customer::getDate))
.entrySet().stream().map(notExistCustomerByYear(customerIdList))
.flatMap(Collection::stream)
.collect(Collectors.groupingBy(Customer::getDate));
The method that accomplishes what I really want is as follows;
private Function<Map.Entry<String, List<Customer>>, List<Customer>> notExistCustomerByYear(List<String> customerIdList) {
return e -> {
List<String> customerIds = e.getValue().stream().map(Customer::getCustomerId).collect(Collectors.toList());
List<String> notExistCustomerIdsInYear = customerIdList.stream().filter(id -> !customerIds.contains(id)).collect(Collectors.toList());
return notExistCustomerIdsInYear.stream().map(id -> new Customer(e.getKey(), id)).collect(Collectors.toList());
};
}
What you describe is not filtering. According to your question and comments, you want a completely new list with combinations of year and customer-id which were not present in the original list.
First, you can group customer-ids per year to have a better representation of your original list.
Map<Integer, List<String>> customerIdPerYear = customerList.stream()
.collect(Collectors.groupingBy(
Customer::getYear,
Collectors.mapping(
Customer::getCustomerId,
Collectors.toList())));
System.out.println(customerIdPerYear);
// output: {2018=[a, b], 2019=[b, c], 2020=[a, c], 2021=[a]}
In a second step, you create a new list per year with the customer-ids not found in the original list. Finally, you can create new Customer objects and return a flattened list with flatMap.
List<Customer> missingCustomersPerYear = customerIdPerYear.entrySet().stream()
.flatMap(e -> customerIdList.stream()
.filter(id -> !e.getValue().contains(id))
.map(id -> new Customer(e.getKey(), id)))
.collect(Collectors.toList());
System.out.println(missingCustomersPerYear);
// output: [(2018, c), (2019, a), (2020, b), (2021, b), (2021, c)]
To be complete, here is the Customer class used for the above examples:
class Customer {
private int year;
private String customerId;
public Customer(final int year, final String customerId) {
this.year = year;
this.customerId = customerId;
}
public int getYear() {
return year;
}
public String getCustomerId() {
return customerId;
}
#Override
public String toString() {
return "(" + year + ", " + customerId + ")";
}
}

Java 8: Group Object array to Map to return as JSON

I have an Object 2D array (Object [][]) that is returned from querying a database. I now want to map it to Objects which can be returned in API call after grouping.
Here is my 2D Object array.
Object [][] dbResult =
{
{1, "a", 9, "Content1", "format1", false, true},
{1, "a", 9, "Content1", "format2", true, false},
{2, "b", 8, "Content2", "format3", true, false},
{2, "b", 8, "Content2", "format4", false, false},
{3, "c", 7, "Content3", "format5", true, true},
{4, "d", 8, "Content2", "format6", false, true},
{4, "d", 6, "Content3", "format7", true, true},
{4, "d", 5, "Content4", "format8", false, false},
{5, "e", 4, "Content5", "format9", false, true},
{6, "f", 3, "Content6", "format10", true, false}
};
Here is the legend/key for the index.
[ID, Name, AnotherID, AnotherName, Format, Boolean, Boolean]
I want to return
List<IdName> idsNames;
Where each of the classes should be mapped like this.
class IdName {
String id;
String name;
List<Another> anotherNameList;
}
class Another {
String anotherId;
String anotherName;
List<Format> formatList;
}
class Format {
String format;
Boolean isEnabled;
Boolean isManaged;
}
I tried using Java 8's groupingBy but I couldn't get to the state that I want.
Sample expected result:
[
{
"id": 1,
"name": "a",
"another": [
{
"anotherId": 9,
"anotherName": "Content1",
"format": [
{
"format": "format1",
"isEnabled": true,
"isManaged": false
},
{
"format": "format2",
"isEnabled": true,
"isManaged": false
}
]
}
]
}
]
Looks like you should use Collectors.collectingAndThen.
First create the extractors (Assuming your classes have constructors & getters):
// The cast types are just an example. You can Cast/convert the array values to any type you want
IdName extractIdName(Object[] row) {
return new IdName((String) row[0], (String) row[1], null);
}
Another extractAnother(Object[] row) {
return new Another((String) row[2], (String) row[3], null);
}
Format extractFormat(Object[] row) {
return new Format((String) row[4], (boolean) row[5], (boolean) row[6]);
}
Then you will need the merge functions:
List<Another> setFormats(Map<Another, List<Format>> map) {
return map.entrySet()
.stream()
.map(e -> {
e.getKey().setFormatList(e.getValue());
return e.getKey();
})
.collect(toList());
}
List<IdName> setAnothers(Map<IdName, List<Another>> map) {
return map.entrySet()
.stream()
.map(entry -> {
entry.getKey().setAnotherNameList(entry.getValue());
return entry.getKey();
})
.collect(toList());
}
Finally this will do the trick:
// Converting Object[][] to List<IdName>
List<IdName> list =
Arrays.stream(dbResult)
.collect(
collectingAndThen(
groupingBy(this::extractIdName,
collectingAndThen(
groupingBy(this::extractAnother,
mapping(this::extractFormat, toList())),
this::setFormats
)),
this::setAnothers));
It can be done in several steps. Let all values be String for simplicity.
Also you are supposed to have constructors and equals/hashcode methods implemented.
Map<IdName, Map<Another, List<String[]>>> map = Arrays.stream(dbResult)
.collect(
groupingBy(s -> new IdName(s[0], s[1], null),
groupingBy(s -> new Another(s[2], s[3], null))));
Then we can create Format objects and put everything together.
for (Map.Entry<IdName, Map<Another, List<String[]>>> entry : map.entrySet()) {
IdName idName = entry.getKey(); // main object
Set<Another> anothers = entry.getValue().keySet();
for (Another another : anothers) { // create list<Format> for each Another
List<Format> formatList = entry.getValue().get(another).stream()
.map(format -> new Format(format[4], format[5], format[6]))
.collect(Collectors.toList());
another.setFormatList(formatList);
}
idName.setAnotherNameList(anothers);
}
Now we can get all assembled objects
Set<IdName> idNames = map.keySet();

How to put array into Hashmap for encoding a JSON object

I'm trying to send API call using json-simple 1.1.1 and I save fields and values as a HashMap. I should send those parameters:
{ api_key : string,
product_id : string,
name : string,
tax_rates : array }
Here is a HashMap example:
HashMap<String,Object> arg = new HashMap<String, Object>();
arg.put("product_id","42");
arg.put("name", "EKOS");
arg.put("tax_rates", taxarray);
I saved taxarray as a HashMap as well:
HashMap<String, Object> taxarray = new HashMap<String, Object>();
taxarray.put("name","EKOS");
taxarray.put("type", "type_value_fixed");
taxarray.put("value", "56");
But when I execute an API call it reurns an error: Parameter 'tax_rates' is not valid. The required type of parameter is an array.
I had been trying to save taxarray HashMap as JSONArray as well. Could you please help me with this?
An additional question: how can I save 2 or more taxrates within one "tax_rates"? Here is an example:
HashMap<String,Object> arg = new HashMap<String, Object>();
arg.put("product_id","42");
arg.put("name", "EKOS");
arg.put("tax_rates", array [
taxarray1[],
taxarray2[]
]);
You should have something like this - Tax class:
public class Tax {
String name;
String type;
Integer[] values;
public Tax(String name, String type, Integer[] values) {
this.name = name;
this.type = type;
this.values = values;
}
}
And then use an array of objects of Tax class instead of HashMap for tax_rates : array.
This code using google json:
Map<String, Object> arg = new HashMap<String, Object>();
arg.put("product_id", "42");
arg.put("name", "EKOS");
arg.put("tax_rates",
new Tax[] { new Tax("EKOS", "type_value_fixed", new Integer[] { 1, 2, 3 }),
new Tax("ABC", "type_value_fixed", new Integer[] { 4, 5 }),
new Tax("DEF", "type_value_fixed", new Integer[] { 6, 7}) });
Gson gson = new Gson();
System.out.println(gson.toJson(arg));
Will give you such json:
{
"product_id": "42",
"name": "EKOS",
"tax_rates": [
{
"name": "EKOS",
"type": "type_value_fixed",
"values": [
1,
2,
3
]
},
{
"name": "ABC",
"type": "type_value_fixed",
"values": [
4,
5
]
},
{
"name": "DEF",
"type": "type_value_fixed",
"values": [
6,
7
]
}
]
}
tax_rates has to be an array, so do this:
List<Double> taxRates = new ArrayList<Double>();
taxRates.add(19);
taxRates.add(17.5);
Map<String,Object> arg = new HashMap<String, Object>();
arg.put("product_id","42");
arg.put("name", "EKOS");
arg.put("tax_rates", taxRates);

Categories

Resources