Dynamically compose DynamoDB Query Expression according to user input - java

I'm writing an API endpoint to allow users to list items according to the parameters they pass in on the request body.
I want the partition key to be the only required parameter; clients will be allowed to pass zero or more key-value pairs to achieve the desired level of granularity in their query.
My DynamoDB Table holds items with the following structure:
{
"appName": { //PARTITION KEY
"S": "APP_1"
},
"requestId": { // SORT KEY
"S": "request_1224"
},
"creationTime": {
"N": "1636332520679"
},
"resolver": {
"S": "SOMEONES_ID"
},
"status": {
"S": "PENDING"
}
}
I have some working code, where the DynamoDBQueryExpression is hard-coded and I do have an idea of how to dynamically put together the Query Expression but I'm thinking of an ugly combination of for-loops and string manipulation.
Is there an elegant and clean way of dynamically putting together the DynamoDBQueryExpression?
Here's the starter code I have, for reference:
Map<String, AttributeValue> eav = new HashMap<String, AttributeValue>();
eav.put(":val1", new AttributeValue().withS(requestModel.getAppName()));
eav.put(":val2", new AttributeValue().withS(accessRequestModel.getRequestStatus()));
DynamoDBQueryExpression<RequestModel> queryExpression = new DynamoDBQueryExpression<RequestModel>()
.withKeyConditionExpression("appName = :val1")
.withFilterExpression("RequestStatus = :val2")
.withExpressionAttributeValues(eav);
Just to be extra clear, see how I'm doing put and passing hard-coded values to withFilterConditionExpression –I'd like these values being passed dynamically according to client's input.
I'm using Java's AWS DynamoDB Mapper.

Related

Convert Enum with attributes to map in Java

I have an enum as below:
#AllArgsConstructor
public enum EnumExample {
VAL1("val1 description", 100),
VAL2("val2 description", 200);
String description;
int value;
}
Now I want to return all enum values with attributes as a list of the map as below:
[
{
"name": "VAL1",
"description": "val1 description",
"value": 100
},
{
"name": "VAL2",
"description": "val2 description",
"value": 200
}
]
I am able to achieve this using the below code:
Arrays.stream(EnumExample.values())
.map(enumExample ->
ImmutableMap.of("name", enumExample.name(),
"description", enumExample.description,
"value", enumExample.value))
.collect(Collectors.toList())
But I want to know if there any best way to achieve the same without explicitly converting EnumExample to Map. If any new attribute gets added then it should be coming in the resulting map as a new K, V pair.
I tried the below ways but both return only enum values [VAL1, VAL2].
com.google.common.collect.Lists.newArrayList(EnumExample.values())
Arrays.stream(EnumExample.values()).collect(Collectors.toList())
Tried to convert to map too but returns {"VAL2":"VAL2","VAL1":"VAL1"}.
Arrays.stream(EnumExample.values())
.collect(Collectors.toMap(o -> o, Function.identity()))
Any leads or better ways that doesn't require a manual map creation is appreciated.
My requirement:
In a webservice, return all the Enum values along with attributes to the client. The client has the logic to parse all the attributes coming. Like today there is a description attribute and tomorrow if new attribute like boolean manadatoryField, then it only needs to be handled by client. But from the server end, I am unable to return the Enum values with attributes without manually creating a map out of each enum and returning the map.
Found a simple and another way of doing using Jackson:
Add annotations to the enum.
#Getter
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
Add an explicit getter for name
public String getName() {
return this.name();
}
new ObjectMapper().writeValueAsString(EnumExample.values()) returns a valid JSON which can be converted to Map. In my case I return, this to client!
Answering my own question to help others. If this is the only way, then do upvote.
Arrays.stream(EnumExample.values())
.map(enumExample ->
ImmutableMap.of("name", enumExample.name(),
"description", enumExample.description,
"value", enumExample.value))
.collect(Collectors.toList())
Any best way to achieve the same without explicitly converting EnumExample to Map is greatly appreciated. For example, If any new attribute gets added then it should be coming in the resulting map as a new K, V pair.

How to map bulk request response in java

