Test content of a collection attribute from a collection of objects - java

Sorry for this title that I try to make as clear as possible. Don't hesitate to edit to impove it.
My problem is that I would like to test the content of this structure and more specialy the content of B objects and assure that it correspond to specific A values :
public class A {
String key;
List<B> bs;
}
And my data have this form
List<A> as = [
{
key : "KEY1",
bs: [
{val1:"val1", val2:"val2}
]
},
{
key : "KEY2",
bs: [
{val1:"val3", val2:"val4"},
{val1:"val5", val2:"val6"}
]
},
];
In the context of Unit testing with Mockito, I would like to be able to test this structure without doing a pre treatment to get the List of B. By testing this structure I would like to be sure that there are two B for KEY2 and that first B has val3 and val4, the second val5 and val6.
At the moment, I have to create a map by key and test every entry. I would like to it in a more straightforward way if any exist.
Here is my actual test.
List<A> as = captor.getAllValues();
assertThat(as)
.isNotNull()
.hasSize(2)
.extracting("key")
.containsOnlyOnce(
tuple("KEY1"),
tuple("KEY2")
);
Map<String, A> estimationParPlateforme = indexBy(as, new Indexer<String, A>() {
#Override
public String apply(A a) {
return a.getKey();
}
});
assertThat(as.get("KEY1").getBs())
.isNotEmpty()
.extracting(
"val1",
"val2"
)
.containsExactly(
tuple(
"val1",
"val2"
)
);
assertThat(as.get("KEY2").getBs())
.isNotEmpty()
.extracting(
"val1",
"val2"
)
.containsExactly(
tuple(
"val3",
"val4"
),
tuple(
"val5",
"val6"
)
);
I think that it is a bit long for a test, and I would like to find a way to improve this. Do you have any solution?
Thanks for any help

You have to define how your datastructure should look. At the moment you have all this information scattered in your test statements. That makes it very ugly as you allready figured. What i like to do is to have some datastructure, that i can fill with data, that i am expecting. Then i can use this, to test, whether my data is valid or not. An example:
static Map<String, B[]> expectedBs = new HashMap<String, B[]>();
public static void main(String[] args) {
//put the expected values into a map
expectedBs.put("KEY1", new B[] { new B("val1", "val2") });
expectedBs.put("KEY2", new B[] { new B("val3", "val4"), new B("val5", "val6") });
//build a valid datastructure
List<A> as = new ArrayList<A>();
List<B> bs1 = new ArrayList<B>();
bs1.add(new B("val1", "val2"));
List<B> bs2 = new ArrayList<B>();
bs2.add(new B("val3", "val4"));
bs2.add(new B("val5", "val6"));
as.add(new A("KEY1", bs1));
as.add(new A("KEY2", bs2));
//test validity of as
System.out.println(isValid(as)); //prints true
//modify the datastructure such that it is not valid anymore
bs1.get(0).val1 = "val2";
//test validity once more
System.out.println(isValid(as)); //prints false
}
static boolean isValid(List<A> as) {
for (A a : as) {
B[] expected = expectedBs.get(a.key);
if (!equals(a.bs, expected))
return false;
}
return true;
}
static boolean equals(List<B> x, B[] y) {
if (x == null)
return false;
if (y == null)
return false;
if (x.size() != y.length)
return false;
for (int i = 0; i < x.size(); i++)
if (!x.get(i).equals(y[i]))
return false;
return true;
}
A:
public class A {
String key;
List<B> bs;
public A(String key, List<B> bs) {
this.key = key;
this.bs = bs;
}
}
B:
public class B {
String val1, val2;
public B(String val1, String val2) {
this.val1 = val1;
this.val2 = val2;
}
#Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (!(obj instanceof B))
return false;
B objB = (B) obj;
if (!objB.val1.equals(this.val1))
return false;
if (!objB.val2.equals(this.val2))
return false;
return true;
}
}
Sorry for the code beeing so long. Hope you get the idea. Whether this is more elegant or not is up to you to decide.

Related

