I was trying to serialize/deserialize JSON using GSON. The payload in question is ApiGatewayAuthorizerContext. Inside it, there is a HashMap<String, String>. But when doing from/to json, the field naming strategy is not applied to the Keys.
#JsonIgnoreProperties(ignoreUnknown = true)
public class ApiGatewayAuthorizerContext {
//-------------------------------------------------------------
// Variables - Private
//-------------------------------------------------------------
private Map<String, String> contextProperties = new HashMap<>();
private String principalId;
private CognitoAuthorizerClaims claims;
}
Same with MultiValuedTreeMap<String, String> in AwsProxyRequest class too, which is a MultivaluedMap<Key, Value>.
My field naming strategy is simple, replace - with _, for example, the payload below is not a valid JSON for many downstream components I use, and want to replace all '-', with '_'.
"MultiValueHeaders": {
"Accept": [
"application/json, text/plain, */*"
],
"Authorization": [
"Bearer ey...b9w"
],
"Content-Type": [
"application/json;charset=utf-8"
],
"Host": [
"aws-us-east-1-dev-dws-api.xxxxxxxx.com"
],
"User-Agent": [
"axios/0.20.0"
],
"X-Amzn-Trace-Id": [
"Root=1-xxxxxxxx-xxxxxxxxxxxxxxxx"
],
"X-Forwarded-For": [
"127.0.232.171"
],
"X-Forwarded-Port": [
"443"
],
"X-Forwarded-Proto": [
"https"
]
},
Any idea?
EDIT: Adding Field Naming Strategy.
public class ApiEventNamingStrategy implements FieldNamingStrategy {
/**
* Translates the field name into its {#link FieldNamingPolicy.UPPER_CAMEL_CASE} representation.
*
* #param field the field object that we are translating
* #return the translated field name.
*/
public String translateName(Field field) {
String fieldName = FieldNamingPolicy.UPPER_CAMEL_CASE.translateName(field);
if (fieldName.contains("-")) {
fieldName = fieldName.replace('-', '_');
}
return fieldName;
}
}
which is used to setFieldNamingStrategy as shown below,
private static Gson gson =
(new GsonBuilder()).setFieldNamingStrategy(new ApiEventNamingStrategy()).create();
The result is, all the member variables other than the ones inside the Map gets checked, and renamed. Seems setFieldNamingStrategy wont look inside a Map and rename the Keys.
Now I'm looking at the registering a TypeAdapter by utilizing registerTypeAdapterFactory. Seems the the answer by #linfaxin here gson-wont-properly-serialise-a-class-that-extends-hashmap would come to rescue! But the problem is, where/how to and/or the right place to introduce the field naming strategy in the RetainFieldMapFactory class, becasue I see a lot of avenues to hack it in.
Any ideas are most welcome!
btw, the values are populated by AWS APIGateway AND a custom authorization lambda. No way I think I could change the behavior of the APIGateway.
GSON will not get inside map and consider what you want to do. Jackson either.
Considering that you already have your content in a map, I think it is much much easier to just convert the map with 3 lines of code instead of trying to hack how libraries serialize and deserialize objects.
Map<String, String> contextPropertiesNormalized= contextProperties.keySet()
.stream()
.collect(Collectors.toMap(k-> k.contains("-") ? k.replace("-","_"): k, v -> contextProperties::get));
Related
I'm struggling to find a solution for this...
I would like to know how to get a specified parameter passing it through the request without having to code each specific case in Spring Boot API.
For example, in my case I have this JSON object:
{
"id": 3,
"name": "test",
"dominio": "dom",
"altas": "6",
"bajas": "2",
"default_group": [
{
"idRef": 1,
"name": "Users",
"path": "OU=es"
}
],
"office": [
{
"idRef": 1,
"title": "Intern",
"name": "CN=Office license",
"path": "OU=licenseOffice"
},
{
"idRef": 2,
"title": "Specialist",
"name": "CN=Office License F3",
"path": "OU=LicenseGroupF"
}
]
Apart from this, I have all the entities defined in my code, but I would like to access one of their parameters just using their names, like name, dominio, altas, bajas, default_group or office.
The idea is to do this without having to code each method for each parameter.
I wouldn't have to do this for the nested objects (office and default_group) just getting the info from them passing the name of the parameter.
So I would like to do something like:
GET --> localhost:8080/api/3/name
And this would return the name of object with id 3
Or doing this:
GET --> localhost:8080/api/3/default_group
And this would return the Array containing all the default_groups inside.
Apart from this, I would like to know if is it possible to do a PUT request for the methods doing the same thing.
I don't know if this can be done, but in case that it can, would it be possible for you to give some guidance or something...
Thank you very much
Edit. Thanks to #daniu I made it work flawlessly, I paste my solution here based on his comment so if anybody find it helpful. My object is called "Compania".
#GetMapping("/{companiaId}/{field_name}")
public Object queryField(
#PathVariable("companiaId") Long companiaId,
#PathVariable("field_name") String fieldName) {
Map<String, Function<Compania, Object>> fieldRetrievers = Map.of(
"name", Compania::getName,
"dominio", Compania::getDominio,
"altas", Compania::getAltas,
"bajas", Compania::getBajas,
"default_group", Compania::getDefault_group,
"office", Compania::getOffice
);
Compania c = companiaService.getCompaniaNotOpt(companiaId);
Function<Compania, Object> retriever = fieldRetrievers.get(fieldName);
return retriever.apply(c);
}
getCompaniaNotOpt is a method that takes a Compania without being Optional, so it works.
Thanks daniu.
I wouldn't consider this the cleanest of designs, but what would work is creating a Map that contains all the field accessors by name:
Map<String, Function<YourClass>> fieldRetrievers = Map.of(
"name", YourClass::getName,
"default_group", YourClass::getDefaultGroup,
"office", YourClass::getOffice
);
Then you can use that in your controller (service actually, but to keep it short here):
#GetMapping("/path/{field_name}")
Object queryField(#PathVariable("field_name") String fieldName) {
YourClass c = getObject();
Function<YourClass, Object> retriever = fieldRetrievers.get(fieldName);
return retriever.apply(c);
}
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.
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()));
My JSON object is like this:
{
"id": 911,
"slug": "andreas-nikotini",
"title": "Nikotini (Greek-Russian Dance Pop)",
"picture": "https://s3-eu-west-1.amazonaws.com/ellostatic/video_thumb/911/53e29f833eb372c72d127a298723edf9.jpeg",
"artists":
[
{
"name": "Andreas"
}
],
"favorite_count": 0,
"like_count": 177,
"view_count": 29752,
"is_favorite": false
}
And I parse it to next object:
public class RelatedVideoGSON {
int id;
String slug;
String title;
String picture;
String favorite_count;
ArrayList<ArtistGSON> artists;
String like_count;
String view_count;
boolean is_favorite;
}
Where like_count, view_count, and favorite_count are Integers.
I want to add thousand separator to the Integers, while object is parsing. How can I do that?
The easiest way I can think of doing this is: (if it's me, I won't bother digging into the Gson parsing mechanism :-) )
parse the string/json to a general HashMap<Object, Object> map
modify the specific key-values as you like.
dump the modified map to string/json again.
re-parse the dumped string/json to the final object you want.
This procedure may seems not elegant, but the problem itself is not elegant anyway, comma splited string which represents numbers like 4,532,345 should only be done at the very front-end rather than stored in structure.
PS: You can parse a general HashMap from string by code like this:
HashMap<Object, Object> m = gson.fromJson(s, new TypeToken<Map<Object, Object>>(){}.getType());
I have an object that is curently being serialized to:
{
"label" : "label",
"proxyIds" : [ ],
"childIds" : [ 161, 204, 206, 303, 311 ],
"actionIds" : [ 157, 202 ],
}
That proxyIds is an empty (not null) collection in the java object.
How do I configure Jackson to not include that object in the json at all?
I want behaviour similar to "unwrapped" collections in xml/soap where if the collection is empty it is not included. I do not need to distinguish between null and empty collection and want to reduce the size of the json payload.
Since Jackson 2.0.0 (25-Mar-2012), you can also use the #JsonInclude annotation to control this on a per-field or per-class basis.
public class MyObject {
#JsonInclude(Include.NON_EMPTY)
private List<Integer> proxyIds;
...
}
This may be a long shot but how about using Inclusions and defining NON_DEFAULT as the inclusion property. The docs say:
"Value that indicates that only properties that have values that differ from default settings (meaning values they have when Bean is constructed with its no-arguments constructor) are to be included."
So if the default value is an empty array it should skip it.
Something like:
ObjectMapper mapper = new ObjectMapper();
mapper.getSerializationConfig().setSerializationInclusion(Inclusion.NON_DEFAULT);
public class Test {
String[] array = { };
....
}
http://jackson.codehaus.org/1.1.2/javadoc/org/codehaus/jackson/map/annotate/JsonSerialize.Inclusion.html