I have a following map
Map<String, String> map = new HashMap<>();
And collection of dto
List<MyDto> dtoCollection = new ArrayList<>();
class MyDto {
String type;
String name;
}
for(MyDto dto : dtoCollection) {
map.compute(dto.getType(), (key,value) -> value + ", from anonymous\n"());
}
And the question is how to replace Map<String, String> to Map<String, StrinBuilder> and make append inside the loop?
You can simply replace value + ", from anonymous\n" with value == null ? new StringBuilder(dto.getName()) : value.append(", from anonymous\n")).
Illustration:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class MyDto {
String type;
String name;
public MyDto(String type, String name) {
this.type = type;
this.name = name;
}
public String getType() {
return type;
}
public String getName() {
return name;
}
}
public class Main {
public static void main(String[] args) {
Map<String, StringBuilder> map = new HashMap<>();
List<MyDto> dtoCollection = new ArrayList<>();
for (MyDto dto : dtoCollection) {
map.compute(dto.getType(), (key, value) -> value == null ? new StringBuilder(dto.getName())
: value.append(", from anonymous\n"));
}
}
}
Am I missing something?
Such methods as Map::merge or collection to map would require creation of extra StringBuilder instances which should be concatenated then:
map.merge(
dto.getType(),
new StringBuilder(dto.getName()).append(" from anonymous\n"), // redundant StringBuilder
(v1, v2) -> v1.append(v2) // merging multiple string builders
);
It is possible to use computeIfAbsent to create only one instance of StringBuilder when it's missing in the map and after that call append to the already existing value:
Map<String, StringBuilder> map = new HashMap<>();
List<MyDto> dtoCollection = Arrays.asList(
new MyDto("type1", "aaa"), new MyDto("type2", "bbb"),
new MyDto("type3", "ccc"), new MyDto("type1", "aa2"));
for (MyDto dto : dtoCollection) {
map.computeIfAbsent(dto.getType(), (key) -> new StringBuilder()) // create StringBuilder if needed
.append(dto.getName()).append(" from anonymous\n");
}
System.out.println(map);
Output:
{type3=ccc from anonymous
, type2=bbb from anonymous
, type1=aaa from anonymous
aa2 from anonymous
}
Related
I googled for it but I mostly found cases for grouping by aggregated fields or on to alter response of stream but not the scenario below:
I have a class User with fields category and marketingChannel.
I have to write a method in the declarative style that accepts a list of users and counts users based on
category and also based on marketingChannel individually (i.e not groupingBy(... ,groupingBy(..)) ).
I am unable to do it in a single loop. This is what I have to achieve.
I coded few methods as follows:
import java.util.*;
import java.util.stream.*;
public class Main
{
public static void main(String[] args) {
List<User> users = User.createDemoList();
imperative(users);
declerativeMultipleLoop(users);
declerativeMultipleColumn(users);
}
public static void imperative(List<User> users){
Map<String, Integer> categoryMap = new HashMap<>();
Map<String, Integer> channelMap = new HashMap<>();
for(User user : users){
Integer value = categoryMap.getOrDefault(user.getCategory(), 0);
categoryMap.put(user.getCategory(), value+1);
value = channelMap.getOrDefault(user.getMarketingChannel(), 0);
channelMap.put(user.getMarketingChannel(), value+1);
}
System.out.println("imperative");
System.out.println(categoryMap);
System.out.println(channelMap);
}
public static void declerativeMultipleLoop(List<User> users){
Map<String, Long> categoryMap = users.stream()
.collect(Collectors.groupingBy(
User::getCategory, Collectors.counting()));
Map<String, Long> channelMap = users.stream()
.collect(Collectors.groupingBy(
User::getMarketingChannel, Collectors.counting()));
System.out.println("declerativeMultipleLoop");
System.out.println(categoryMap);
System.out.println(channelMap);
}
public static void declerativeMultipleColumn(List<User> users){
Map<String, Map<String, Long>> map = users.stream()
.collect(Collectors.groupingBy(
User::getCategory,
Collectors.groupingBy(User::getMarketingChannel,
Collectors.counting())));
System.out.println("declerativeMultipleColumn");
System.out.println("groupingBy category and marketChannel");
System.out.println(map);
Map<String, Long> categoryMap = new HashMap<>();
Map<String, Long> channelMap = new HashMap<>();
for (Map.Entry<String, Map<String, Long>> entry: map.entrySet()) {
String category = entry.getKey();
Integer count = entry.getValue().size();
Long value = categoryMap.getOrDefault(category,0L);
categoryMap.put(category, value+count);
for (Map.Entry<String, Long> channelEntry : entry.getValue().entrySet()){
String channel = channelEntry.getKey();
Long channelCount = channelEntry.getValue();
Long channelValue = channelMap.getOrDefault(channel,0L);
channelMap.put(channel, channelValue+channelCount);
}
}
System.out.println("After Implerative Loop on above.");
System.out.println(categoryMap);
System.out.println(channelMap);
}
}
class User{
private String name;
private String category;
private String marketChannel;
public User(String name, String category, String marketChannel){
this.name = name;
this.category = category;
this.marketChannel = marketChannel;
}
public String getName(){
return this.name;
}
public String getCategory(){
return this.category;
}
public String getMarketingChannel(){
return this.marketChannel;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(name, user.name) &&
Objects.equals(category, user.category) &&
Objects.equals(marketChannel, user.marketChannel);
}
#Override
public int hashCode() {
return Objects.hash(name, category, marketChannel);
}
public static List<User> createDemoList(){
return Arrays.asList(
new User("a", "student","google"),
new User("b", "student","bing"),
new User("c", "business","google"),
new User("d", "business", "direct")
);
}
The method declerativeMultipleLoop is declarative but it has a separate loop for each field. Complexity : O(noOfFields * No of users)
The problem is in declerativeMultipleColumn Method as I end up writing imperative code and multiple loops.
I want to write the above method in completely declarative and as efficient as possible. i.e Complexity : O(No of users)
Sample output:
imperative
{business=2, student=2}
{direct=1, google=2, bing=1}
declerativeMultipleLoop
{business=2, student=2}
{direct=1, google=2, bing=1}
declerativeMultipleColumn
groupingBy category and marketChannel
{business={direct=1, google=1}, student={google=1, bing=1}}
After Implerative Loop on above.
{business=2, student=2}
{direct=1, google=2, bing=1}
If I understand your requirement it is to use a single stream operation that results in 2 separate maps. That is going to require a structure to hold the maps and a collector to build the structure. Something like the following:
class Counts {
public final Map<String, Integer> categoryCounts = new HashMap<>();
public final Map<String, Integer> channelCounts = new HashMap<>();
public static Collector<User,Counts,Counts> countsCollector() {
return Collector.of(Counts::new, Counts::accept, Counts::combine, CONCURRENT, UNORDERED);
}
private Counts() { }
private void accept(User user) {
categoryCounts.merge(user.getCategory(), 1, Integer::sum);
channelCounts.merge(user.getChannel(), 1, Integer::sum);
}
private Counts combine(Counts other) {
other.categoryCounts.forEach((c, v) -> categoryCounts.merge(c, v, Integer::sum));
other.channelCounts.forEach((c, v) -> channelCounts.merge(c, v, Integer::sum));
return this;
}
}
That can then be used as a collector:
Counts counts = users.stream().collect(Counts.countsCollector());
counts.categoryCounts.get("student")...
(Opinion only: the distinction between imperative and declarative is pretty arbitrary in this case. Defining stream operations feels pretty procedural to me (as opposed to the equivalent in, say, Haskell)).
You can compute two maps in a single forEach method:
public static void main(String[] args) {
List<User> users = Arrays.asList(
new User("a", "student", "google"),
new User("b", "student", "bing"),
new User("c", "business", "google"),
new User("d", "business", "direct"));
Map<String, Integer> categoryMap = new HashMap<>();
Map<String, Integer> channelMap = new HashMap<>();
// group users into maps
users.forEach(user -> {
categoryMap.compute(user.getCategory(),
(key, value) -> value == null ? 1 : value + 1);
channelMap.compute(user.getChannel(),
(key, value) -> value == null ? 1 : value + 1);
});
// output
System.out.println(categoryMap); // {business=2, student=2}
System.out.println(channelMap); // {direct=1, google=2, bing=1}
}
static class User {
private final String name, category, channel;
public User(String name, String category, String channel) {
this.name = name;
this.category = category;
this.channel = channel;
}
public String getName() { return this.name; }
public String getCategory() { return this.category; }
public String getChannel() { return this.channel; }
}
Scenario:
I'm working with an unusual external API in which every attribute is a map with multiple values.
In order to convert this response into simple Java objects, I had to do some dirty unboxing. Below is one of the typical java class. As you can see how I'm unboxing data from response and mapping them to my java class:
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;
import java.time.LocalDate;
import java.util.Map;
import static com.my.util.BaseUtil.unbox;
#Getter
#Setter
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class PartyDetailsDto {
private String partyId;
private String partyType;
private String title;
private String firstName;
private String lastName;
private String middleName;
private LocalDate dateOfBirth;
#JsonProperty(value = "partyId")
public void unboxPartyId(Map<String, String> data) {
this.partyId = unbox(data, "evidenceId");
}
#JsonProperty(value = "partyType")
public void unboxPartyType(Map<String, String> partyType) {
this.partyType = unbox(partyType, "value");
}
#JsonProperty(value = "individual")
public void unboxIndividualDetails(Map<String, Object> individual) {
Map<String, String> title = (Map<String, String>) individual.get("title");
Map<String, String> firstName = (Map<String, String>) individual.get("firstName");
Map<String, String> lastName = (Map<String, String>) individual.get("lastName");
Map<String, String> middleName = (Map<String, String>) individual.get("middleName");
Map<String, String> dateOfBirth = (Map<String, String>) individual.get("birthDate");
this.title = unbox(title, "value");
this.firstName = unbox(firstName, "value");
this.lastName = unbox(lastName, "value");
this.middleName = unbox(middleName, "value");
this.dateOfBirth = LocalDate.parse(unbox(dateOfBirth, "value"));
}
}
This is the sample util method - unbox - which I've created in order to avoid writing such ugly code. Right now, it only works for cases where String is returned.
import java.util.Map;
public class BaseUtil {
// TODO: Make this method generic
public static String unbox(Map<String, String> data, String key) {
if (data != null && data.containsKey(key)) {
return data.get(key);
}
return null;
}
}
I'm trying to convert above method into a generic one where I could specify the return type dynamically and cast the returned data accordingly.
Can anyone help me out in creating one?
I've tried this:
public static <T> T unbox(Map<String, String> data, String key, Class<T> type) {
if (data != null && data.containsKey(key)) {
return (type) data.get(key);
}
return null;
}
But it obviously doesn't work but in theory that's the kind of solution that I'm expecting.
EDIT: Here's a sample input of complex type:
// The associatePartyRole is a list of Stings.
#JsonProperty(value = "associatedPartyRole")
public void unboxAssociatedPartyRole(Map<String, Object> data) {
this.associatedPartyRole = unbox(data, "value", List.class);
// Compilation error: Need list, provided object.
}
EDIT 2: Here's the final solution:
PartyDetailsDto.java
public class PartyDetailsDto implements Serializable {
private static final long serialVersionUID = 3851075484507637508L;
private String partyId;
private String partyType;
private String title;
private String firstName;
private String lastName;
private String middleName;
private LocalDate dateOfBirth;
#JsonProperty(value = "partyId")
public void unboxPartyId(Map<String, String> data) {
this.partyId = unbox(data, "evidenceId");
}
#JsonProperty(value = "partyType")
public void unboxPartyType(Map<String, String> partyType) {
this.partyType = unbox(partyType, "value");
}
#JsonProperty(value = "individual")
public void unboxIndividualDetails(Map<String, Object> individual) {
this.title = unbox(unbox(individual, "title", Map.class), "value");
this.firstName = unbox(unbox(individual, "firstName", Map.class), "value");
this.lastName = unbox(unbox(individual, "lastName", Map.class), "value");
this.middleName = unbox(unbox(individual, "middleName", Map.class), "value");
this.dateOfBirth = LocalDate.parse(unbox(unbox(individual, "title", Map.class), "value"));
}
}
BaseUtil.java
public class BaseLineUtil {
public static <T> T unbox(Map<String, Object> data, String key, Class<?> ofType) {
return Optional.ofNullable(data)
.map(m -> (T) ofType.cast(m.get(key)))
.orElse(null);
}
public static <T> T unbox(Map<String, T> data, String key) {
return Optional.ofNullable(data)
.map(m -> (T) m.get(key))
.orElse(null);
}
}
Thanks #deduper #davidxxx for your answers.
Maybe that :
public static <T> T unbox(Map<String, T> data, String key) {
if (data != null && data.containsKey(key)) {
return data.get(key);
}
return null;
}
Here T implies T extends Object.
That you can use so with any class:
Map<String, Integer> map = ...;
Integer value = unbox(map, "key");
Note that you could simplify your implementation such as :
public static <T> T unbox(Map<String, T> data, String key) {
return Optional.ofNullable(data)
.map(m -> m.get(key))
.orElse(null);
}
It is also more efficient (a single map access)
OP comment :
I followed your solution but it doesn't seem to work when the return
type is supposed to be a list or an array. How would i handle that
case
That is surprising. It should work. Try that sample code :
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("key", 100);
Integer value = unbox(map, "key");
System.out.println(value);
Map<String, List<Integer>> mapOfList = new HashMap<>();
mapOfList.put("key", Arrays.asList(1, 2));
List<Integer> valueList = unbox(mapOfList, "key");
System.out.println(valueList);
Map<String, int[]> mapOfArray = new HashMap<>();
mapOfArray.put("key", new int[] { 1, 2, 3 });
int[] valueArray = unbox(mapOfArray, "key");
System.out.println(Arrays.toString(valueArray));
}
It outputs :
100
[1, 2]
[1, 2, 3]
If that is not what you are looking for. Please rewrite your question by specifying exactly what you want to perform.
Edit after requiement change :
public void unboxIndividualDetails(Map<String, Object> individual) {...}
In fact here you want to perform unsafe casts. To achieve that you don't need to pass a Class instance and that not will not make your code more safe type either.
What you want is telling to the compiler to accept that the object that is declared as Object be assigned to a more specific type variable.
In terms of cast logic that looks like :
Object o = Integer.valueOf(100);
Integer i = (Integer)o;
By declaring a parameterized generic type method, the compiler infers T from the target type (the variable type that received the call method return), so you can do just return (T)myObject.
Concrete code :
public static <T> T unboxUnsafe(Map<String, Object> data, String key) {
return Optional.ofNullable(data)
.map(m -> (T)m.get(key))
.orElse(null);
}
And here a sample test :
public static void main(String[] args) {
Map<String, Object> mapOfObjects = new HashMap< >( );
mapOfObjects.put("longKey", 1L );
mapOfObjects.put("stringKey", "hello" );
Long l = unboxUnsafe(mapOfObjects, "longKey");
System.out.println(l);
String s = unboxUnsafe(mapOfObjects, "stringKey");
System.out.println(s);
}
Output :
1
hello
„…I followed [#davidxx's] solution but it doesn't seem to work when the return type is supposed to be a list or an array. How would i handle that case?…“
Through a process I call „EDD“ („Experiment-driven Development“) the following way to handle those cases emerged…
public static < T > T unbox( Map< String, T > data, String key, Class< ? > ofType ) {
if ( data != null && data.containsKey( key ) ) {
return (T)ofType.cast( data.get( key ) ) ;
}
return null;
}
You can observe in main(String[]) that the following calls successfully return the expected result…
...
List< String > unBoxedList = unbox( mapOfLists, foo, List.class );
...
List< ? >[ ] unBoxedArrayOfLists = unbox( mapOfArrayOfLists, "bar", List[ ].class );
...
String unBoxedString = unbox( mapOfStrings, foo, String.class );
...
Integer unBoxedInteger = unbox( mapOfIntegers, bar, Integer.class );
...
Click the green Start button at the top of the page in the link above, to run the experiment.
After feedback in the comments from #saran3h that clarified his use case, the following refactor emerged from a subsequent iteration of the experiment…
public class BaseUtil {
public List<Object> associatedPartyRole ;
// TODO: Make this method generic
public static < T > T unbox( Map< String, T > data, String key, Class< ? > ofType ) {
if ( data != null && data.containsKey( key ) ) {
return (T)ofType.cast( data.get( key ) ) ;
}
return null;
}
public void unboxAssociatedPartyRole(Map<String, Object> data) {
this.associatedPartyRole = (List)unbox(data, "foo", Object.class);
}
}
That new case was successfully tested with…
...
private static final Map<String, Object> mapOfObjects = new HashMap< >( );
...
mapOfObjects.put( foo, (Object)mapOfLists.get( foo ) );
...
BaseUtil user = new BaseUtil( );
user.unboxAssociatedPartyRole( mapOfObjects );
List< Object > objs = user.associatedPartyRole;
assertIsA( objs, List.class );
Observe the results of running the experiment with the above refactor (pardon my French)…
[What The Reifiable Fuck#&*%$*!?]
EXPERIMENT SUCCESSFUL
I have List of stories. Using unique property(id) I want to collect keyword and targeting as list of values. Can I do this with MultiMap? Or is there other library for this?
[{
id = 1,
title = Onboarding,
keyword = new joinee,
targeting = finance
}, {
id = 1,
title = Onboarding,
keyword = training,
targeting = HR
}]
The Desired output must like this :
{
id = 1,
title = Onboarding,
keyword = [new joinee,training], //may be keywords - plural
targeting = [HR,finance]
}
Sample my tried Code as follows:
package prac;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class JavaPrac {
public static void main(String[] args) {
Multimap<Integer, Map> multiMap = ArrayListMultimap.create();
List<Map> stories=new ArrayList();
Map story1=new HashMap();
story1.put("id", 1);
story1.put("title", "Onboarding");
story1.put("keyword","new joinee");
story1.put("targeting","finance");
Map story2=new HashMap();
story2.put("id", 1);
story2.put("title", "Onboarding");
story2.put("keyword","training");
story2.put("targeting","HR");
stories.add(story1);
stories.add(story2);
System.out.println(stories);
stories.forEach((story) -> {
multiMap.put((Integer) story.get("id"), story);
});
}
}
A multimap can only store multiple values per key but what you want is to combine those multiple values so that you get one element that has the same id and title as well as a collection of keywords and targeting information. Thus it would probably be best to either have something like MultiStory or already have Story contain those collections.
I'd suggest using proper objects instead of just maps but with maps and Java 8 lambdas you could use compute() etc. to build maps that contain collections and combine maps that don't.
Here's an example of how you'd do it with maps. Note that this is very bad style and an example using proper pojos will follow:
Disclaimer: example based on the OP's code, not recommended (read text above)
//Problem 1: we don't know the type of the values, i.e. we could put anything for "id" etc.
Map<String, Object> story1=new HashMap<>();
story1.put("id", 1);
story1.put("title", "Onboarding");
story1.put("keyword","new joinee");
story1.put("targeting","finance");
Map<String, Object> story2=new HashMap<>();
story2.put("id", 1);
story2.put("title", "Onboarding");
story2.put("keyword","training");
story2.put("targeting","HR");
List<Map<String, Object>> stories=new ArrayList<>();
stories.add(story1);
stories.add(story2);
Map<Integer, Map<String, Object>> combined = new HashMap<>();
stories.forEach((story) -> {
//Problem 2: because we don't know the type of the values we need a lot of nasty casts
Map<String, Object> combinedStory = combined.computeIfAbsent( (Integer)story.get( "id" ), k -> new HashMap<String, Object>() );
combinedStory.put("id", story.get( "id" ) );
combinedStory.put("title", story.get( "title" ) );
//Problem 3: the combined map would look a lot like your "story" maps but would contain different types
((List<String>)combinedStory.computeIfAbsent( "keyword", v -> new List<String>() )).add( (String)story.get("keyword") );
((List<String>)combinedStory.computeIfAbsent( "targeting", v -> new List<String>() )).add( (String)story.get("targeting") );
});
Using POJOs
Here's a greatly simplified example of how you'd do it with proper Java objects (POJOs). Note that those are meant to resemble your code as much as possible and there are a lot of other issues but addressing those would be way too much here and better designed code would be a lot larger and probably harder to understand - after all it's just meant to show you a difference.
First let's define our classes (for simplicity I made the fields public, you'd normally not do that):
class Story {
public final int id;
public String title;
public String keyword;
public String targeting;
public Story(int storyId) {
id = storyId ;
}
}
class MultiStory {
public final int id;
public String title;
public Set<String> keywords = new HashSet<>();
public Set<String> targetingInfo = new HashSet<>();
public MultiStory( int storyId ) {
id = storyId ;
}
}
Then let's reiterate the code above:
Story story1=new Story( 1 );
story1.title = "Onboarding";
story1.keyword = "new joinee";
story1.targeting = "finance";
Story story2=new Story( 1 );
story2.title = "Onboarding";
story2.keyword = "training";
story2.targeting = "HR";
List<Story> stories=new ArrayList<>();
stories.add(story1);
stories.add(story2);
Map<Integer, MultiStory> combined = new HashMap<>();
stories.forEach((story) -> {
MultiStory multiStory = combined.computeIfAbsent( story.id, v -> new MultiStory( story.id ) );
multiStory.title = story.title;
multiStory.keywords.add( story.keyword );
multiStory.targetingInfo.add( story.targeting );
});
As you can see, there are no casts needed and it's clear what fields are available (though not necessarily filled) which makes it easier to reason about the code and spot errors (the compiler can help a lot here which it couldn't to in the example that uses maps).
Here is a solution using classes to represent the story and tags:
public static void main(String[] args) {
TagsCollector app = new TagsCollector();
app.go();
}
private void go() {
List<Story> stories = createStories();
System.out.println(stories);
Map<Long, Tags> tagsById = collectTags(stories);
tagsById.forEach((aLong, tags) -> System.out.println(tags));
}
private List<Story> createStories() {
return Arrays.asList(
new Story(1, "Onboarding", "new joinee", "finance"),
new Story(1, "Onboarding", "training", "HR")
);
}
private Map<Long, Tags> collectTags(List<Story> stories) {
Map<Long, Tags> tagsById = new HashMap<>();
stories.forEach(s -> {
Tags tags = tagsById.computeIfAbsent(s.id, v -> new Tags(s));
tags.getKeywords().add(s.getKeyword());
tags.getTargetings().add(s.getTargeting());
});
return tagsById;
}
Class used to represent the Story:
public class Story {
private final long id;
private final String title;
private final String keyword;
private final String targeting;
public Story(long id, String title, String keyword, String targeting) {
this.id = id;
this.title = title;
this.keyword = keyword;
this.targeting = targeting;
}
public long getId() {
return id;
}
public String getTitle() {
return title;
}
public String getKeyword() {
return keyword;
}
public String getTargeting() {
return targeting;
}
#Override
public String toString() {
return String.format("Story %s, title=%s, keyword=%s, targeting=%s", id, title, keyword, targeting);
}
}
Class used to represent the Tags:
public class Tags {
private final long id;
private final String title;
private final List<String> keywords = new ArrayList<>();
private final List<String> targetings = new ArrayList<>();
Tags(Story story) {
this.id = story.id;
this.title = story.title;
}
public List<String> getKeywords() {
return keywords;
}
public List<String> getTargetings() {
return targetings;
}
#Override
public String toString() {
return String.format("Tags for id %s, title:%s: keywords=%s, targetings=%s", id, title, keywords, targetings);
}
}
Output
[Story 1, title=Onboarding, keyword=new joinee, targeting=finance, Story 1, title=Onboarding, keyword=training, targeting=HR]
Tags for id 1, title:Onboarding: keywords=[new joinee, training], targetings=[finance, HR]
Yes, you can do that with a Multimap. First I would define a pojo for Story in order to make things clearer:
public class Story {
private int id;
private String title;
private String keyword;
private String targeting;
//getters setters
}
Second you need to define a key with hashcode and equals.
public static class StoryKey {
private final int id;
private final String title;
public StoryKey(int id, String title) {
this.id = id;
this.title = title;
}
//getters
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StoryKey storyKey = (StoryKey) o;
if (id != storyKey.id) return false;
return title != null ? title.equals(storyKey.title) : storyKey.title == null;
}
#Override
public int hashCode() {
int result = id;
result = 31 * result + (title != null ? title.hashCode() : 0);
return result;
}
The code will look like:
ArrayListMultimap<StoryKey, Story> multiMap = ArrayListMultimap.create();
List<Story> stories = new ArrayList();
Story story1 = new Story();
story1.setId(1);
story1.setTitle("Onboarding");
story1.setKeyword("training");
story1.setTargeting("HR");
Story story2 = new Story();
story2.setId(1);
story2.setTitle("Onboarding");
story2.setKeyword("new joinee,");
story2.setTargeting("finance");
stories.add(story1);
stories.add(story2);
System.out.println(stories);
stories.
forEach((story) -> {
multiMap.put(new StoryKey(story.getId(), story.getTitle()), story);
});
multiMap.keys().forEach(key ->
System.out.println(
"id =" + key.getId() +
" title =" + key.getTitle()+
"keyword =" + multiMap.get(key).stream().map(story->story.getKeyword()).collect(Collectors.toList()).toString()+
"targeting ="+ multiMap.get(key).stream().map(story->story.getTargeting()).collect(Collectors.toList()).toString())
);
I have a Model and a Property class with the following signatures:
public class Property {
public String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Model {
private List<Property> properties = new ArrayList<>();
public List<Property> getProperties() {
return properties;
}
}
I want a Map<String, Set<Model>> from a List<Model> where the key would be the name from the Property class. How can I can I use java8 streams to group that list by its Properyes' name? All Propertyes are unique by name.
It is possible to solve in a single stream or should I split it somehow or go for the classical solution?
yourModels.stream()
.flatMap(model -> model.getProperties().stream()
.map(property -> new AbstractMap.SimpleEntry<>(model, property.getName())))
.collect(Collectors.groupingBy(
Entry::getValue,
Collectors.mapping(
Entry::getKey,
Collectors.toSet())));
Why not use forEach ?
Here is concise solution using forEach
Map<String, Set<Model>> resultMap = new HashMap<>();
listOfModels.forEach(currentModel ->
currentModel.getProperties().forEach(prop -> {
Set<Model> setOfModels = resultMap.getOrDefault(prop.getName(), new HashSet<>());
setOfModels.add(currentModel);
resultMap.put(prop.getName(), setOfModels);
})
);
I have a dynamic incoming JSON like shown below :
1st possibility
{
"key1":"value1",
"key2":"value2",
"key3":{
"inKey1":"inValue1",
"inKey2":"inValue2"
}
}
2nd possibility
{ "key1":"value1", "key2":"value2", "key3":[{
"inKey1":"inValue1",
"inKey2":"inValue2"
},{
"inKey1":"inValue3",
"inKey2":"inValue4"
}] }
The value of key3 is generally a map. But sometimes it can come as an array as well. I have to bind this JSON to a Bean and then proceed further. I am planning to write two beans, one with key3 as a map and the other with key3 as an array. I will check if value of key3 is an instance of map or an array and then bind to the corresponding bean. Is there any optimal way to get this task done with a single bean ? Please guide me.
Beans (which I havent written yet) would be something like :
public class Bean1{
private String key1;
private String key2;
private Map<String, String> key3 = new HashMap<String, String>();
}
public class Bean2{
private String key1;
private String key2;
private Map<String, String> key3[];
}
Deserialization of different json inputs with common properties can be done through generalization (Inheritance).
Define a parent bean : Define a parent bean with common properties
public class ParentBean {
protected String key1;
protected String key2;
public ParentBean(String key1, String key2) {
super();
this.key1 = key1;
this.key2 = key2;
}
// Setters and Getters
}
Define child beans : Define child beans with special properties
Bean1
public class Bean1 extends ParentBean {
private Map<String, String> key3;
public Bean1(String key1, String key2, Map<String, String> key3) {
super(key1, key2);
this.key3 = key3;
}
// Setters and Getters
}
Bean2
public class Bean2 extends ParentBean {
private Map<String, String> key3[];
public Bean2(String key1, String key2, Map<String, String>[] key3) {
super(key1, key2);
this.key3 = key3;
}
// Setters and Getters
}
Design a Deserializer : Since the framework is not specified, I have taken the liberty of using jackson framwork. Using jackson a Deserializer can be designed as follows:
public class ParentBeanDeserializer extends StdDeserializer<ParentBean>{
public ParentBeanDeserializer() {
this(null);
}
public ParentBeanDeserializer(Class<?> c) {
super(c);
}
#Override
public ParentBean deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException{
ParentBean pb;
JsonNode node = jp.getCodec().readTree(jp);
String value1 = node.findValue("key1").asText();
String value2 = node.findValue("key2").asText();
JsonNode node3 = node.findValue("key3");
ObjectMapper mapper = new ObjectMapper();
if(node3.isArray()){
String json3 = node3.toString();
Map<String, String>[] map = mapper.readValue(json3, Map[].class);
pb = new Bean2(value1,value2,map);
}
else{
String json3 = node3.toString();
Map<String, String> map = mapper.readValue(json3, Map.class);
pb = new Bean1(value1,value2,map);
}
return pb;
}
}
Usage : Beans/Classes mentioned above can be used as follows:
String json = getJasonResponseAsText();
ObjectMapper om = new ObjectMapper();
SimpleModule mod = new SimpleModule();
mod.addDeserializer(ParentBean.class, new ParentBeanDeserializer());
om.registerModule(mod);
ParentBean pb = om.readValue(json, ParentBean.class);
if (pb instanceof Bean1) {
Bean1 b1 = (Bean1)pb;
//Perform Bean1 related activites
}
else if (pb instanceof Bean2) {
Bean2 b2 = (Bean2)pb;
//Perform Bean2 related activites
}
I'm using org.json the main thing is to check whether the key is instanceof object or array. when you get to know the kind of the key. you can put your logic easily.
In the first case you have object then i simply typecast into object and fetch the values.
in the second case you have the array so i typecast into JSONArary and used a simple for loop to gather the elements.
Code:
package jsontest;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONObject;
/**
*
* #author Sahil
*/
class Model1{
String key1;
String key2;
List<Model2> key3;
Model1(){
key3 = new ArrayList<>();
}
#Override
public String toString() {
String s = "key1=" + key1 + ", key2=" + key2;
for(Model2 m: key3){
s+="\n"+"inKey1="+m.key1+", inkey2="+m.key2;
}
return s;
}
}
class Model2{
String key1;
String key2;
}
public class JSONTest {
static Model1 parse(String s){
JSONObject object = new JSONObject(s);
Model1 model = new Model1();
model.key1 = object.getString("key1");
model.key2 = object.getString("key2");
if (object.get("key3") instanceof JSONObject){
JSONObject key3 = object.getJSONObject("key3");
Model2 model2 = new Model2();
model2.key1 = key3.getString("inKey1");
model2.key2 = key3.getString("inKey2");
model.key3.add(model2);
}else{
JSONArray array = object.getJSONArray("key3");
for(int i=0;i<array.length();i++){
JSONObject row = array.getJSONObject(i);
Model2 model2 = new Model2();
model2.key1 = row.getString("inKey1");
model2.key2 = row.getString("inKey2");
model.key3.add(model2);
}
}
return model;
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
String firstCase = "{\r\n\"key1\":\"value1\",\r\n\"key2\":\"value2\",\r\n\"key3\":{\r\n \"inKey1\":\"inValue1\",\r\n \"inKey2\":\"inValue2\"\r\n }\r\n}";
String secondCase = "{ \"key1\":\"value1\", \"key2\":\"value2\", \"key3\":[{\r\n \"inKey1\":\"inValue1\",\r\n \"inKey2\":\"inValue2\"\r\n },{\r\n \"inKey1\":\"inValue3\",\r\n \"inKey2\":\"inValue4\"\r\n }] }";
System.out.println(parse(firstCase));
System.out.println("---------");
System.out.println(parse(secondCase));
}
}
Result:
key1=value1, key2=value2
inKey1=inValue1, inkey2=inValue2
---------
key1=value1, key2=value2
inKey1=inValue1, inkey2=inValue2
inKey1=inValue3, inkey2=inValue4