Map dynamic field names while deserializing a JSON to a Java object - java

I have an interesting problem that I want to solve. This is while I am parsing a response from one of the platforms that we interact with. The response changes based on the User.
Say for User A, I have the following JSON :
{
"userId": "AA001",
"AA001_Name": "Username",
"AA001_Email": "user#gmail.com",
"AA001_Phone": "000-000-0000"
}
For User B, I have :
{
"userId" : "AA002",
"AA002_Name" : "Username",
"AA002_Email" : "user#gmail.com",
"AA002_Phone" : "000-000-0000"
}
Now, while deserializing, I want to map both of them to the following object, ignoring the field name the json came with :
class User {
private String userId,
private String name,
private String email,
private String phone
}
It is easy to map the userId, as that's the field in the JSON as well, but what about the custom fields?
Now, I can't use the #JsonProperty as the name of the field is dynamically changing based on the user.
Is there any way this could be accomplished?
Please note that I might have several such custom objects like Department, Organization etc, and the platform returns the data in such a manner, meaning the keys have the user-specific information appended.
Any help is appreciated. I am badly stuck at this.

I think you can't do any better than using #JsonCreator:
class User {
private String userId;
private String name;
private String email;
private String phone;
#JsonCreator
public User(Map<String, Object> map) {
this.userId = (String) map.get("userId");
map.entrySet().stream()
.filter(e -> e.getKey().endsWith("_Name"))
.findFirst()
.ifPresent(e -> this.name = (String) e.getValue());
// repeat for other fields
}
// getters & setters (if needed)
}
You can change the stream by a traditional for each on the map's entry set to optimize performance-wise.

You can't use #JSONProperty as is, but what if you reformatted the keys before deserializing the JSON? I.E
String key = "AA001_Name"
String[] key = key.split("_")
key[0] = "AA001"
key[1] = "Name"
//Use key[1] as the new field name
Do this for each key, create a new JSON Object with the correct field names, then deserialize it.

Related

Map Cypher query result to DTO

