Best way to maintain key-value mapping pairs in Springboot Java - java

I want to know the best way to handle key-value mapping in a Springboot API.
The request of the API is as:
{
"a": "1",
"b": "b"
}
This API then calls a downstream API. But before it does that I need to convert the values of the fields a and b. The conversion would be as per:
For field a:
1 -> One,
2 -> Two,
3 -> TATA
for fields b:
b -> beta,
a -> alpha,
c -> c
Both of the above mappings are constant(wont change throughout) and hence need to be initialized and maintained in our API. Now once the mapping takes place the request to the downstream API will look like.
{
"a" : "One",
"b" : "beta"
}
i.e. 1 is mapped to One for field a, and b is mapped to beta for field b; based on the key value mapping mentioned above.
Need to know the best practice in Java and Spring boot to achieve this. How do I maintain the mapping table, initialize it just once and convert incoming API requests to corresponding values at runtime.
Can't use enum because some keys are numerical. If Hash Map and Hast table are my best bets, then how do I initialize them and maintain them throughout, since the mapping is constant don't want to create them again and again for each API call? Where do I read the key-value pairs from to initialize the hashmap/table?

You could keep your translations in code in a private static final field, and initialize it in a static initialization block:
private static final Map<String, Map<String, String>> TRANSLATIONS_PER_KEY = new HashMap<>();
static {
Map<String, String> translateForA = new HashMap<>();
translateForA.put("1", "One");
...
TRANSLATIONS_PER_KEY.put("a", translateForA);
...
}
Then you could do the translation using Guava Maps.transformEntries():
Map<String, String> translated =
Maps.transformEntries(input, (key, value) ->
TRANSLATIONS_PER_KEY.get(key)
.getOrDefault(value));
If you want to avoid NullPointers, and return the original value if there is no translation, you could do it like this:
Map<String, String> translated =
Maps.transformEntries(input, (key, value) ->
TRANSLATIONS_PER_KEY.getOrDefault(key, emptyMap())
.getOrDefault(value, value));

You may create enum for all mappings with pares
#AllArgsConstructor
#Getter
public enum AType {
ONE("One", "1"),
TWO("Two", "2"),
TATA("TATA", "3");
private String translation;
private String mapping;
public String of(String v) {
for (AType aType : values()) {
if (aType.getMapping().equals(v)) {
return translation;
}
}
throw new IllegalArgumentException("try again");
}
}
and new enum for b

Related

java groupBy and summarize on multiple field

