Convert Enum with attributes to map in Java - 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.

Related

How could I get specific parameters just by passing them in the request? SpringBoot API

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);
}

GSON wont rename fields inside HashMap

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));

Parsing json configuration with multiple primary keys

{
"ssn1": {
"name": "person1",
"address": "address1"
"drivingLicense": "dl1"
},
"ssn2": {
"name": "person2",
"address": "address2"
"drivingLicense": "dl2"
}
}
I have a json configuration data as mentioned above.
class Citizen {
Map<String, Person> personMap;
}
class Person {
String name;
String address;
String drivingLicense;
}
The pojos is like the one mentioned above. I could easily deserialize the json configuration into a java Map<String, Person> and search using SSN.
Question: How can I deserialize to get two maps - one keyed on SSN and other with driving license? I could use the SSN->person map to create another map DrivingLicense->Person. Can I create the DrivingLicense map in an elegant way to that I don't have to write
ssnMap.entrySet().stream().collect(Collectors.toMap(
e -> e.getValue().getDrivingLicense(),
e -> e.getValue()));
again and again for other primary keys like passport number?
Assumptions
Driving license is unique for each person
Query by driving license does not require SSN as output
There could be other uniquely identifying keys in the Person class
You can't do that with any kind of content-agnostic deserialization, which operates only on structure. You can do something trivial in postprocessing like
var byDl = bySsn.values().stream().collect(toMap(Person::drivingLicense, identity()));

Using GSON to parse Json into a JAVA Object where the Json Elements may vary

I am trying to parse a JSON .txt file into a JAVA object using GSON. The JSON file has the following structure:
{
"event0" : {
"a" : "abc",
"b" : "def"
},
"event1" : {
"a" : "ghi",
"b" : "jkl",
"c" : "mno"
}
}
I have read the text file into a String called dataStr. I want to use the fromJson method to capture the events into the following JAVA class:
public class Event {
private String a;
private String b;
private String c;
public Event() {}
}
The problem is that the JSON might have one extra field "c" in some of the elements. I want to parse all the events into Event class objects, and for the cases where there is no "c" field, I want to make it null or zero in my object. It is not known beforehand which of the elements will have the "c" field.
Specifically, I was not able to figure out how to handle one extra field in some of the JSON elements. I want to do something along the lines of:
Gson gson = new Gson();
ArrayList<Event> events = gson.fromJson(dataStr, Event.class);
But I am stuck with first, how to iterate over the events in the Json file, and secondly, how to handle some occasional missing fields into the same Event object. I would really appreciate a kick in the right direction. Thank you all.
I am fairly new to JSON parsing, and might have missed something in the following answers:
Using Gson to convert Json into Java Object
Mapping JSON into POJO using Gson
Using gson to parse json to java object
Parse JSON into a java object
How to parse a json file into a java POJO class using GSON
I'm not sure if I understood your question right. As per my understanding, you are trying to convert a json object with an extra field which is not available in the java class. Frankly, I don't understand why you want that or if it's possible to start with. You can have a workaround by converting the json to Map.
Map map = gson.fromJson(jsonString, Map.class);
Gson automatically do that for you.
So, if you have a class "Alpha" with 3 fields ("a", "b" and "c") and you try to work on a json object that has 2 fields with names that match with Alpha's "a" and "b", Gson will fill "a" and "b" with json file's value and "c" will automatically set as null.
So, in your case, if you write this:
ArrayList<Event> events = gson.fromJson(dataStr, Event.class);
And in your json there are events with only 2 fields (that match with any Event's class fields) and events with all fields set, you will get a list of Events with no errors. Maybe you'll get some fields null, but the code will work.
I hope to be helpful! Ask for further informations, if you want to!
EDIT
Note that your json file has not to be .txt but .json instead!
First I believe your JSON should look like this:
{
"events": [
{
"name": "event0",
"a": "abc",
"b": "def"
},
{
"name": "event1",
"a": "abc",
"b": "def",
"c": "mno"
}
]
}
This will need two classes for your model:
public List<Event> events = null;
public class Event {
public String name;
public String a;
public String b;
public String c;
}
And then then with GSON
Events events = gson.fromJson(jsonData, Events.class);
Also I recommend to always use an online validator for JSON so you are sure your JSON structure is correct before coding against it.
https://jsonlint.com/
Or for formate the JSON:
http://jsonprettyprint.com/
Also this website can create the Java classes for you from either a JSON Schema or by using an example file.
http://www.jsonschema2pojo.org/
Try the below code snippet:
Gson gson = new Gson();
ArrayList<Event> events = gson.fromJson(dataStr, new TypeToken<ArrayList<Event>>(){}.getType());
In the source code of Gson has a very clear explain

Associativity array in Java

I receive a List<org.apache.avro.generic.GenericRecord> with the data contents as shown below (JSON notation used for clarity). How can I best hold these record types using Java?
Record 1:
[
{
"serial_no" : "x",
"data1" : "d"
},
{
"serial_no" : "y",
"data2" : "d2"
},
............................MANY MORE
]
Record 2:
[
{
"id":"x",
"type":"A"
},
{
"id" : "x",
"type" : "B"
},
{
"id" : "y",
"type" : "A",
},
{
"id" : "y",
"type" : "B"
}
]
As you see here, each serial number has two records in record2. serial_no in record1 is same as id in record2.
My Goal is:
Fatsest way to find these two records.
Solution I think:
Create a map like
map.put("x", [map.put("A",List), map.put("B",List)]);
But I feel like, its a complex structure. Because map contains list of maps[each map is Map<String,List<Map<String,String>>>].
Any suggestions?
EDIT
Each entries in records are avro GenericRecord
It looks as if you are trying to parse JSON using Java. Why not use a specific library for that?
Like the basic http://www.json.org/java/ or Google's https://github.com/google/gson
Otherwise, I do not think that the complex structure you are proposing is especially slow. You might want to design your own object class to hold the data if you think it is more efficient or easier to get to the data.
EDIT
Based on your question I assumed JSON was the format you received it in, sorry.
I would just create a wrapper for GenericRecord, or subclass it. Then add the methods that you need to extract the data, or make it Comparable for sorting.
Something along the lines of
public class MyRecord extends GenericRecord implements Comparable<MyRecord>
{
// Determine the type
public int getType()
{
if ( this.get( "id") != null )
return 2;
return 1;
}
// Add methods that allow you to retrieve the serial field from any of the two record types
public String getId()
{
if ( this.get( "id") != null )
return (String)this.get("id");
return (String)this.get("serial_no");
}
// add comparator methods that will allow you to sort the list, compare based on Id, etc
#Override
public int compareTo(MyRecord another)
{
// Just a simple example
return this.getId().compareTo( another.getId() );
}
}
Define classes for repeated entries:
class SerialNoData {
String serialNo;
Object data;
}
and
class IdType {
String id;
String type;
}
; once parsed put the instances into arrays or Lists to get the desired format.
How complex the map is doesn't really make a difference for the speed. Depending on the type of Map you use getting a list of records will be constant time (with a reasonably small overhead). Finding something in the sublists will then be O(n), since you need to iterate through the list and look at all the Maps.
Define following classes
class Serial{
String serial-no;
String data;
List<IdType> idTypes;
}
class IdType{
String id;
String type;
}
After that you can use jackson or any kind of JSON processing library.

Categories