Serialize HashMap with object keys with Gson

I am writing some code to serialize a neural network system I have developed. This system has a "database" that keeps track of the evolution of the neural networks, and it does so by storing the ID of each gene in a HashMap with a GeneKey, which is a record containing the ID of the gene before and the ID of the gene after the gene we're storing.
A HashMap with some data looks like this:
existingNodes = {
GeneKey[a=0, b=3] = 4,
GeneKey[a=1, b=4] = 5
}
Everything in the system serializes fine, except this HashMap, because Json can only have numbers and strings as its keys, and in my HashMap I'm using objects for the keys. Is there an easy way to serialize this to json using Gson?
Edit: This is how the HashMap is constructed:
HashMap<GeneKey, Integer> existingNodes = new HashMap<>();
existingNodes.put(new GeneKey(0, 3), 4);
existingNodes.put(new GeneKey(1, 4), 5);
System.out.println("existingNodes = "+registry);
//existingNodes = {
// GeneKey[a=0, b=3] = 4,
// GeneKey[a=1, b=4] = 5
//}
This is the GeneKey class:
public record GeneKey(int a, int b) {}
It would be helpful if you could give the JSON string which you got after serializing using Gson.
However, please check the following code if this solves your issue.
Define GenKey class with overriden hashCode and equals methods as:
public class GeneKey {
private int a;
private int b;
public GeneKey(int a, int b) {
this.a = a;
this.b = b;
}
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public int getB() {
return b;
}
public void setB(int b) {
this.b = b;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + a;
result = prime * result + b;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
GeneKey other = (GeneKey) obj;
if (a != other.a)
return false;
if (b != other.b)
return false;
return true;
}
#Override
public String toString() {
//return "GeneKey [a=" + a + ", b=" + b + "]";
// Updated for deserialization using Gson
Gson gson = new
GsonBuilder().serializeNulls().disableHtmlEscaping().create();
return gson.toJson(this);
}
}
Now try to convert the HashMap with GenKey as key into JSON String using Gson :
HashMap<GeneKey, Integer> existingNodes = new HashMap<>();
existingNodes.put(new GeneKey(0, 3), 4);
existingNodes.put(new GeneKey(1, 4), 5);
Gson gson = new
GsonBuilder().serializeNulls().disableHtmlEscaping().create();
System.out.println(gson.toJson(existingNodes));
This is the output which I received in my console:
{"{\"a\":0,\"b\":3}":4,"{\"a\":1,\"b\":4}":5}
Updating the answer to add deserialization logic if required:
//Say, we pass the serialized JSON as a payload to some REST API. We can deserialize the Key of the response as follows -
#PostMapping("/getJson")
public Map<GeneKey, Integer> getJson(#RequestBody Map<String, Integer> response) {
final Gson gson = new Gson();
Map<GeneKey, Integer> deserializedMap = new HashMap<GeneKey,
Integer>();
response.keySet().forEach( k -> {
GeneKey key = gson.fromJson(k, GeneKey.class);
deserializedMap.put(key, response.get(k));
});
System.out.println(deserializedMap);
return deserializedMap;
}
GsonBuilder#enableComplexMapKeySerialization
However, now the map is represented by an array of array of entries.
see Gson Serializing HashMap<Teacher, List<Student>>

Java Jackson- Compare two JSONs ignoring order of keys and elements in arrays

