Cherry pick attributes from an object in a generic way - java

I want to build a data aggregator which calls multiple services and extract some attributes from their response and then build the aggregated object.
Please consider the below example:
To fetch order details, I need to make a call to GetOrderDetails API of OrderService, which returns below output (simplified one):
{
statusCode: OK,
orderDetails: {
orderId: "order_id",
offer: {
offerId: "offer_id",
offerType: "offer_type"
},
address: {
addressId: "address_id",
houseNo: "house_no",
city: "city",
state: "state"
}
}
}
Similarly, I need to make a call other services.
Problem
I need to build output object by cherry picking the attributes from the response of these services without hard coding the logic through getters of the concerned attributes.
I am planning to have a list of needed attributes and their respective paths stored as config and then extracting logic would be generic.
Well, config will look like:
output_attribute: dotted_hierarchy_path
orderId: orderDetails.orderId
addressId: orderDetails.address.addressId
So, my output object will be:
{
orderId: <order_id>,
addressId: <address_id>
}
If I have this kind of configuration, my java code would be generic i.e. it can cherry pick any attribute from any object.
To extract the required attributes, I am thinking is to traverse the dotted path and through Java Reflection get the value. But Java Reflection is slow.
This is a practical world problem and hence want folks to put their valuable approaches.

As you said, reflection is slow and tricky. I wouldn't try to reimplement it from scratch but rather use existing libraries, such as FasterXML/jackson. And I wouldn't worry about performance for now.
In general, I would try to convert objects to nested maps. If you have access to plain jsons, you don't need to map them to objects, this way you skip reflection:
Map<String, Object> root = objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {});
If you have to deal with objects, you can convert them to nested maps in a similar way:
Map<String, Object> root = objectMapper.convertValue(object, new TypeReference<Map<String, Object>>() {});
Getting values by dotted_hierarchy_path from the nested maps should be pretty easy.

Related

Deserialize YAML map of POJOs with Jackson where Map keys are an object field