I'm trying to use the Java summarize which I have just discovered and they are just perfect for my use case.
The only issue is that I can't make it working when I need to summarize on multiple field:
final Map<PackageType, LongSummaryStatistics> map2 = artifactory.getStorageInfo()
.getRepositorySummaries()
.stream()
.map(o -> RepositorySummaryValue.from(o))
.collect(groupingBy(
k -> k.getPackageType(),
summarizingLong(k -> k.filesCount)
));
this is my RepositorySummaryValue class:
#lombok.Value
#Builder(builderClassName = "Builder")
private static class RepositorySummaryValue {
long filesCount;
#NonNull
PackageType packageType;
#NonNull
String key;
#NonNull
RepositoryType type;
long usedSpaceBytes;
#SneakyThrows
static RepositorySummaryValue from(RepositorySummary source) {
return builder()
.filesCount(source.getFilesCount())
.packageType(source.getPackageType())
.key(source.getKey())
.type(source.getType())
.usedSpaceBytes(source.getUsedSpaceBytes())
.build();
}
}
What I want is to get summarise also for summarizingLong(k -> k.usedSpaceBytes)
Any way for doing it?
=========EDIT============
I'm using Java 8
Here's an idea that with a little setup would allow any number of fields of some type T to be summarized. It may give you some ideas. It makes use of the compute* features of a map introduced in Java 8.
I used a record in lieu of a class to hold the data.
first, set up a list of method references to get the values you want.
then, initialize another list with the titles of names of those values (here I used your names but the data is just for demo). Note: There are many ways to do this. A single list with a record holding the name and method reference would be another way.
create a map of maps to house the results. The key to the outer map is the packageType, the key to the inner map is the name of the field you are summarizing for each packageType.
Now simply iterate over the list of data, and build the map. The details of how the compute* methods work are explained in the Map Interface JavaDoc but here is a quick summary.
computeIfAbsent will evaluate its second argument if the supplied key is not there. That second argument (here it's the inner map) is returned for access. In this case, another computeIfAbsent is used to see if that map has a key for the field. If not it adds it and creates a LongSummaryStatistic instance wit the key for the field name. That instance is also made available for access. The data item is then accepted and the statistics updated.
public class Summarizing {
record Data(String getPackageType, long getFilesCount,
long getUsedSpaceBytes) {
}
static List<Function<Data, Long>> summaryFields = List
.of(Data::getFilesCount, Data::getUsedSpaceBytes);
static List<String> names = List.of("filesCount", "usedSpaceBytes");
public static void main(String[] args) {
List<Data> list = List.of(new Data("foo", 10, 100),
new Data("bar", 20, 200),
new Data("foo", 30, 300),
new Data("bar", 40, 400));
Map<String, Map<String, LongSummaryStatistics>> result = new HashMap<>();
for (Data d : list) {
Map<String, LongSummaryStatistics> innerMap = result
.computeIfAbsent(d.getPackageType(),
v -> new HashMap<>());
for (int i = 0; i < summaryFields.size(); i++) {
innerMap.computeIfAbsent(names.get(i),
v -> new LongSummaryStatistics())
.accept(summaryFields.get(i)
.apply(d));
}
}
result.entrySet().forEach(e-> {
System.out.println(e.getKey());
for (Entry<?,?> ee : e.getValue().entrySet()) {
System.out.println(" " + ee);
}
});
}
}
prints
bar
filesCount=LongSummaryStatistics{count=2, sum=60, min=20, average=30.000000, max=40}
usedSpaceBytes=LongSummaryStatistics{count=2, sum=600, min=200, average=300.000000, max=400}
foo
filesCount=LongSummaryStatistics{count=2, sum=40, min=10, average=20.000000, max=30}
usedSpaceBytes=LongSummaryStatistics{count=2, sum=400, min=100, average=200.000000, max=300}

Assemble values from Map using Java streams

Sorry maybe for dumb question. I am looking for elegant way to go over elements of my map and filter properties.
Let's say I have map with two elements.
Map<String, MyElement> myMap;
This is how looks my element
class MyElement {
Map <String, Property1> properties1;
Map <String, Property2> properties2;
}
MyElement[0] includes properties1 map filled with some properties, and properties2 is null.
MyElement[1] includes properties2 map filled with some properties, and properties1 is null.
It might be vise versa, I have no idea for which MyElelmet Internal Maps are null and for which are not.
I would like to go over each MyElement in map and assemble properties1 or properties2 from each element in case if it is not empty.
Result should be two separate maps (new collections)
Map <String, Property1> assembledProperties1;
Map <String, Property2> assembledProperties2;
You can think about it as a collecting results to multiple outputs (assembledProperties1, assembledProperties2).
Is there any elegant way to do it with Java streams, without ugly if statements?
Since don't want to utilize MyElement as a mutable container, you can define a special type of object that will carry references to the maps of properties.
In order to be able to perform mutable reduction on a stream of type MyElement with this object we need to define a method that will expect MyElement as a parameter to update maps based on the next element of the stream, and another method that is needed to merge partial results of execution in parallel (i.e. to combine the two objects).
public class PropertyWrapper {
private Map<String, Property1> properties1 = new HashMap<>();
private Map<String, Property2> properties2 = new HashMap<>();
public PropertyWrapper merge(MyElement element) {
if (element.getProperties1() != null) properties1.putAll(element.getProperties1());
if (element.getProperties2() != null) properties2.putAll(element.getProperties2());
return this;
}
public PropertyWrapper merge(PropertyWrapper other) {
this.properties1.putAll(other.getProperties1());
this.properties2.putAll(other.getProperties2());
return this;
}
// getters and toString()
}
With that, the actual code might look like that:
public static void main(String[] args) {
Map<String, MyElement> sourceMap =
Map.of("key1", new MyElement(Map.of("a", new Property1("a"), "b", new Property1("b")), null),
"key2", new MyElement(null, Map.of("c", new Property2("c"), "d", new Property2("d"))));
PropertyWrapper result = sourceMap.values().stream()
.collect(
PropertyWrapper::new,
PropertyWrapper::merge,
PropertyWrapper::merge);
System.out.println(result.getProperties1());
System.out.println(result.getProperties2());
}
Output
{a=Property1{a}, b=Property1{b}}
{d=Property2{d}, c=Property2{c}}
Also note that it's a good practice to avoid keeping nullable references to collections. If these fields will always be initialized with empty collection, the need of null-check will be eliminated.

java - to set multiple value in a map

I got a scenario like the following:
Map1 - Map<String, Map<String,List<Vo>>>
Map2 - Map<String, Set<String>
Is it possible to set the same have a same key reference for the above 2 Maps like the following?
Map<String, Collection<?> mapCommon=new HashMap<String, Collection<?>();
Can anyone please give some idea about how to set this?
edit: yes same reference
You are touching here two interesting elements.
Firstly - Map does not belong to Collection. List and Set do belong, but Map is a different one even though it shares some commonalities with Lists and Sets.
Secondly - Mixing the types into one commonMap the way you are trying is doable but it should be avoided as it is generally not considered as best practice. The problem we are dealing with is caused by type erasure. Once compiler compiles the code - it does not pass any information about generic types hold by Map or Set. Effectively your Map<String, List<Vo>> becomes raw-type Map<?> in the compiled code. The problem with that is casting back original values. The compiler will not allow you to check the instance if it is Map<String, List<Vo>> or Set<String>.
The fllowing piece of code will fail:
public static void processElement(Object commonMapObjectEitherMapOrSet) {
if (commonMapObjectEitherMapOrSet instanceof Map<String, List<Vo>>) {
//...
}
}
Error: Cannot perform instanceof check against parameterized type
Map>. Use the form Map instead since further
generic type information will be erased at runtime
The possible workaround would be to forget about generics and check if the instance is a raw-type Set or Map. The code below shows how check if Object is either Map or Set.
public static void processElement(Object commonMapObjectEitherMapOrSet) {
if (commonMapObjectEitherMapOrSet instanceof Map) {
System.out.println("Got map; but types held in the map are not known due to type-erasure");
// This is where things will get messy as you will get warnings:
Map<String, List<Vo>> map = (Map<String, List<Vo>>) commonMapObjectEitherMapOrSet;
// ...
}
if (commonMapObjectEitherMapOrSet instanceof Set) {
System.out.println("Got set; but types held in the set are not known due to type-erasure");
// This is where things will get messy as you will get warnings:
Set<String> set = (Set<String>) commonMapObjectEitherMapOrSet;
// ...
}
}
The problem with the above is casting the value from your commonMap back to your desired types ie. Map<String, List<Vo>> and Set<String>. The compiler won't be able to check if the casting is correct and will issue a warning. You can technically Suppress the warning with (#SuppressWarnings("unchecked") annotation ) but this may not be the best thing to do.
At this stage - it makes sense to consider whether or not to create your own specialized class to manage different types.
Back to your original question - to answer it I am posting the code that maps things to the common map:
package stackoverflow;
import java.util.*;
class Vo {}
public class MultipleRefs {
public static void main(String[] args) {
Map<String, List<Vo>> mapVo = new HashMap<>();
Set<String> set = new HashSet<>();
Map<String, Object> commonMap = new HashMap<>();
//commonMap.put("a", Map)
commonMap.put("mapVoOne", mapVo);
commonMap.put("setOne", set);
commonMap.forEach((key, value) -> processElement(value));
}
public static void processElement(Object commonMapObject) {
if (commonMapObject instanceof Map) {
System.out.println("Got map; but types held in the map are not known due to type-erasure");
// This is where things will get messy:
Map<String, List<Vo>> map = (Map<String, List<Vo>>) commonMapObject;
System.out.println(" processElement prints map: " + map);
}
if (commonMapObject instanceof Set) {
System.out.println("Got set; but types held in the set are not known due to type-erasure");
// This is where things will get messy:
Set<String> set = (Set<String>) commonMapObject;
System.out.println(" processElement prints set: " + set);
}
}
}
If I understand you would want to have the same key to be used for various different types of values.
Why not have a new Class itself that would consists of maps, sets, whose instances could be used as values
class MyClass {
private Map<String, List<Vo>> theMap;
private Set<String> theSet;
...
... // have its own getters and setters
}
And then you can have your top level map defined like this
Map<String, MyClass> myMainMap = new HashMap<String, MyClass>();
Or as an alternative have a tuple
You can check this link further to see how that is done.
What you want to do is impossible because Set and Map do not share any common implementation or super class except Object. You can see it in the official documentation :
Javadoc Map
Javadoc Set
You could do a Map<String, Object> but I strongly not advise you to doing that. How could you know if your object is a map or a set ? It is not possible to do that properly.
In my opinion, the best solution you have is to create a new class to wrap your two collections :
public class YourWrapper {
Map<String, Map<String,List<Vo>>> a;
Map<String, Set<String> b;
// getter setter etc...
}
After that you can create your collection :
Map<String, YourWrapper> myMap = new HashMap<String, YourWrapper>();

Java 8 Map KeySet Stream not working as desired for use in Collector

I have been trying to learn Java 8's new functional interface features, and I am having some difficulty refactoring code that I have previously written.
As part of a test case, I want to store a list of read names in a Map structure in order to check to see if those reads have been "fixed" in a subsequent section of code. I am converting from an existing Map> data structure. The reason why I am flattening this datastructure is because the outer "String" key of the original Map is not needed in the subsequent analysis (I used it to segregate data from different sources before merging them in the intermediate data). Here is my original program logic:
public class MyClass {
private Map<String, Map<String, Short>> anchorLookup;
...
public void CheckMissingAnchors(...){
Map<String, Boolean> anchorfound = new HashMap<>();
// My old logic used the foreach syntax to populate the "anchorfound" map
for(String rg : anchorLookup.keySet()){
for(String clone : anchorLookup.get(rg).keySet()){
anchorfound.put(clone, false);
}
}
...
// Does work to identify the read name in the file. If found, the boolean in the map
// is set to "true." Afterwards, the program prints the "true" and "false" counts in
// the map
}
}
I attempted to refactor the code to use functional interfaces; however, I getting errors from my IDE (Netbeans 8.0 Patch 2 running Java 1.8.0_05):
public class MyClass {
private Map<String, Map<String, Short>> anchorLookup;
...
public void CheckMissingAnchors(...){
Map<String, Boolean> anchorfound = anchorLookup.keySet()
.stream()
.map((s) -> anchorlookup.get(s).keySet()) // at this point I am expecting a
// Stream<Set<String>> which I thought could be "streamed" for the collector method
// ; however, my IDE does not allow me to select the "stream()" method
.sequential() // this still gives me a Stream<Set<String>>
.collect(Collectors.toMap((s) -> s, (s) -> false);
// I receive an error for the preceding method call, as Stream<Set<String>> cannot be
// converted to type String
...
}
}
Is there a better way to create the "anchorfound" map using the Collection methods or is the vanilla Java "foreach" structure the best way to generate this data structure?
I apologize for any obvious errors in my code. My formal training was not in computer science but I would like to learn more about Java's implementation of functional programming concepts.
I believe what you need is a flatMap.
This way you convert each key of the outer map to a stream of the keys of the corresponding inner map, and then flatten them to a single stream of String.
public class MyClass {
private Map<String, Map<String, Short>> anchorLookup;
...
public void CheckMissingAnchors(...){
Map<String, Boolean> anchorfound = anchorLookup.keySet()
.stream()
.flatMap(s -> anchorlookup.get(s).keySet().stream())
.collect(Collectors.toMap((s) -> s, (s) -> false);
...
}
}
Eran's suggestion of flatMap is a good one, +1.
This can be simplified somewhat by using Map.values() instead of Map.keySet(), since the map's keys aren't used for any other purpose than to retrieve the values. Streaming the result of Map.values() gives a Stream<Map<String,Short>>. Here we don't care about the inner map's values, so we can use keySet() to extract the keys, giving a Stream<Set<String>>. Now we just flatMap these sets into Stream<String>. Finally we send the results into the collector as before.
The resulting code looks like this:
public class MyClass {
private Map<String, Map<String, Short>> anchorLookup;
public void checkMissingAnchors() {
Map<String, Boolean> anchorfound = anchorLookup.values().stream()
.map(Map::keySet)
.flatMap(Set::stream)
.collect(Collectors.toMap(s -> s, s -> false));
}
}

Is there any better solution than three nested Maps that preserves the same functionality?

Here is the situation
private Map<String, Map<String, Map<String, String>>> properties;
And I want to have access to all levels - get Map<String, Map<String, String>>, Map<String, String> or simply just String from the inner map.
Can this be done in a better way to avoid this nested structure? Creating a wrapper which would hide the implementation and provide simple methods is the obvious solution but it just hides the main problem.
I like the Key approach.
public class Key {
private String keyA;
private String keyB;
private String keyC;
public Key(String a, String b, String c) {
keyA = "".equals(a) ? null : a;
keyB = "".equals(b) ? null : b;
keyC = "".equals(c) ? null : c;
}
public String getKey(){
return keyA + keyB + keyC;
}
// equals() can be implemented using getKey()
}
And then:
Map<Key, String> map = new HashMap<Key, String>();
map.put(new Key("a", "",""), "only one key");
map.put(new Key("a", "b", "c"), "all keys");
Do notice that you'll only need one map, and can still get an object with only keyA since it will have a different key value.
Of course, if you want to store multiple objects with the same index (or recover multiple objects that start with the same key beginning, as in recover Key("a", "a", "a") and Key("a", "a", "b) when searching for Key("a", "a", "") it will not work. But then, you should not be using a Map anyway, and should probably get a proper database.
You might consider having a look at Google Guavas Collection classes - maybe you can find a suitable implementation that you could use "out-of-the-box" instead of rolling your own?
A clean solution (although initially more cumbersome) is to normalize your data, and load them into an in-memory database. Then, using JPA, you can get advantage of all the OneToMany relationships and get collections of objects that share one or more keys.

Categories