Background
I am trying to fetch data from mongo by matching custom class with few fields.
Mongo Collection:
"_id":{
"sNo":"1001",
"name": "Sameer",
"city": "Pune",
"state": "Maharashtra"
}
Pojo to match above details
#Document
class Id {
private String sNo;
private String name;
private String city;
private String state;
// getter & setters
}
Java code to fetch data from mongo where I want to fetch data based on sNo, name & city and want to skip state for matching:
ArrayList<Id> ids = new ArrayList<Id>();
...// code to populate Ids
MatchOperation match = Aggregation.match(Criteria.where("_id").in(ids));
Aggregation agg = Aggregation.newAggregation(match);
AggregationResults<Document> result = mongoTemplate.aggregate(agg, detailsCollection,
Document.class);
Problem:
I didn't find any way to skip few of the fields while match.
for now I am getting all data from collection.
Please help to suggest, how this can be achieved?
I am trying to get a single nested value from a ResponseEntity but I am trying to do so without having to create a pojo for every possible item as this is a third party api response.
Example response.getBody() as it appears in Postman:
{
"message": "2 records found",
"records": [
{
"Account": {
"Id": "1",
"Name": "Foo Inc"
},
"CaseNumber": "200",
"Contact": {
"FirstName": "Foo",
"LastName": "Bar"
},
"Status": "In Progress",
"StatusMessage": "We are working on this."
},
{
"Account": {
"Id": "1",
"Name": "Foo Inc"
},
"CaseNumber": "100",
"Contact": {
"FirstName": "Foo",
"LastName": "Bar"
},
"Status": "Closed"
}
]
}
Basically, if I were in JS, I am looking for:
for(let record of res.body.records){
if(record && record.CaseNumber === "200"){
console.log(record.Status)
}
res.body.records[0].Status
Currently, they are are doing this to check if the response is empty:
ResponseEntity<Object> response = restTemplate.exchange(sfdcURL, HttpMethod.POST, entity, Object.class);
LinkedHashMap<Object, Object> resMap = (LinkedHashMap<Object, Object>) response.getBody();
List<Object> recordsList = (List<Object>) resMap.get("records");
if (recordsList.size() <= 0) { return error }
But I need to get the value of of "Status" and I need to do so without creating a pojo.
I appreciate any guidance on how I can do this in Java
UPDATE
So the response.getBody() is returned and when it is displayed in Postman, it looks like the pretty JSON shown above. However, when I do:
System.out.println(response.getBody().toString())
it looks like:
{message=2 Records Found, records=[{Account={Id=1, Name=Foo Inc}, CaseNumber=200, Contact={FirstName=Foo, LastName=Bar}, //etc
To make it worse, one of the fields appears in the console as follows (including linebreaks):
[...], Status=In Progress, LastEmail=From: noreply#blah.com
Sent: 2022-08-08 10:14:54
To: foo#bar.com
Subject: apropos case #200
Hello Foo,
We are working on your case and stuff
Thank you,
us, StatusMessage=We are working on this., OtherFields=blah, [...]
text.replaceAll("=", ":") would help some, but won't add quotations marks nor would it help separate that email block.
How can I so that the responses here like ObjectMapper and JSONObject can work?
You can either convert the string to valid json (not that trivial) and deserialise into a Map<String, Object>, or just pluck the value out of the raw string using regex:
String statusOfCaseNumber200 = response.getBody().toString()
.replaceAll(".*CaseNumber=200\\b.*?\\bStatus=([^,}]*).*", "$1");
This matches the whole string, captures the desired status value then replaces with the status, effectively "extracting" it.
The regex:
.*CaseNumber=200\b everything up to and including CaseNumber=200 (not matching longer numbers like 2001)
.*? as few chars as possible
\\bStatus= "Status=" without any preceding word chars
([^,}]*) non comma/curly brace characters
.* the rest
It's not bulletproof, but it will probably work for your use case so it doesn't need to be bulletproof.
Some test code:
String body = "{message=2 Records Found, records=[{Account={Id=1, Name=Foo Inc}, CaseNumber=200, Contact={FirstName=Foo, LastName=Bar}, Status=In Progress, StatusMessage=We are working on this.}, {Account={Id=1, Name=Foo Inc}, CaseNumber=100, Contact={FirstName=Foo, LastName=Bar}, Status=Closed}]";
String statusOfCaseNumber200 = body.replaceAll(".*CaseNumber=200\\b.*?\\bStatus=([^,}]*).*", "$1");
System.out.println(statusOfCaseNumber200); // "In Progress"
PLEASE DO NOT use Genson as Hiran showed in his example. The library hasn't been updated since 2019 and has many vulnerable dependencies!
Use Jackson or Gson.
Here how you can serialize a string into a Jackson JsonNode:
ObjectMapper mapper = new ObjectMapper();
String json = ...;
JsonNode node = mapper.readTree(json);
If you want to serialize a JSON object string into a Map:
ObjectMapper mapper = new ObjectMapper();
String json = ...;
Map<String, Object> map = mapper.readValue(json, HashMap.class);
You can read more about JsonNode here and a tutorial here.
You can use JSON-Java library and your code will look like this:
JSONObject jsonObject = new JSONObject(JSON_STRING);
String status = jsonObject.getJSONArray("records")
.getJSONObject(0)
.getString("Status");
System.out.println(status);
Or in a loop
JSONArray jsonArray = new JSONObject(jsonString).getJSONArray("records");
for(int i =0; i < jsonArray.length(); i++) {
String status = jsonArray
.getJSONObject(i)
.getString("Status");
System.out.println(status);
}
So the response.getBody() is returned and when it is displayed in Postman, it looks like the pretty JSON shown above. However, when I do:
...
text.replaceAll("=", ":") would help some, but won't add quotations
marks nor would it help separate that email block.
How can I so that the responses here like ObjectMapper and JSONObject
can work?
Firstly, Jackson is the default message converter which Spring Web uses under the hood to serialize and deserialize JSON. You don't need to introduce any dependencies.
Secondly, the process serialization/deserialization is handled by the framework automatically, so that in many cases you don't need to deal with the ObjectMapper yourself.
To emphasize, I'll repeat: in most of the cases in Spring you don't need to handle raw JSON yourself. And in the body of ResponseEntiry<Object> produced by the method RestTemplate.exchange() you have a LinkedHashMap in the guise of Object, it's not a raw JSON (if you want to know why it is a LinkedHashMap, well because that's how Jackson stores information, and it's a subclass of Object like any other class in Java). And sure, when you're invoking toString() on any implementation of the Map you'll get = between a Key and a Value.
So, the problem you've mentioned in the updated question is artificial.
If you want to deal with a Map instead of an object with properly typed properties and here's how you can do that:
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<LinkedHashMap<String, Object>> response = restTemplate.exchange(
sfdcURL, HttpMethod.POST, entity, new ParameterizedTypeReference<>() {}
);
Map<String, Object> resMap = response.getBody();
List<Object> recordsList = (List<Object>) resMap.get("records");
if (recordsList.isEmpty()) { ... }
If there are redundant lines in the Values which you want to trim, then as a remedy you can introduce a custom Jackson-module declaring a Deserializer which would handle leading/trailing white-space and new lines, described in this answer. Deserialize in the module would be applied by default, other options would require creating classes representing domain objects which you for some reasons want to avoid.
As Oliver suggested JsonNode seems to be the best approach. But, if I receive the ResponseEntity<Object>, I still cannot figure out a way to convert it to readable Json (and thus convert it to JsonNode), so I am still open to responses for that part.
I was able to get it to work by changing the ResponseEntity<Object> to ResponseEntity<JsonNode> so this is what I will be submitting for now:
ResponseEntity<JsonNode> response = restTemplate.exchange(sfdcURL,
HttpMethod.POST, entity, JsonNode.class);
JsonNode records = response.getBody().get("records");
String status = null;
String statusMessage = null;
for (JsonNode rec : records) {
if(rec.get("CaseNumber").asText().equals(caseNumber)) {
status = rec.get("Status").asText();
if(rec.has("StatusMessage")) {
statusMessage = rec.get("StatusMessage").asText();
}
} else {
statusMessage = "Invalid CaseNumber";
}
}
Because the overall method returns a ResponseEntity<Object> I then converted my strings to a HashMap and returned that:
HashMap<String, String> resMap = new HashMap<String, String>();
resMap.put("Status", status);
resMap.put("StatusMessage", statusMessage);
return new ResponseEntity<>(resMap, HttpStatus.OK);
This is not a perfect solution, but it works for now. Would still be better for exception handling if I could receive a ResponseEntity<Object> and then convert it to a JsonNode though. Thanks everyone for the responses!
I have a requirement to build a JSON dynamically and need to call an external API.
For instance,
Input : "FIRST_NAME": "XXX"
Based on the above input I need to build a JSON dynamically like below
{
"Req":{
"user":{
"CreatedTime":"2017-03-02T07:52:58Z",
"UpdatedTime":"2017-03-02T07:52:58Z",
"Details":{
"Names":[
{
"Name":{
"First":"kirtq"
}
}
]
}
}
}
}
If I get contact number as input : CONTACT_NUMBER:889999999
Then I have to build a JSON like below
{
"UpdateMemberReq": {
"Customer": {
"CreatedTime": "2017-03-02T07:52:58Z",
"UpdatedTime": "2017-03-02T07:52:58Z",
"CustomerDetails": {
"Contacts": {
"MobilePhone": {
"value": "07888728687"
}
}
}
}
}
}
Like this I have around 30 fields for each request I will get one filed based on that I have to build a JSON dynamically and once I prepared the JSON dynamically I have to call an external API (POST) by passing this JSON as raw type in the body.
I have implemented like below .
List list = new ArrayList();
Name user = mapper.readValue(json2, Name.class);
System.out.println(user);
Map<String, Object> name1 = new HashMap<>();
name1.put("Name", user);
list.add(name1);
Map<String, Object> map1 = new HashMap<>();
map1.put("Names", list);
Map<String, Object> map2 = new HashMap<>();
map2.put("CustomerDetails",map1);
Map<String,Object> map = new HashMap();
map.put("Customer",map2);
Can anyone suggest to me the best way to handle this in java/spring boot?
Thanks!!
Can anyone suggest to me the best way to handle this in java/spring boot?
Given that you don't have a fixed schema for which you want to create JSON, you'll have to do exactly like you do.
This means assembling a map dynamically and then mapping it to a json string.
What you can do to improve is try to extract common and reusable components, for building certain parts of the request.
I'd recommend you create a class structure to keep things manageable with some classes like ...
JsonGenerationService ( the main service the rest of the code uses )
UserJsonGenerator -> generates JSON for user entities
CustomerJsonGenerator -> generates JSON for customers
JsonGeneratorCommon -> contains all the common methods
suggest me how to work on this, i am having all the data in linkedlinkHashmap like key pair values. i want the output in this format i am trying this but couldn't get exact format
{
RequestorId:123
UserId:111
FirstName:john
LastName:peter
Phone_Number:xxx
Email_Address:#com
Address:yyy
Picture:eeee
Work_Location:rrrr
CurrentRole:bca
LanguageSkills:english
Groups: [
{
GroupID: 1
GroupName:1
ContentGroup:f&r
Owner:[
{
UserId:111
FirstName:eee
LastName:rrr
}
]
}
{
GroupID: 2
GroupName:2
ContentGroup:bca
Owner:[
{
UserId:121
FirstName:www
LastName:qqq
},
{
UserId:123
FirstName:ttt
LastName:uuu
}
]
}
}
You can create a Map <String,Object> valueMap and should have the following classes
public Owner{
int UserId;
String FirstName;
String LastName;
}
public Groups{
List<Owner> owner;
int GroupId;
int GroupName;
String ContentGroup;
}
and assign values to HashMap
valueMap.put("PhoneNumber","xxx");
valueMap.put("Email_Address","#com");
.
.
.
valueMap.put("Groups",<Group>Object);
Populate these classes with value and use Google GSON library to convert the hashmap to JSON using
Gson gson=new Gson();
String json=gson.toJson(valueMap);
You could look for a java json library to convert your java data object to json such as gson library eg http://www.mkyong.com/java/how-do-convert-java-object-to-from-json-format-gson-api/
Your example here to converting list of objects to json is similar to the solution offered in this stackoverflow post
Parse List of JSON objects using GSON
Where mention importantly match java model to json model via TypeAdapter
I'm using the Azure SDK, which uses Gson to serialize and deserialize objects to upload to their Mobile Services API. I've had success doing this with a customs class of primitives only, as in the examples given in the Gson User Guide. I'd like to do this with a custom class that contains an ArrayList. I'd even settle for a List or an Array, I'm not too picky. Here's my class:
public class clsUser {
private int UserID;
private String UserName;
private String UserStatus;
public ArrayList<String> UserEmails;
Gson appears to serialize the class when sending to the server this way:
{ UserEmails: [ 'myEmail#gmail.com', 'myEmail2#yahoo.com' ],
UserStatus: 'A',
UserName: 'Scott',
UserID: 1 }
On the server, I'm storing it all in a relational SQLServer database, so I'm keeping UserEmails as a String in there, and trying to bring it back out as an array.
However back on my Android/Gson/Client side, I'm getting an error:
java.lang.IllegalStateException: Expected BEGIN_ARRAY but was STRING
I suspect that the problem is that SQLServer is returning UserEmails with surrounding quotes. I'm not sure how to fix this. Complicating matters is that the Gson implementation is inside the Azure SDK, so I don't think I could even write a custom deserializer if I wanted to. Any suggestions on fixing this? Thanks in advance!
Konrad's comment helped me past this stumbling block. If the problem is that the server is returning this field as a String, then all I needed to do was insert a step to convert it to an Array before returning the data to my client. (Yay for dynamic types in Javascript!) My updated server script is below:
function lookup(request, response) {
var UserID = request.query.UserID;
if (UserID == null || isNaN(parseFloat(UserID))) {
response.send(400, "Invalid Parameters (" + UserID + ")");
} else {
request.service.mssql.query("select ID UserID, Full_Name UserName, Email_Addresses UserEmails, Status UserStatus from User_List where ID=?;", [UserID], {
success: function (results) {
if (results.length > 0) {
//*** Added this line below to convert String back to Array ***//
results[0].UserEmails = results[0].UserEmails.split(',')
response.send(200, results[0]);
} else {
response.send(200, []);
}
}, error: function (err) {
response.send(500, {"Message" : "Lookup error = " + err});
}
});
}
}