We are going to consume third party REST API service which supports bulkGETOperations , before we send the request we need to assign a unique id(bid) to each element in array and the same will be returned in response if it SUCCESS(200 OK).
Could some one please help me with the way / most efficient way to map the response based on the unique bid which was passed in the request and after mapping I need to store individual fields in database based on certain condition .Below is the sample request JSON , response will be same in below format but will contain additional fields per school
"testList": [
{
"schoolIdentifier": {
"schoolId": "abc",
"schoolName": {
"name": "ABC"
}
},
"bid": 1
},
{
"schoolIdentifier": {
"schoolId": "bbb",
"schoolName": {
"name": "BCD"
}
},
"bid": 2
}
]
Note: I am aware that this can be done with Map but looking for efficient solution using java 8
You can use merge to lookup and add the response fields. Assuming you've School class with all the request and response fields and are mapped by bid id.
static Map<Integer, School> merge(Map<Integer, School> request, Map<Integer, School> response) {
Map<Integer, School> combined = new HashMap<>(request);
response.forEach(
(key, value) -> combined.merge(key, value, (req, resp) -> /** map additional fields from response **/ ));
return combined;
}
'response will be same in below format but will contain additional fields per school'
I suppose the response you receive already contains all you need in a School object and and you just need to convert the list response into a Map efficiently (with key as bid)
You are getting a list testList which contains both bid and the school Object. You can simply convert a list to the Map like :
Map<Integer, SchoolDto> map = testList.parallelStream().collect(
Collectors.toMap(bidSchoolDto -> bidSchoolDto.getBid()
, bidSchoolDto -> bidSchoolDto.getSchool()));

Get String value if present or null if not present using Java8 Stream

I have a json similar like as shown below. The requirement is to get the key value based on the id. ie. lets say If Id is A1 key value should return 2gAwIBAgIQKGZsKfAUzaJHVantyrwVdzANBgkqhkiG9w0BAQs. The key array will always conatins only one element.
{
"keys": [
{
"id": "A1",
"key": [
"2gAwIBAgIQKGZsKfAUzaJHVantyrwVdzANBgkqhkiG9w0BAQs"
]
},
{
"id": "A2",
"key": [
"swKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sfsf2dew"
]
},
{
"id": "A3",
"key": [
"EyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3Mubdwe2"
]
}
]
}
To implement the above I have wrote the below code using Java8 Stream but the problem is that it returns Optional<KeyDetails> which again I needs to parse and get the key value
String keyDetails = "{\"keys\":[{\"id\":\"A1\",\"key\":[\"2gAwIBAgIQKGZsKfAUzaJHVantyrwVdzANBgkqhkiG9w0BAQs\"]},{\"id\":\"A2\",\"key\":[\"swKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sfsf2dew\"]},{\"id\":\"A3\",\"key\":[\"EyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3Mubdwe2\"]}]}";
AccessKeys accessKeys = new ObjectMapper().readValue(keyDetails, AccessKeys.class);
Optional<KeyDetails> filteredKey = accessKeys.getKeys().stream().filter(key-> key.getId().equals("A3")).findAny();
if(filteredKey.isPresent()) {
String keyValue = filteredKey.get().getKey().get(0);
System.out.println("keyValue==>"+keyValue);
}
What I want is to get Optional<String> instead of Optional<KeyDetails>. i.e if the id is present it should return just the key value which is present within the key array.
Some condition that the json satisfies were:
Sometimes there can be a situation for duplicate key id, in that case it should pick only one (first one)
Key array will always contains one string value
Can anyone please help me on this
Sometimes there can be a situation for duplicate key id, in that case
it should pick only one (first one)
You're looking for the map method (to transform) and findFirst instead of findAny to always guarantee the first match is returned.
accessKeys.getKeys()
.stream()
.filter(key-> key.getId().equals("A3"))
.findFirst()
.map(e -> e.getKey().get(0)).orElse(null);
findAny, as the name suggests, should be used in cases where you don't care which matched item is returned. Yes, both findFirst and findAny will act the same in a sequential stream but I'd rather make my intention clear in the first place.

Print JSON with ordered properties