I want to map cypher query results to a DTO/POJO class. I have the following entities defined in neo4j:
Products , which has properties; Name, Title, Address
Sellers, which has properties; Name, Id
Listings, which has properties; Name, Id
Relationshps are defined as: Products -> Sellers & Sellers -> Listings
My query results is List of Product.Name, [ {Listings.Name, Listings.Id, Sellers.Id, Sellers.Name} ].
I wish to map this to a DTO, I am not able map this result which has different nodes and labels to a DTO/POJO class.
As you have already noticed, Spring Data Neo4j is more strict when it comes to map "arbitrary" data that is not directly applicable to one domain entity.
But on the other hand Spring Data Neo4j also offers support for mapping loose data with the Neo4jClient.
Example:
class SoldProductInformation {
String productName;
Set<SellingInformation> sellingInformation;
}
class SellingInformation {
String listingsName;
String listingsId;
String sellerName;
String sellerId
}
neo4jClient.query("...return product.name as productName, someListWithTheInformationFromTheQuestion")
.fetchAs(SoldProductInformation.class)
.mappedBy((TypeSystem t, Record record) -> {
String productName = record.get("productName").asString();
List<SellingInformation> sellingInformations = record.get("someListWithTheInformationFromTheQuestion").asList(value -> {
String listingsName = value.get("listingsName").asString();
// same for listingsId, sellerName, sellerId...
return new SellingInformation(....);
});
return new SoldProductInformation(....);
})
If you have more entity aligned fields and/or maybe return also nodes, you can make use of the derived mapping function:
BiFunction<TypeSystem, MapAccessor, Product> mappingFunction = neo4jMappingContext.getRequiredMappingFunctionFor(Product.class);
and apply it via
neo4jClient.query("...return product,...")
.fetchAs(SoldProductInformation.class)
.mappedBy((TypeSystem t, Record record) -> {
Product product = mappingFunction.apply(typeSystem, record.get("product"));
String productName = product.getName();
// ....
see https://github.com/spring-projects/spring-data-neo4j/issues/2288#issuecomment-861255508 for a complete example.

How to add a String Array item in Java's Switch's Case Value?

We know that since Java 7, Switch's expression can be a String. So I was making an app, where, when a user selects a category he/she will be assigned the concerned department as per the category value. Here's the code:-
public class Selector {
///String array to save the departments
private final static String[] DEPTS = {
"A",
"B",
"C",
"D"
};
//String array for the categories
private final static String[] CATEGORY = {
"Wind",
"Air",
"Fire",
"Cloud",
"River",
"Tree",
"Abc",
"Def"
};
//return the department when user selects a particular category item from above
public static String setDepartment(String category) {
switch(category){
case "Wind":
return DEPTS[0];
case "Air":
return DEPTS[1];
case "Fire": case "Cloud": case "River":
return DEPTS[2];
case "Tree": case "Abc": case "Def":
return DEPTS[3];
}
return null;
}
}
So I was thinking just how I can return the department item using the array index of department, can I use the same thing in the case value, like,
case CATEGORY[0]: case CATEGORY[1]:
return DEPTS[2];
Cause if the category items contain a large string than the cases will become too long to write. If java doesn't allow this, can you suggest some other way so that my code doesn't become cumbersome? Thanks.
Why don't you use a enum to do that.
public class Selector {
private enum DepartmentCategory = {
Wind("A"),
Air("B"),
Fire("C"),
Cloud("C"),
River("C"),
Tree("D"),
Abc("D"),
Def("E");
private String department;
DepartmentCategory(String department) {
this.department = department;
}
public String getDepartment() {
return department;
}
};
}
Now if you are given a department, you can easily get the category by the following code.
String category = "Wind";
DepartmentCategory dc = DepartmentCategory.valueOf(category);
dc.getDepartment(); // Returns the department
You could use a Map<String, String> to map the category to the department.
Then instead of a switch, you'll have to use map.get(category) which will return the department.
You could make it a Map<String, List<String>> to map the department to the categories, and do
for(String dept : map.keySet())
{
if(map.get(dept).contains(category))
{
return dept;
}
}
Edit: With enums, that works well if there will be no extra departments or categories in the future. A map allows a more dynamic approach. Both work well, if used correctly
I would use a Map<String, String> to store your associations between department and category rather than a switch statements. It seems like you are trying to use an Array[] for a purpose in which it wasn't intended.
If you use a Map<String, String> then you can store your data as follows:
Map<String, String> departments = new HashMap<String, String>();
departments.add("Wind", "A")
departments.add("Fire", "B")
departments.add("Fire", "C")
departments.add("River", "C")
departments.add("Cloud", "C")
You can then easily retrieve departments names by using the syntax:
String category = "Cloud"
String department = departments.get(category)
If you want to associate multiple departments you can use Map<String, List<String>> to represent a relationship between a type and category and multiple departments.
Traditionally if you think about representing this in a database you would be using a map with an index and an object underneath.
I don't think you want to use Arrays to do this in your case.

How to initialise a Map<K, Map<K,V>> on a single line

Is it possible to combine these two lines of code into one?
allPeople.put("Me", new HashMap<String, String>());
allPeople.get("Me").put("Name", "Surname");
The literal replacement of these two lines would be (in Java 8+):
allPeople.compute("Me", (k, v) -> new HashMap<>()).put("Name", "Surname");
or, in the style of Bax's answer, for pre-Java 9, you could use:
allPeople.put("Me", new HashMap<>(Collections.singletonMap("Name", "Surname")));
Starting with Java 9 there is a JDK provided Map factory
allPeople.put("Me", Map.of("Name", "Surname"));
You should probably represent a person as an object. That way you cannot call get("someKey") on a key that does not exist and your code blow up. That is the idea of object oriented programming. To encapsulate related data and functionality. Nested maps does a similar thing, but it is more error prone. For a language that does not support objects, that makes sense. But representing a person as an object allows you to better control the fields the mapping has, thus making your code more error-free.
class Person {
private String name;
private String surname;
public Person(String name, String surname) {
this.name = name;
this.surname = surname;
}
}
Then you create a map that maps names to people:
Map<String, Person> allPeople = new HashMap<>();
// Create an object that represents a person
Person me = new Person("name", "surname");
// Map the string "me" to the object me that represents me
allPeople.put("ME", me);

A data structure similar to a HashMap with more than one "distinct" value

Is there any similar data structure that stores a key and unlike a HashMap<Key, Value> it stores more value regarded to a single key? If not, any suggestion for doing so? Because making several HashMap with same key and different values does not look so neat.
EDIT
When I said more than one value, I mean distinct value. For example, consider a map that has a key for each person and we wish to store persons Name and address and phone number.
Based on your edit you probably want to still store a single Value for each Key but make the value an Object. For example
public class PersonInfo {
private String name;
private String address;
private String phoneNumber;
etc...
}
and define your map as HashMap<Key, PersonInfo>
Check Guava's Multimap ! It let you have multiple values for the same key.
Example
Multimap<String, String> multimap = ArrayListMultimap.create();
multimap.put("Fruits", "Banana");
multimap.put("Fruits", "Apple");
multimap.put("Fruits", "Pear");
multimap.put("Vegetables", "Carrot");
// Getting values
Collection<string> fruits = myMultimap.get("Fruits");
Guava is a really useful library for java programmer you should definitely check it out !
Edit
Well after you edit you really just need a simple HashMap<Key, Person>. You can define your Person class like this:
public class Person {
private String name;
private String address;
private String phoneNumber;
public Person(String name, String address, String phoneNumber) {
this.name = name;
this.address = address;
this.phoneNumber = phoneNumber;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
public String getPhoneNumber() {
return phoneNumber;
}
}
Use your HashMap<Key, Person> where Key could be a String for example:
Map<String, Person> personMap = new HashMap<String, Person>();
personMap.put("patrick_key", new Person("Patrick", "1000 Notre-Dame street", "123456789"));
personMap.put("stephan_key", new Person("Stephan", "2000 Notre-Dame street", "987654321"));
then you can access Patrick like that:
Person person = personMap.get("patrick_key");
Use a hashmap and each "value" is a list (linked or double linked on the basis of your needs).
A simple solution to this is to create some form of PersonInfo object, then map that value along with the key String. When you want to call the distinct key, you can just retrieve the object which contains any value you wish to describe about that person.
If you then want them to be distinct, you can record each phone number on a separate list and any time the user inputs a number, you can just check it against the global list of numbers.
(address/name's can be used twice because they're fairly common ie. think Junior and Senior living in the same home)

How to search a property in a list of beans?

I have a list of Order objects -
class Order {
Date date;
float amount;
String companyCode;
}
List<Order> orders = /* Initialize with list of order objects with valid data */
I have a list of Company objects -
class Company {
String name;
String code;
String address;
}
List<Company> companies = /* Initialize with list of company objects with valid data */
I need a to create a map of companyCode and name.
Is there some library that would allow me to write code like this (where BeanSearch is the hypothetical library class)?
Map<String, String> codeAndName = new HashMap<String, String>();
for(Order o: orders) {
codeAndName.put(o.getCompanyCode(),
BeanSearch.find(companies, "code", o.getCompanyCode).getName());
}
Alternatively is there another good way to do it?
http://commons.apache.org/collections/apidocs/org/apache/commons/collections/CollectionUtils.html should work for you right? Specifically you can use the find method

Categories

Resources