java 11 collect to map by keeping value unique - java-8

I have a specific json value as shown below,
{
"record_id" : "r01",
"teacherNstudents": [
{
"teacher" : {
"name" : "tony",
"tid" : "T01"
},
"student" : {
"name" : "steve",
"sid" : "S01"
}
},
{
"teacher" : {
"name" : "tony",
"tid" : "T01"
},
"student" : {
"name" : "natasha",
"sid" : "S02"
}
},
{
"teacher" : {
"name" : "tony",
"tid" : "T01"
},
"student" : {
"name" : "bruce",
"sid" : "S03"
}
},
{
"teacher" : {
"name" : "tony",
"tid" : "T01"
},
"student" : {
"name" : "victor",
"sid" : "S04"
}
},
{
"teacher" : {
"name" : "henry",
"tid" : "T02"
},
"student" : {
"name" : "jack",
"sid" : "S05"
}
},
{
"teacher" : {
"name" : "henry",
"tid" : "T02"
},
"student" : {
"name" : "robert",
"sid" : "S06"
}
}
]
}
I am trying to generate a map like the one below,
[ {"S01", "T01"} , {"S05","T02"} ]
This is by removing all duplicate values and selecting only one teacher and student. The current code I wrote for this is
var firstMap = records.getTeacherNstudents()
.stream()
.collect(Collectors.toMap(tS -> tS.getTeacher().getTid(),
tS -> tS.getStudent().getSid(),
(a1, a2) -> a1));
return firstMap.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
I believe this can be improved, by using Collectors.groupingBy. I am still working on it, but if anyone has any good idea on how to solve this, please share.

Using Java 8 groupingBy
You can try the below approach in order to have the Map<String,List<String>> or Map<String,Set<String>>(avoid duplicates) where key of map will be the teacher id and value as List or Set of Students corresponding to each teacher.
I have used groupingBy feature from java 8 and did the grouping based on the tId and before collecting it, I have downstream it to List or Set of student Ids corresponding to each tId.
Approach A: Map<String,Set< String >> (Uniques)
data.getTeacherStudentMappingList()
.stream()
.collect(Collectors.groupingBy(x -> x.getTeacher().getTid(), LinkedHashMap::new,
Collectors.mapping(y -> y.getStudent().getSid(),Collectors.toSet())));
Approach B : Map<String,List< String >> (Non-uniques, duplicates)
data.getTeacherStudentMappingList()
.stream()
.collect(Collectors.groupingBy(x -> x.getTeacher().getTid(), LinkedHashMap::new,
Collectors.mapping(y -> y.getStudent().getSid(),Collectors.toList())));
Here,
data is the converted object from the given json.
LinkedHashmap::new is used to preserve the order of student data from the json in the output.
collectors.mapping is used to convert the values corresponding to each key into the student ids.
Collectors.toList() will collect the list of student ids in the list.
Collectors.toSet() will collect the unique student ids in the set.
Output:
{T01=[S01, S02, S03, S04], T02=[S05, S06]}

Related

Using `push` to create a field array in laravel