I have two JSON strings which I want to compare.
I want neither the order of the keys to matter or the order of elements in an array.
However I do want an extra field to be considered "not equal"
Non strict mode with JSONAssert seems like it fits the bill except for an extra field being considered equal "http://jsonassert.skyscreamer.org/cookbook.html"
If at all possible I would like to avoid pulling in extra dependancies. I already have jackson in my project
I have 2 ideas how to do it.
Is to write java objects and serialize it, and write own equals method.
Is to serialize it to Map<Object, Object> and compare 2 map.
String json1 = "{...}"
String json2= "{...}"
Object json1Object = objectMapper.readValue(json1, Object.class);
Object json2Object = objectMapper.readValue(json2, Object.class);
Assertions.assertEquals(json1Object, json2Object);
Assertions.assertTrue(json1Object.equals(json2Object));
So you probably have only one option. Write own comparator.
My quick solution:
#Test
public void comparingJsonTest4() throws JsonProcessingException {
String json1 = "{\"id\": 1, \"name\": \"test\", \"cars\": [\"Ford\", \"BMW\", \"Fiat\"]}";
String json2 = "{\"name\": \"test\", \"id\": 1, \"cars\": [\"BMW\", \"Ford\", \"Fiat\"]}";
ObjectMapper objectMapper = new ObjectMapper();
JsonNode json1Node = objectMapper.readTree(json1);
JsonNode json2Node = objectMapper.readTree(json2);
Assertions.assertEquals(0, new ComparatorWithoutOrder().compare(json1Node, json2Node));
}
class ComparatorWithoutOrder implements Comparator<JsonNode> {
#Override
public int compare(JsonNode o1, JsonNode o2) {
if(o1 == o2) {
return 0;
}
if(o1.getClass() != o2.getClass()) {
return -1;
}
if(o1.getClass() == ObjectNode.class) {
List<String> o1FieldNames = new ArrayList<>();
o1.fieldNames().forEachRemaining(o1FieldNames::add);
List<String> o2FieldNames = new ArrayList<>();
o2.fieldNames().forEachRemaining(o2FieldNames::add);
if(o1FieldNames.size() != o2FieldNames.size()) {
return -1;
}
if(!o2FieldNames.containsAll(o1FieldNames) || !o1FieldNames.containsAll(o2FieldNames)) {
return -1;
}
for (String o1FieldName : o1FieldNames) {
if (!(compare(o1.get(o1FieldName), o2.get(o1FieldName)) == 0)) {
return -1;
}
}
return 0;
}
if(o1.getClass() == ArrayNode.class) {
List<JsonNode> o1Children = new ArrayList<>();
o1.elements().forEachRemaining(o1Children::add);
List<JsonNode> o2Children = new ArrayList<>();
o2.elements().forEachRemaining(o2Children::add);
if(o1Children.size() != o2Children.size()) {
return -1;
}
for (JsonNode c1 : o1Children) {
boolean found = false;
for (JsonNode c2 : o2Children) {
if (compare(c1, c2) == 0) {
found = true;
break;
}
}
if (!found) {
return -1;
}
}
return 0;
}
return o1.equals(o2) ? 0 : -1;
}
}
At the beginning I wanted to use something like this:
json1Node.equals(new ComparatorWithoutOrder(), json2Node);
but thre was a problem to propoer handle ArrayNode inside ObjectNode. So if you want, you could skip implements Comparator<JsonNode>, because finally I don't use this functionality.

Grouping elements that each group contains only one object with specified field