I have JSON with objects in specific order:
{
"Aaa": {
"Langs": {
"Val": [
"Test"
],
"Pro": [
"Test2"
]
}
},
"Bbb": {
"Langs": {
"Val": [
"Test"
],
"Pro": [
"Test2"
]
}
},
"Ddd": {
"Langs": {
"Val": [
"Test"
],
"Pro": [
]
}
},
}
And I would like to add new object Ccc between Bbb and Ddd. I tried to configure object mapper like this:
ObjectMapper mapper = new ObjectMapper()
.enable(SerializationFeature.INDENT_OUTPUT)
.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
and then print with this code, but Ccc ends at the end of file.
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
prettyPrinter.indentArraysWith(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE);
//Write whole JSON in FILE
String finalJson = mapper.writer(prettyPrinter).writeValueAsString(rootFlores);
finalJson = finalJson.replaceAll("\\[ ]", "[" + System.lineSeparator() + " ]");
finalJson = finalJson.replaceAll("/", "\\\\/");
Files.write(Paths.get("DictionaryFlores_new.json"), Collections.singleton(finalJson));
Is here a way how to print JSON ordered?
Jackson deserialization/serialization does not sort properties
According to this answer, the Jackson SORT_PROPERTIES_ALPHABETICALLY only applies to POJO properties, not Maps. In JSON there is no difference between a Map and an Object, so you need to set the order in the Map first by using a LinkedHashMap or TreeMap
By definition, the keys of an object are unordered. I guess some libraries could offer an option to control the order of the keys when stringifying, but I wouldn't count on it.
When you need a certain order in json, you need to use an array. Of course, then you'd have to move the keys to a property in the child objects, and then the resulting array could only be indexed by number (not by the key). So then you might have to do additional processing to covert the data structure in the JSON to the data structure you really want to process.
Since you seems ready to use regex to update a JSON, I would suggest a "safer" approach. Don't try to create a pattern that would unsure that you don't update a value somewhere.
Iterate you values, on object at the time. Stringify the object and append the String yourself. That way, you are in charge of the object order. Example :
StringBuilder sb = new StringBuilder("{");
List<JsonPOJO> list = new ArrayList<>();
//populate the list
for(JsonPOJO pojo : list){
sb.append(pojo.stringify()).append(",");
}
sb.setLength(sb.length() - 1); //remove the last commma
sb.append("}");
Here, you are only managing the comma between each JSON object, not create the "complex" part about the JSON. And you are in full control of the order of the value in the String representation, it will only depend on the way you populate the List.
Note: sorry for the "draft" code, I don't really have access to my system here so just write this snippet to give you a basic idea on how to "manage" a JSON without having to recreating an API completely.
Note2: I would note suggest this unless this looks really necessary. As you mention in a comment, you are have only the problem with one key where you already have a JSON with 80000 keys, so I guess this is a "bad luck" scenario asking for last resort solution ;)

How to convert Neo4j JSON to Java Object

I've been playing with Neo4j 2.0 RC1 for a couple of weeks. I'm writing a Spring Security implementation using Neo4j as the database. When I load a user, the response I get from Neo4j looks like this:
{
"columns" : [ "username", "password", "accountNonExpired","accountNonLocked", "credentialsNonExpired", "enabled" ],
"data" : [ [ "admin", "admin", true, false, true, false ]
}
Originally the only fields returned where username and password (both strings) and I was able to do this:
class Result
{
private List<String> columns = new ArrayList<String>();
private List<ArrayList<String>> data = new ArrayList<ArrayList<String>>();
};
ClientRespose resp = webResource.path(path).type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).post(ClientResponse.class, body );
String s = response.getEntity(String.class);
Result r = new Gson().fromJson(s, Result.class);
Of course when I added the other fields (booleans) I needed to change Result to look like this:
class Result
{
private List<String> columns = new ArrayList<String>();
private List<ArrayList<Object>> data = new ArrayList<ArrayList<Object>>();
};
My code still worked, but when I tried to cast any of the data items to String or Boolean I got a 'failed to cast Object to...' exception. This is of course because there is no type information, so GSon is creating Object instances to put everything it.
So I'm guessing there must be a better way to process the JSON that comes back from Neo4j?
Can I somehow skip the JSON conversion stage and get the Jersey HTTP client to populate my User objects directly?
The problem is that the data is not returned as map.
I usually do this:
get the result as Map
get the columns list
get the data nested list
using an for loop over data to get each row
using a for loop over columns and it's index to access the data in the row
You can also add a factory method to your user Object that gets one of these results rows and constructs your User object.
e.g.
class User {
public static User from(List<Object> row) {
User u=new User((String)row.get(0),(String)row.get(1));
u.setAccountNonExpired((Boolean)row.get(2));
u.setAccountNonLocked((Boolean)row.get(3));
u.setCredentialsNonExpired((Boolean)row.get(4));
u.setEnabled((Boolean)row.get(5));
return u;
}
}
Something you can try with Neo4j 2.0 is to use the transactional endpoint and return the node object (which will return just the node-properties as a map), which you then can map directly with Gson or Jackson to an object (the row).
match (u:User {username:{username}})
return u
otherwise you can also use the literal map syntax in Neo4j 2.0
match (u:User {username:{username}})
return {username : u.username, password: u.password, ....} as user

Categories