Given class:
#Data
class Widget {
String name;
int price;
}
And the following yaml file:
Widget1:
price: 5
Widget2:
price: 6
Widget3:
price: 7
Using Jackson I want to deserialize this into a Map<String, Widget> where the widget name field is set to the corresponding map key. The following snippet works but has the downside of preventing use of immutable object types such as lombok #Value. Another inelegant solution I considered was creating a separate immutable WidgetWithName class that is constructed after jackson deserialization. If someone can propose a better approach, that would be of interest.
Map<String, Widget> getWidgets(String yaml) throws Exception {
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
TypeFactory typeFactory = mapper.getTypeFactory();
MapType mapType = typeFactory.constructMapType(HashMap.class, String.class, Widget.class);
map = mapper.readValue(yaml, mapType);
map.forEach((k, v) -> v.setName(k); // so much for immutability
return map
}
Jackson will not help you because it is a generalization over YAML and JSON (and XML or so I heard) and therefore provides less functionality than directly using SnakeYAML.
With SnakeYAML, you could theoretically implement this with custom constructors, but it would need a lot of knowledge about the deserialization process because YAML is not designed to merge nodes on different levels into one value.
Frankly, having a separate class is by far the simplest and best maintainable solution. A separate class named WidgetBuilder, with only the price field and a function Widget finish(String name) would allow you to
return map.entrySet().stream().map((k, v) -> v.finish(k)).collect(
Collectors.toMap(Widget::getName, Function.identity()));
which seems elegant enough. (You can collect to a list instead if you don't need the map anymore.)

Should the Object class ever be referenced in Java?

I found this part of code:
Map<String, Object> myMap = ...
It seems like the above should replace Object with some abstract class or interface that provides more structure for the value in the map. Is there any good reason to directly reference the Object class?
API calls/responses (consuming/producing JSON) will always have a String key but the value may be text, numeric, boolean, array, or an object.
In the specific case of my project we use Spring MVC (which uses Jackson under the hood). Our controllers always consume domain objects directly, e.g. an instance of the User class. Processing a Map with more than a couple of keys is a chore and prone to error.
We frequently return Map<String, Object> because responses almost always include metadata that is generated when the request is made. For example, a GET request made to myapp/api/users might return something like:
{
count: 2,
timestamp: '2020-11-06T17:24:12.123Z',
users: [
{id: 1, firstName: 'Alice', lastName: 'Ackbar'},
{id: 2, firstName: 'Boba', lastName: 'Bling'}
]
}
While the users property contains serialized User entities the remaining fields exist solely for the response. There is no point to creating a UsersResponseEntity class.

Modeling JSON in Java

The few times I've worked with Java/Rest/JSon, JSON elements have always been built in camelCase format.
For example:
"someField": {
"someSonField1": "20191106",
"someSonField2": "20201119",
...
}
However, in a functional document they have passed me to build a Rest JSon client, they use this notation:
"some_field": {
"some_son_field_1": "20191106",
"some_son_field_2": "20201119",
...
}
Is it expressed somewhere that Java has to use the notation 1?.
It seems to me that if it is done this way, everything goes much more smoothly when modeling the objects:
#XmlRootElement(name = "someField")
#XmlType(propOrder = {"someSonField1", "someSonField2"})
public class someField {
private String someSonField1;
private String someSonField2;
//...
}
Thanks!
Q: Is it expressed somewhere that Java has to use the notation?
A: No: it's 100% "convention", not mandatory.
As it happens, the standard convention for both JSON (a creature of Javascript) and Java is camelcase. For example: Java Naming Conventions.
some_son_field_1 is an example of snake case. It's associated with classic "C" programs. It's also common (but NOT universal) with XML. It, too, is a "convention" - not a requirement.
I'm curious why you're choosing XML bindings for JSON data. Have you considered using Jackson?
Finally, this article might be of interest to you:
5 Basic REST API Design Guidelines
I see you're using javax.xml.bind package? Have you tried #XmlElement?
#XmlRootElement(name = "someField")
#XmlType(propOrder = {"some_son_field_1", "some_son_field_2"})
public class someField {
#XmlElement(name="some_son_field_1")
private String someSonField1;
#XmlElement(name="some_son_field_2")
private String someSonField2;
//...
}
Not sure, probably you should try putting them on getters, as your fields are private.
Or you could use unify-jdocs, a library which I created to read and write JSON documents without using any POJO objects. Rather than defining POJO objects, which we know can be difficult to manage in case of complex documents and changes to the JSON document, just don't use them at all. Directly read and write paths in the JSON document. For example, in your snippet, you could read and write the fields as:
Document d = new JDocument(s); // where s is a JSON string
String s1 = d.getString("$.some_field.some_son_field_1");
String s2 = d.getString("$.some_field.some_son_field_2");
You could use a similar way to write to these paths as so:
d.setString("$.some_field.some_son_field_1", "val1");
d.setString("$.some_field.some_son_field_2", "val2");
This library offers a whole lot of other features which can be used to manipulate JSON documents. Features like model documents which lock the structure of documents to a template, field level validations, comparisons, merging documents etc.
Obviously, you would consider it only if you wanted to work without POJO objects. Alternatively, you could use it to read and write a POJO object using your own method.
Check it out on https://github.com/americanexpress/unify-jdocs.

Good practices in REST / Java

I'm currently trying to acquire skills in REST, and specifically in "good" Rest, hypermedia and all the good practices that comes with it.
In order to do so, I was asked to develop a prototype REST server containing data of my choice and implementing everything I'll have to use in a real project coming after that.
So I made a server using Spring boot and Jackson for json handling.
My data architecture is close to this : I have a collection of LaunchVehicle (I like space =D) like Ariane V, Falcon 9, etc. I can retrieve the JSON object flawlessly
{ "name":"Ariane V","country":"Europe","firstFlight":null,"GTO_Payload":1.0,"LEO_Payload":2.3,"weight":136.0 }
The thing is, I'd like to add a "space agency" field which would be an object containing some Strings and Floats, inside my LaunchVehicle. However, when the client retrieve a LaunchVehicle, I don't want it to retrieve the full SpaceAgency object, just the name for exemple. From here, he would be able to follow the link to the space agency via an hypermedia link included in the response it would have received.
How can I do this ? Right now I'm only able to send to the client my full LaunchVehicle object with the SpaceAgency object and all his fields. Is there any annotations doing what I want ? Thanks ;)
public class LaunchVehicle {
private String name;
private String country;
private Date firstFlight;
private Map<String, Float> characteristics;
private SpaceAgency spaceAgency;
#JsonCreator
constructor...
#JsonProperty(required=false)
getters and setters...
}
Thanks a lot, don't hesitate if I'm not precise or understandable enough.
Try #JsonIgnoreProperties annotation at the class level. That should provide you the feature that you want.
Otherwise, you could always use some kind of DTO object to create your response model, and there just have the fields that are going to be used at the API layer.
I would rather prefer to use an appropiate DTO/ApiModel for your API layer than having a full domain object with JSON annotations in it.
If your SpaceAgency class only defines the properties that you need to deserialize, Jackson will only deserialize those. It will forget the unmapped properties.
Try jax-ws-rs!
It's a standart REST implementation in Java.
Oracle docs
Very good tutorial by Mkyong
You can use the Gson API for this
JsonParser parser = new JsonParser();
JsonObject obj = parser.parse(spaceAgency).getAsJsonObject();
String agencyName = obj.get("agencyName").getAsString();
I think you should reference the space agency as a hyperlink.
So the JSON will look like:
{ "name":"Ariane V",
"country":"Europe",
< other fields omitted >
"_links": {
"agency": { "href": "agencies/ESA" },
< other links omitted >
}
}
To achieve this you need to specify the link in your data transfer object. Don't make this a reference to an actual object of that type -- to do so would mean populating that object, even when the client doesn't ask for it.
How you achieve this depends on what technology you're using. In Jersey it's
public class LaunchVehicle {
...
#InjectLink(resource=AgencyResource.class)
URI agencyLink;
...
}
https://jersey.java.net/documentation/latest/declarative-linking.html
Linking like this is what "real" REST is all about. However note that plenty of real-world solutions claim to be doing REST without actually using hyperlinks. A more hacky solution would be to have a String agencyId field in your JSON, which could be put into a URL template to get agency details.

JSON Polymorphism

I have a List of javascript objects on my client side, which are a list of "events" that a user has executed. When the user is ready, I want to send this to the server. The order of events is important, so preserving the list order is necessary.
What I would like to do is to have a JSON library (don't mind which one) to bind the JSON to some Event objects in my Java code, where Event is an abstract class, and I have 3 concrete classes that all extend Event (lets say EventA, EventB and EventC).
Ideal scenario would be something like
List<Event> events = jsonlibrary.deserialise(jsonString);
which may contain a list of items such as
[eventA, eventC, eventA, eventA, eventB]
Is this possible, or do I have to inspect the JSON tree manually, and deserialise the individual elements of the json array?
JSON objects are just key/value pairs and contain no type information. That means identifying the type of a JSON object automatically isn't possible. You have to implement some logic on the server-side to find out what kind of event you are dealing with.
I would suggest to use a factory method which takes a json string, parses it to find out what kind of Event it is, builds an Event object of the correct subclass and returns it.
You could use Genson library http://code.google.com/p/genson/.
It can deserialize to concrete types if the json was produced using Genson. Otherwise you only need to add something like [{"#class":"my.java.class", "the rest of the properties"}...]
// an example
abstract class Event {
String id;
}
class Click extends Event {
double x, y;
}
// you can define aliases instead of plain class name with package (its a bit nicer and more secure)
Genson genson = new Genson.Builder().setWithClassMetadata(true).addAlias("click",
Click.class).create();
String json = "[{\"#class\":\"click\", \"id\":\"here\", \"x\":1,\"y\":2}]";
// deserialize to an unknown type with a cast warning
List<Event> events = genson.deserialize(json, List.class);
// or better define to which generic type
GenericType<List<Event>> eventListType = new GenericType<List<Event>>() {};
events = genson.deserialize(json, eventListType);
EDIT
here is the wiki example http://code.google.com/p/genson/wiki/GettingStarted#Interface/Abstract_classes_support
Why not using jackson json library ?
It is a full Object/JSON Mapper with data binding functionnality.
It is fast, small footprint, documented, overused, and many others things you will enjoy!
I began a library that implements the desired fonctionality (for json and xml) if the json is encoded by the same library :
https://github.com/giraudsa/serialisation
to use it,
MyObject myObject = new SpecialisedObject();
String json = JsonMarshaller.ToJson(myObject);
MyObject myClonedObject = JsonUnMarshaller(json);

Categories

Resources