I have a problem with grouping java objects. Let's look at example object:
public class MyObject {
private String field1;
public MyObject(String field1) {
this.field1 = field1;
}
}
What i want to achieve is grouping MyObject's in such a way that each group contains only one object with specified field1 value. For example, for such list of elements:
public static void main(String[] args) {
MyObject o1 = new MyObject("1");
MyObject o2 = new MyObject("1");
MyObject o3 = new MyObject("1");
MyObject o4 = new MyObject("2");
MyObject o5 = new MyObject("2");
MyObject o6 = new MyObject("3");
List<MyObject> list = Arrays.asList(o1, o2, o3, o4, o5, o6);
List<List<MyObject>> listsWithUniqueField1Values = new ArrayList<>();
I want to get listsWithUniqueField1Values looks like that:
[
[
MyObject{field1='1'},
MyObject{field1='2'},
MyObject{field1='3'}
],
[
MyObject{field1='1'},
MyObject{field1='2'}
],
[
MyObject{field1='1'}
]
]
I've tried to acheive it in effective way with using java.util.stream.Collectors.groupingBy method, but i faild.
I don't think you can do with it with groupingBy. Here is my solution - I also added an autogenerated equals, hashCode, and toString
public class SO67140234 {
public static void main(String[] args) {
MyObject o1 = new MyObject("1");
MyObject o2 = new MyObject("1");
MyObject o3 = new MyObject("1");
MyObject o4 = new MyObject("2");
MyObject o5 = new MyObject("2");
MyObject o6 = new MyObject("3");
List<MyObject> list = Arrays.asList(o1, o2, o3, o4, o5, o6);
List<Set<MyObject>> listsWithUniqueField1Values = new ArrayList<>();
outer:
for (MyObject obj : list) {
for (Set<MyObject> bucket : listsWithUniqueField1Values) {
if (bucket.add(obj)) {
continue outer;
}
}
listsWithUniqueField1Values.add(new HashSet<>(Collections.singleton(obj)));
}
System.out.println(listsWithUniqueField1Values);
}
}
class MyObject {
private final String field1;
public MyObject(String field1) {
this.field1 = field1;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyObject myObject = (MyObject) o;
return Objects.equals(field1, myObject.field1);
}
#Override
public int hashCode() {
return Objects.hash(field1);
}
#Override
public String toString() {
return "MyObject{" +
"field1='" + field1 + '\'' +
'}';
}
}
In order to group by instances of MyObject, this class needs to implement equals and hashCode methods, also field1 should be final to avoid corruption of hashCode upon changing its value.
public class MyObject {
private final String field1;
public MyObject(String field1) {
this.field1 = field1;
}
public String getField1() {return this.field1;}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (null == o || !(o instanceof MyObject)) return false;
MyObject that = (MyObject) o;
return Objects.equals(this.field1, that.field1);
}
#Override
public int hashCode() {
return Objects.hash(this.field1);
}
#Override
public String toString() {
return "field1=" + this.field1;
}
}
Collectors.groupingBy cannot be used to obtain the required result, but a custom Stream::collect operation may be applied to create a list of sets of unique MyObject instances (somewhat reminding #Rubydesic's solution but without nested loop).
List<MyObject> list = Arrays.asList(o1, o4, o5, o2, o6, o3);
List<Set<MyObject>> result = list.stream()
.collect(
ArrayList::new, // `Supplier<ArrayList<Set<>>>`
(lst, x) -> { // accumulator
for (Set<MyObject> set : lst) {
if (set.add(x)) {
return; // found a bucket to place MyObject instance
}
}
// create new bucket
Set<MyObject> newSet = new HashSet<>();
newSet.add(x);
lst.add(newSet);
},
(lst1, lst2) -> {} // empty combiner
);
System.out.println(result);
Output :
[[field1=1, field1=2, field1=3], [field1=1, field1=2], [field1=1]]
Assuming MyObject has a getter, one of the easiest way I can think of is to combine
Collectors.collectingAndThen
Collectors.groupingBy
A LinkedList
A method popping items from the LinkedList and inserting them inside of the result
List<List<MyObject>> finalResult = list.stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(MyObject::getField1, Collectors.toCollection(LinkedList::new)),
map -> {
List<List<MyObject>> result = new ArrayList<>();
Collection<LinkedList<MyObject>> values = map.values();
while (!values.isEmpty()) {
List<MyObject> subList = values.stream()
.map(LinkedList::pop)
.toList();
result.add(subList);
values.removeIf(LinkedList::isEmpty);
}
return result;
}));
The result is
[
[
MyObject{field1='1'},
MyObject{field1='2'},
MyObject{field1='3'}
],
[
MyObject{field1='1'},
MyObject{field1='2'}
],
[
MyObject{field1='1'}
]
]
You could do this using a groupingBy itself. (Without the need of equals or hashCode)
First group using field1. This would give a map as:
{ 1 : [1,1,1], 2 : [2,2], 3 : [3] }
Now for each of these keys, iterate their respective lists and add each MyObject to a different list in listsWithUniqueField1Values.
a. First processing for key 1, the list becomes [[1]] -> [[1], [1]] -> [[1], [1], [1]].
b. Then key 2, the list becomes [[1,2], [1], [1]] -> [[1,2], [1,2], [1]].
c. The for key 3, the list becomes [[1,2,3], [1,2], [1]].
Code :
List<List<MyObject>> uniqueList = new ArrayList<>();
list.stream()
.collect(Collectors.groupingBy(MyObject::getField1))
.values()
.stream()
.forEach(values -> addToList(uniqueList, values));
return uniqueList;
The below method addToList is where the unique list is populated.
ListIterator is used over Iterator in this case, as add method is available in ListIterator.
private static void addToList(List<List<MyObject>> uniqueList, List<MyObject> values) {
ListIterator<List<MyObject>> iterator = uniqueList.listIterator();
for (MyObject o : values) {
List<MyObject> list;
if (!iterator.hasNext()) {
// the object needs to be added to a new list.
list = new ArrayList<>();
iterator.add(list);
} else {
list = iterator.next();
}
list.add(o);
}
}