I want to add a field array to mongoDB in laravel. I use push to create an array field. And I did it the following way:
public function addFieldByUser($id)
{
$product = Product::find($id);
if (empty($product)) {
abort(404);
}
if (!isset($product->image['productC'])) {
$product->push("image.productC", []);
}
}
I have successfully added the field productC in mongoDB, and here are the results:
{
"_id" : ObjectId("6295eb7210b4ec7cb72c9426"),
"name" : "test",
"image" : {
"productA" : [
{
"_id" : "6295eb6c8e88fb54231e66c3",
"link" : "https://example.com",
"image" : "dc1c94e421d4ab6a592bcae33ec97345.jpg",
"externalLink" : true
}
],
"productB" : [
{
"_id" : "6295eb957cb6f9350f0f0de5",
"link" : "https://example.com",
"image" : "1ccd4b1d7601a3beb213eb5b42c5d9bf.jpg",
"externalLink" : true
}
],
"productC" : [
[]
]
}
}
But inside productC there is one more sub-array. But my original intention was to add field productC as an empty array
I would like to have a result like this:
{
"_id" : ObjectId("6295eb7210b4ec7cb72c9426"),
"name" : "test",
"image" : {
"productA" : [
{
"_id" : "6295eb6c8e88fb54231e66c3",
"link" : "https://example.com",
"image" : "dc1c94e421d4ab6a592bcae33ec97345.jpg",
"externalLink" : true
}
],
"productB" : [
{
"_id" : "6295eb957cb6f9350f0f0de5",
"link" : "https://example.com",
"image" : "1ccd4b1d7601a3beb213eb5b42c5d9bf.jpg",
"externalLink" : true
}
],
"productC" : []
}
}
Anyone please give me any suggestions, I'm new to mongoDB so it's really giving me a hard time. Thank you very much
Push doesn't make sense here. push implies that you have an array that you want to add a value to. But here you just want to initialise an array, so you'll have to use another method.
(Also notably if you use push on an eloquent model it'll also save it to the database)
You could do something like $product->image['productC'] = []
And then $product->save() if you want to save it

Cosmos DB Collection not using _id index when querying by _id?

I have a CosmosDb - MongoDb collection that I'm using purely as a key/value store for arbitrary data where the _id is the key for my collection.
When I run the query below:
globaldb:PRIMARY> db.FieldData.find({_id : new BinData(3, "xIAPpVWVkEaspHxRbLjaRA==")}).explain(true)
I get this result:
{
"_t" : "ExplainResponse",
"ok" : 1,
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "data.FieldData",
"indexFilterSet" : false,
"parsedQuery" : {
"$and" : [ ]
},
"winningPlan" : {
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1,
"executionTimeMillis" : 106,
"totalKeysExamined" : 0,
"totalDocsExamined" : 3571,
"executionStages" : {
},
"allPlansExecution" : [ ]
},
"serverInfo" : #REMOVED#
}
Notice that the totalKeysExamined is 0 and the totalDocsExamined is 3571 and the query took over 106ms. If i run without .explain() it does find the document.
I would have expected this query to be lightning quick given that the _id field is automatically indexed as a unique primary key on the collection. As this collection grows in size, I only expect this problem to get worse.
I'm definitely not understanding something about the index and how it works here. Any help would be most appreciated.
Thanks!

RepositoryRestResource returns results in a different format than RepositoryRestController

I am facing an issue with using different Spring Controllers.
We are using a standard Spring PagingAndSortingRepository annotated with RepositoryRestResource to serve search responses . One of the methods is
Page<Custodian> findByShortNameContainingIgnoreCase(#Param("shortName") String shortName, Pageable p);
It returns all entities of Custodian that satisfy the conditions grouped in Pages.
The result looks like this:
{
"_embedded" : {
"custodians" : [ {
"shortName" : "fr-custodian",
"name" : "french custodian",
"contact" : "Francoir",
"_links" : {
"self" : {
"href" : "http://127.0.0.1:9000/api/custodians/10004"
},
"custodian" : {
"href" : "http://127.0.0.1:9000/api/custodians/10004"
}
}
} ]
},
"_links" : {
"self" : {
"href" : "http://127.0.0.1:9000/api/custodians/search/findByShortNameContainingIgnoreCase?shortName=fr&page=0&size=3&sort=shortName,asc"
}
},
"page" : {
"size" : 3,
"totalElements" : 1,
"totalPages" : 1,
"number" : 0
}
}
This is the format our frontend expects.
However, we need another query that results in a pretty long function (and thus URL) because it takes multiple parameters.
To be specific, it globally searches for a string in Custodian. So every parameter has the same value.
In order to shorten the URL we created a RepositoryRestController annotated with ResponseBody and implemented a function that takes only one parameter, calls the long URL internally and re-returns the result (a Page).
#RequestMapping(value = "/custodian", method = RequestMethod.GET)
public Page<Custodian> search(#RequestParam(value = "keyWord") String keyWord, Pageable p) {
return repo.LONGURL(keyWord, keyWord, p);
}
Unfortunately, Spring doesn't apply the same format to the result of our function.
It looks like this:
{
"content" : [ {
"id" : 10004,
"shortName" : "fr-custodian",
"name" : "french custodian",
"contact" : "Francoir",
} ],
"pageable" : {
"sort" : {
"sorted" : true,
"unsorted" : false
},
"offset" : 0,
"pageSize" : 3,
"pageNumber" : 0,
"unpaged" : false,
"paged" : true
},
"totalElements" : 3,
"totalPages" : 1,
"last" : true,
"size" : 3,
"number" : 0,
"sort" : {
"sorted" : true,
"unsorted" : false
},
"numberOfElements" : 3,
"first" : true
}
How do you get Spring to deliver the same format in our custom method?

$sort pipeline after $group not working using Spring aggregate class

I have a User collection which looks like below sample :
User :{
"_id" : ObjectId("59f6dc660a975a3e3290ea01"),
"basicInfo" : {
"name" : "xxxx",
"age" : 27,
"gender" : "Male"
}
"otherInfo" {
"projects" : [
{
"_id" : ObjectId("59f6f9230a975a67cc7d7638"),
"name" : "Test Project",
"projectImage" : "images/project/59f6f9230a975a67cc7d7638.jpg",
"desc" : "This is a testing project",
"status" : "Active",
"verifyDet" : {
"method" : "Admin",
"status" : "PENDING",
"isVerified" : false
}
},
{
"_id" : ObjectId("59f6f9230a975a67cc7d5556"),
"name" : "Test Project Two",
"projectImage" : "images/project/59f6f9230a975a67cc7d5556.jpg",
"desc" : "This is a testing project",
"status" : "Closed",
"verifyDet" : {
"method" : "Admin",
"status" : "APPROVED",
"isVerified" : true
}
}
]
}
}
Note: One user can be part of multiple projects. But he needs approval from Admin to participate in the project activities. Verification is managed by verifyDet and projects are managed by projects array.
Actual requirement is to show the list of members in such a way that members having verification pending comes on top in alphabetic order and then approved/verified members in alphabetic order to Admin.
When I run below query on mongo shell I get list of Users with only one project detail(_id=59f6f9230a975a67cc7d7638) for which I want to search and result sorted by Verification pending users and User name. The result comes appropriately.
db.User.aggregate(
{$unwind:"$otherInfo.projects"},
{
$match:{
"otherInfo.projects._id":ObjectId("59f6f9230a975a67cc7d7638"),
"otherInfo.projects.status":"Active"
}
},
{$group: {_id: {"_id":"$_id", "basicInfo":"$basicInfo"}, "projects": {$push: "$otherInfo.projects"}}},
{$project:{"_id":"$_id._id", "basicInfo":"$_id.basicInfo", "otherInfo.projects":"$projects"}},
{$sort:{"otherInfo.projects.verifyDet.isVerified":1, "basicInfo.name":1}}
)
But when I create same aggregate in Spring like mentioned below I get exception:
public List<Map> fetchUsersList(String projectId, Pageable pageable) {
//unwind operation
AggregationOperation unwindOp = Aggregation.unwind("$otherInfo.projects");
Criteria criteria = Criteria.where("otherInfo.projects._id").is(new ObjectId(projectId));
criteria.and("otherInfo.projects.status").is("Active");
AggregationOperation matchOp = Aggregation.match(criteria);
AggregationOperation groupOp = Aggregation.group(
Fields.from(Fields.field("_id", "$_id")).and(Fields.field("basicInfo","$basicInfo"))).push("$otherInfo.projects").as("projects");
AggregationOperation projectOp = Aggregation.project(
Fields.from(Fields.field("_id","$_id._id"),
Fields.field("basicInfo","$_id.basicInfo"),
Fields.field("otherInfo.projects","$projects")));
AggregationOperation sortOp = Aggregation.sort(Direction.DESC, "otherInfo.projects.verifyDet.isVerified").and(Direction.DESC, "basicInfo.name");
Aggregation agg = Aggregation.newAggregation(unwindOp, matchOp, groupOp, projectOp, sortOp);
AggregationResults<User> results = mongoTemplate.aggregate(agg,
"User", User.class);
return results.getMappedResults();
}
Exception :
2017-12-15 19:24:31,852 ERROR GlobalExceptionHandler:75 - Exception Stack Trace :
java.lang.IllegalArgumentException: Invalid reference 'otherInfo.projects.verifyDet.isVerified'!
at org.springframework.data.mongodb.core.aggregation.ExposedFieldsAggregationOperationContext.getReference(ExposedFieldsAggregationOperationContext.java:99)
at org.springframework.data.mongodb.core.aggregation.ExposedFieldsAggregationOperationContext.getReference(ExposedFieldsAggregationOperationContext.java:80)
at org.springframework.data.mongodb.core.aggregation.SortOperation.toDBObject(SortOperation.java:73)
at org.springframework.data.mongodb.core.aggregation.AggregationOperationRenderer.toDBObject(AggregationOperationRenderer.java:56)
at org.springframework.data.mongodb.core.aggregation.Aggregation.toDbObject(Aggregation.java:580)
at org.springframework.data.mongodb.core.aggregation.Aggregation.toString(Aggregation.java:596)
at com.grpbk.gp.repository.impl.UserRepositoryCustomImpl.fetchUsersList(UserRepositoryCustomImpl.java:1128)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
Please let me know what I am doing wrong.
"otherInfo.projects.verifyDet.isVerified" field needs to be present in Project Oepration or group operation so that sort would get a reference for that.

Mongo query to update field

i am using Mongo library(alex bibly) with codeignitor , and my collection like this
{
chart:{
"LL":[
{ "isEnable":true,
"userName":"Nishchit Dhanani"
}
]
}
}
I want to update isEnable=false .
Any help appreciated .
First of all you have an error in your JSON document. You can not have key values in a dictionary.
So your JSON should be like this:
{
"_id" : ObjectId("526e0d7ef6eca1c46462dfb7"), // I added this ID for querying. You do not need it
"chart" : {
"LL" : {
"isEnable" : false,
"userName" : "Nishchit Dhanani"
}
}
}
And to do what you needed you have to use $set
db.test.update(
{"_id" : ObjectId("526e0d7ef6eca1c46462dfb7")},
{$set : {"chart.LL.isEnable" : false}
})
With your new modification, you need to do something like this:
db.test.update(
{"chart.LL.isEnable" : false},
{$set : {"chart.LL.$.isEnable" : false}}
)

Resources