Can we design a generic function where both integer and string addition to be done?

class generic<T> {
T a;
T b;
generic(T a, T b) {
this.a = a;
this.b = b;
}
public T sum() {
return (a+b);
}
}
//can er design this as it takes a input both as integer and string and //give the append result as the same return type.
Not like that but you could do something like this:
public class Adding{
public static void main(String []args){
String string = "12";
Integer integer = 20;
System.out.println(add(string, integer));
}
private static int add (Object a, Object b) {
return toInt(a) + toInt(b);
}
private static int toInt (Object obj) {
if (obj.getClass() == String.class) {
return Integer.parseInt((String)obj);
} else if (obj.getClass() == Integer.class) {
return (Integer) obj;
} else {
//throw an exception
return 0;
}
}
Edit: you could also use your generic types here too
You don't need to write one. Just use the existing one like so.
BiFunction<String, String, String> biStr = (s1, s2) -> s1 + s2;
BiFunction<Integer, Integer, Integer> biInt = (n1, n2) -> n1 + n2;
System.out.println(biStr.apply("One", "two"));
System.out.println(biInt.apply(10, 6));
You can use the instanceof operator.
You can check for the type of T by asking if instance variable a or b is an instance of String or Integer and make a decision accordingly.
class Generic<T>
{
T a;
T b;
Generic(T a,T b) {
this.a = a;
this.b = b;
}
public T sum() {
if (a instanceof String && b instanceof String) {
// string concatenation e.g. return a + b + "\n";
} else if (a instanceof Integer && b instanceof Integer) {
// integer addition e.g. return a + b;
}
return null;
}
}
Note that you'd have to use class types and not primitive types while creating an object of Generic
More notably, you may be able to design the components of your implementation in a better way than to use this Generic class. (Perhaps, inheritance?)

Java 8 Grouping by Multiple Fields into Single Map

I have a class like this:
public class Foo {
private String a;
private String b;
private LocalDate c;
private int d;
}
I have a list of Foo objects that I want to group by a, b, and c and produce a map. Here's what I have so far:
Map<String, List<Foo>> test = foos.stream().collect(Collectors.groupingBy(Foo::getA, Collectors.collectingAndThen(Collectors.groupingBy(Foo::getB), Collections.unmodifiableList())));
But that itself is wrong. I don't know how to groupby multiple fields but still produce a Map<String, List<Foo>>. Any ideas what I'm doing wrong?
Edit 1: If I have the following Foo's:
{"Test", "Test", "10/02/2015", 5}
{"Test", "Test", "10/02/2015", 4}
{"Test", "Test", "10/02/2015", 3}
{"Test", "Test", "2/02/2015", 5}
{"Test", "Potato", "2/02/2015", 5}
Then it should group to:
{"Test", "Test", "10/02/2015", [5, 4, 3]}
{"Test", "Test", "2/02/2015", 5}
{"Test", "Potato", "2/02/2015", 5}
My original post was misleading in what exactly I wanted but basically it needs to group by a, b, d and produce a list of d. I know I'll probably have to create a new class to store them in like so:
public class FooResult {
private String a;
private String b;
private LocalDate c;
private List<Integer> d;
}
How can I group and map to a new class like shown above?
As a group by multiple fields is not implemented you have to use a composite key consisting of values from a, b and c. With that key the collect operation can be used like this with the Collector#of() factory method.
Map<String, List<Integer>> result = foos.stream().collect(Collector.of(
HashMap::new,
( map, foo ) -> {
map.compute(foo.a + "_" + foo.b + "_" + foo.c, (key,list) -> {
if(list == null){
list = new ArrayList<>();
}
list.add(foo.d);
return list;
});
},
( map1, map2 ) -> {
map2.forEach(( k, v ) -> {
map1.compute(k, (key, list) -> {
if(list == null){
list = v;
} else {
list.addAll(v);
}
return list;
});
});
return map1;
}
));
You can also use intermediate Map with a key that aggregates fields a, b and c from Foo class and List<Integer> value that collects all d field values.. In below example I have created MapKey class - a helper class that aggregates those fields and implements hashCode and equals methods so it can be used as a key in a HashMap.
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FooMain {
public static void main(String[] args) {
final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("MM/dd/yyyy");
final List<Foo> foos = Arrays.asList(
new Foo("Test", "Test", LocalDate.parse("10/02/2015", dateFormat), 5),
new Foo("Test", "Test", LocalDate.parse("10/02/2015", dateFormat), 4),
new Foo("Test", "Test", LocalDate.parse("10/02/2015", dateFormat), 3),
new Foo("Test", "Test", LocalDate.parse("02/02/2015", dateFormat), 5),
new Foo("Test", "Potato", LocalDate.parse("02/02/2015", dateFormat), 5)
);
List<FooResult> result = foos.stream()
.collect(Collectors.groupingBy(foo -> new MapKey(foo.a, foo.b, foo.c), Collectors.mapping(Foo::getD, Collectors.toList())))
.entrySet()
.stream()
.map(entry -> new FooResult(entry.getKey().a, entry.getKey().b, entry.getKey().c, entry.getValue()))
.collect(Collectors.toList());
result.forEach(System.out::println);
}
public static final class Foo {
private final String a;
private final String b;
private final LocalDate c;
private final int d;
Foo(String a, String b, LocalDate c, int d) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
}
int getD() {
return d;
}
}
public static final class FooResult {
private final String a;
private final String b;
private final LocalDate c;
private final List<Integer> d;
FooResult(String a, String b, LocalDate c, List<Integer> d) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
}
#Override
public String toString() {
return "FooResult{" +
"a='" + a + '\'' +
", b='" + b + '\'' +
", c=" + c +
", d=" + d +
'}';
}
}
public static final class MapKey {
private final String a;
private final String b;
private final LocalDate c;
MapKey(String a, String b, LocalDate c) {
this.a = a;
this.b = b;
this.c = c;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MapKey)) return false;
MapKey mapKey = (MapKey) o;
if (a != null ? !a.equals(mapKey.a) : mapKey.a != null) return false;
if (b != null ? !b.equals(mapKey.b) : mapKey.b != null) return false;
return c != null ? c.equals(mapKey.c) : mapKey.c == null;
}
#Override
public int hashCode() {
int result = a != null ? a.hashCode() : 0;
result = 31 * result + (b != null ? b.hashCode() : 0);
result = 31 * result + (c != null ? c.hashCode() : 0);
return result;
}
}
}
Then as you can see you can do your transformation is 6 lines of code. The output of this program is following:
FooResult{a='Test', b='Potato', c=2015-02-02, d=[5]}
FooResult{a='Test', b='Test', c=2015-02-02, d=[5]}
FooResult{a='Test', b='Test', c=2015-10-02, d=[5, 4, 3]}
I've also made Foo, FooResult and MapKey immutable - this is always a good choice when you have to deal with stream transformations. You don't want to have any side effects during stream manipulation and immutable objects guarantee that.

Categories