I am fetching attributes of an object as List, where AvsAttribute is as follows:
class AvsAttribute {
String attrName,
String value
}
I am fetching the attribute values for any entity as follows, as I did not want to use reflection, that's why did like that:
#Override
public List<AvsAttribute> getAttributes(List<String> attributeNameList, final #NonNull T entity)
throws JsonProcessingException, JSONException {
List<AvsAttribute> attributeList = new ArrayList<>();
objectMapper.setSerializationInclusion(Include.NON_NULL);
String jsonString = objectMapper.writeValueAsString(entity);
JSONObject jsonObject = new JSONObject(jsonString);
Iterator<String> keysItr = jsonObject.keys();
while (keysItr.hasNext()) {
String key = keysItr.next();
String value = jsonObject.get(key).toString().trim();
attributeList.add(AvsAttribute.builder().name(key).value(value).build());
}
if (CollectionUtils.isNotEmpty(attributeNameList)) {
return attributeList.stream().filter(item -> attributeNameList.contains(item.getName()))
.collect(Collectors.toList());
}
return attributeList;
}
But I want to make AvsAttribute generic like below:
class AvsAttribute<T> {
String attrName,
T value
}
But I cannot figure out what change should I do to the above getAttributes() function so that it works with above generic class.
I think it already works with a generic type parameter. You need to setup the .value(String value) function in AvsAttribute builder to safely parse a given string into type T.
Then, you can rest assured that the following line will correctly fetch the generic attribute value:
AvsAttribute.builder().name(key).value(value).build()
Does that make sense?
Anyway, Good luck with your code.
Use wildcard ? in attributeList declaration and return type.
Related
I simply have a Map. But it can return a Map, which may also return a Map. It's possible up to 3 to 4 nested Maps. So when I want to access a nested value, I need to do this:
((Map)((Map)((Map)CacheMap.get("id")).get("id")).get("id")).get("id")
Is there a cleaner way to do this?
The reason I'm using a Map instead of mapping it to an object is for maintainability (e.g. when there are new fields).
Note:
Map<String, Object>
It has to be Object because it won't always return a Hashmap. It may return a String or a Long.
Further clarification:
What I'm doing is I'm calling an api which returns a json response which I save as a Map.
Here's some helper methods that may help things seem cleaner and more readable:
#SuppressWarnings("unchecked")
public static Map<String, Object> getMap(Map<String, Object> map, String key) {
return (Map<String, Object>)map.get(key);
}
#SuppressWarnings("unchecked")
public static String getString(Map<String, Object> map, String key) {
return (String)map.get(key);
}
#SuppressWarnings("unchecked")
public static Integer geInteger(Map<String, Object> map, String key) {
return (Integer)map.get(key);
}
// you can add more methods for Date, Long, and any other types you know you'll get
But you would have to nest the calls:
String attrValue = getString(getMap(getMap(map, id1), id2), attrName);
Or, if you want something more funky, add the above methods as instance methods to a map impl:
public class FunkyMap extends HashMap<String, Object> {
#SuppressWarnings("unchecked")
public FunkyMap getNode(String key) {
return (FunkyMap)get(key);
}
#SuppressWarnings("unchecked")
public String getString(String key) {
return (String)get(key);
}
#SuppressWarnings("unchecked")
public Integer geInteger(String key) {
return (Integer)get(key);
}
// you can add more methods for Date, Long, and any other types you know you'll get
}
Deserialize into this class with your json library (you'll probably have to provide it with a factory method for the map class impl), then you can chain the calls more naturally:
String attrValue = map.getNode(id1).getNode(id2).getString(attrName);
The funky option is what I did for a company, and it worked a treat :)
If you don't know the depth of the JSON tree and if you worry about maintainability if new fields are added, I would recommend not to deserialize the full tree in a Map but instead use a low-level parser.
For example, if your JSON looks like the following:
{
"id": {
"id": {
"id": {
"id": 22.0
}
}
}
}
You could write something like that to get the id using Jackson:
public Object getId(String json) throws JsonParseException, IOException
{
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(json);
JsonNode id = root.get("id");
while (id != null && id.isObject())
{
id = id.get("id");
}
//Cannot find a JsonNode for the id
if (id == null)
{
return null;
}
//Convert id to either String or Long
if (id.isTextual())
return id.asText();
if (id.isNumber())
return id.asLong();
return null;
}
I want to use the method execute() of the following class:
public class Parser {
#Header("header1")
private String attribute1;
#Header("header2")
private String attribute2;
#Header("header3")
private String attribute3;
#Header("header4")
private String attribute4;
public String execute(String headerValue) {
//Execute
}
}
What I want this method to achieve is matching the headerValue parameter with one in the list of #Header annotations, and returning the value of the respective attribute. For example, if I call execute("header3"), it should return the value of attribute3
How can I achieve this? Or is it a better way to code this requirement?
Why don't you just use a map for this? You'd need one anyways in order to store the mapping of the annotation parameter value to the field but if you can do this without reflection it should be easier to code and to maintain.
What I mean is:
Map<String, String> attributes; //initialized
attributes.put("header1", value1);
...
In execute() you then just access the map.
You could improve this using an enum, e.g. in order to restrict the number of possible values.
Something like this:
enum HeaderType {
HEADER1,
HEADER2,
...
}
private Map<HeaderType, String> headerAttribs = ...;
void setAttrib( HeaderType type, String value ) {
headerAttribs.put(type, value);
}
String getAttrib( HeaderType type ) {
return headerAttribs.get(type);
}
public String execute(HeaderType type ) {
//Execute
}
If you need to use a string for the header type you could consider employing an additional map string->header type to look up the correct type first.
Alternatively you could use a switch statement which since Java 7 should work with strings as well.
Try this:
public String execute(String headerValue) throws IllegalArgumentException, SecurityException, IllegalAccessException, NoSuchFieldException {
for(Field field:this.getClass().getFields()) {
if (field.isAnnotationPresent(Header.class)) {
Header annotation = field.getAnnotation(Header.class);
String name = annotation.value();
if(name.equals(headerValue)) {
Object val = this.getClass().getField(name).get(this);
return (String) val;
}
}
}
return null;
}
There are a couple of exception to handle in line:
Object val = this.getClass().getField(name).get(this);
You can return null for that exception if you don't want to throw it from this method.
This may help you
Field f[]= Parser.class.getDeclaredFields();
for (int i = 0; i < f.length; i++) {
Annotation annotation[]= f[i].getAnnotations();
for (int j=0;j<annotation.length;j++){
Class<Annotation> type = (Class<Annotation>) annotation[j].annotationType();
for (Method method : type.getDeclaredMethods()) {
if(method.getName() .equals(headerValue))
{
String name=f[i].getName();
return name;
}
}
}
}
Parser.class.getDeclaredFields() will include private fields also.
I have an object that represents an event that I would like to serialize into json, using gson or another library if that works easier.
I want to add the following type of field to the json:
private Map<String, String> additionalProperties;
And also in another use case:
private Map<String, Object> additionalProperties;
But if I add additionalProperties to the Event object, and build Gson in the normal way:
Gson gson = BUILDER.create();
String json = gson.toJson(event);
It will appear like so:
additional_properties: {"value1":1,"value2":"abc"}
I would simply like to append to the event object in the following form:
{"value1":1,"value2":"abc"}
Here is an example output- the additional properties added are the object 'z' and the object 'advertiser':
{"organisationid":"2345612ß","projectid":"12345678",
"place":{"placeId":"2345","last_place":"123-3"},
"advertiser":{"advertiserId":"2345a","code":"a123-3"},
"user":{"isY":false,"isHere":false,"isBuyer":false},
"x":{"identifier":"SHDG-28CHD"},
"z":{"identifier":"abcSHDG-28CHD"},
"event_type":"x_depart"}
Here is what it currently looks like:
{"organisationid":"2345612ß","projectid":"12345678",
"place":{"placeId":"2345","last_place":"123-3"},
additionalproperty: {"advertiser":{"advertiserId":"2345a","code":"a123-3"},
"user":{"isY":false,"isHere":false,"isBuyer":false},
"x":{"identifier":"SHDG-28CHD"},
additionalproperty: {"z":{"identifier":"abcSHDG-28CHD"}},
"event_type":"x_depart"}
The best way to solve this would be with a framework for adding properties dynamically, but without this, you could add the additional properties to your Event object (include #JsonIgnore
so that it is not part of the final json), create a new JSONObject from the additional properties and merge it to the event JSONObject before serializing to json. This way the additional properties are added dynamically to the resulting Event output.
In the Event class:
#JsonIgnore
private Map<String, Object> additionalProperties;
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
public void setAdditionalProperties(Map<String, Object> additionalProperties) {
this.additionalProperties = additionalProperties;
}
A function to merge two JSONObjects:
public static JSONObject mergeObjects(JSONObject source, JSONObject target) throws JSONException {
for (String key: JSONObject.getNames(source)) {
Object value = source.get(key);
if (!target.has(key)) {
// new value for "key":
target.put(key, value);
} else {
// existing value for "key" - recursively deep merge:
if (value instanceof JSONObject) {
JSONObject valueJson = (JSONObject)value;
deepMerge(valueJson, target.getJSONObject(key));
} else {
target.put(key, value);
}
}
}
return target;
}
Putting the two objects together:
String jsonAdd = mapper.writeValueAsString(additional);
String jsonEvent = mapper.writeValueAsString(event);
JSONObject jsonAddObj = new JSONObject(jsonAdd);
JSONObject JsonEventObj = new JSONObject(jsonEvent);
JSONObject finalJson = Merge.deepMerge(jsonAddObj, JsonEventObj);
If your Event class is something like;
class Event {
private Map<String, Object> additionalProperties;
}
and you are calling;
String json = gson.toJson(event);
The ecpected and the valid output would be:
{"additionalProperties":{"value1":1,"value2":"abc"}}
If you want an output such as;
{"value1":1,"value2":"abc"}
You can call;
String json = gson.toJson(event.additionalProperties);
I would simply like to append to the event object in the following
form:
{"value1":1,"value2":"abc"}
That way it won't be a valid json variable if you append that value directly to a json object. You should better try if the json value you want is valid or not at http://jsonviewer.stack.hu/
I have a class like this:
public class DeserializedHeader
int typeToClassId;
Object obj
I know what type of object obj is based on the typeToClassId, which is unfortunately only known at runtime.
I want to parse obj out based on typeToClassId - what's the best approach here? Annotations seem like they're out, and something based on ObjectMapper seems right, but I'm having trouble figuring out what the best approach is likely to be.
Something along the lines of
Class clazz = lookUpClassBasedOnId(typeToClassId)
objectMapper.readValue(obj, clazz)
Obviously, this doesn't work since obj is already deserialized... but could I do this in 2 steps somehow, perhaps with convertValue?
This is really complex and painful problem. I do not know any sophisticated and elegant solution, but I can share with you my idea which I developed. I have created example program which help me to show you how you can solve your problem. At the beginning I have created two simple POJO classes:
class Product {
private String name;
// getters/setters/toString
}
and
class Entity {
private long id;
// getters/setters/toString
}
Example input JSON for those classes could look like this. For Product class:
{
"typeToClassId" : 33,
"obj" : {
"name" : "Computer"
}
}
and for Entity class:
{
"typeToClassId" : 45,
"obj" : {
"id" : 10
}
}
The main functionality which we want to use is "partial serializing/deserializing". To do this we will enable FAIL_ON_UNKNOWN_PROPERTIES feature on ObjectMapper. Now we have to create two classes which define typeToClassId and obj properties.
class HeaderType {
private int typeToClassId;
public int getTypeToClassId() {
return typeToClassId;
}
public void setTypeToClassId(int typeToClassId) {
this.typeToClassId = typeToClassId;
}
#Override
public String toString() {
return "HeaderType [typeToClassId=" + typeToClassId + "]";
}
}
class HeaderObject<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
#Override
public String toString() {
return "HeaderObject [obj=" + obj + "]";
}
}
And, finally source code which can parse JSON:
// Simple binding
Map<Integer, Class<?>> classResolverMap = new HashMap<Integer, Class<?>>();
classResolverMap.put(33, Product.class);
classResolverMap.put(45, Entity.class);
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
String json = "{...}";
// Parse type
HeaderType headerType = mapper.readValue(json, HeaderType.class);
// Retrieve class by integer value
Class<?> clazz = classResolverMap.get(headerType.getTypeToClassId());
// Create dynamic type
JavaType type = mapper.getTypeFactory().constructParametricType(HeaderObject.class, clazz);
// Parse object
HeaderObject<?> headerObject = (HeaderObject<?>) mapper.readValue(json, type);
// Get the object
Object result = headerObject.getObj();
System.out.println(result);
Helpful links:
How To Convert Java Map To / From JSON (Jackson).
java jackson parse object containing a generic type object.
I'm having a bit of a problem understanding how i should configure the objectMapper and pojo when deserializing. My Json is created by another application that
supports both xml and json. It returns a list with myobject, but the Json contains the type, like this:
[
{
"myobject": {
"somethingcool": "amazing",
"contactPersonsForMyObject": [
"test.test#gmail.com",
"test#test.se"
],
"myObjectId": "c85e48730501bfae41e67714c6131b7d"
}
},
{
"myobject": {
"somethingcool": "cool",
"contactPersonsForMyObject": [
"test.test2#gmail.com",
"test#test2.se"
],
"myObjectId": "c85e48730501bfae41e67714cqwerty"
}
}
]
My class:
public class MyObject {
private String myObjectId;
private String somethingcool;
private List<String> contactPersonsForMyObject;
public String getMyObjectId() {
return myObjectId;
}
public void setMyObjectId(String myObjectId) {
this.myObjectId = myObjectId;
}
public String getSomethingcool() {
return somethingcool;
}
public void setSomethingcool(String somethingcool) {
this.somethingcool = somethingcool;
}
public List<String> getContactPersonsForMyObject() {
return contactPersonsForMyObject;
}
public void setContactPersonsForMyObject(List<String> contactPersonsForMyObject) {
this.contactPersonsForMyObject = contactPersonsForMyObject;
}
}
But when doing:
List<MyObject> myObjects = mapper.convertValue(rootNode, new TypeReference<List<MyObject>>() {});
I'm getting a exception stating:
java.lang.IllegalArgumentException: Unrecognized field "myobject" (Class com.domain.MyObject), not marked as ignorable
at [Source: N/A; line: -1, column: -1] (through reference chain: com.domain.MyObject["myobject"])
It's like the mapper do not understand the extra "layer".
When serializing to get this structure it is possible to configure the mapper like this: mapper.configure(SerializationConfig.Feature.WRAP_ROOT_VALUE, true);
So there should be somehow to do the reverse?
Thank you!
You need to give it concrete classes and not interfaces. So
List<Map<String, MyObject>> myObjects = mapper.readValue(json, new TypeReference<ArrayList<HashMap<String, MyObject>>>() {
});
What you need is to use #JsonTypeInfo annotation on type (class), which will include additional type information. In your case it looks as if you wanted to include a type id as property key.
If so, inclusion method should be "as wrapper object", and you will also need to define what type id of "myobject" binds to -- this can be done by adding #JsonTypeName("myobject") for MyObject class (it needs to be included in subtype of whatever has #JsonTypeInfo, but in this case both would be added for the same class).
Your json has an extra level of nesting: you have a list of Maps of Strings to MyObjects, not a List of MyObjects. You'd need to read it like this:
List<Map<String, MyObject>> myObjects = mapper.readValue(json, new TypeReference<List<Map<String, MyObject>>>() {
});
Or else change whatever is generating this json to ditch the inner Map (IMHO that'd be better).
Change List<String> to ArrayList<String>
and then
MyObject myObject = mapper.readValue(json, MyObject.class);
Add the following constructor to MyObject class
#JsonCreator
public MyObject(#JsonProperty("myObjectId") String myObjectId,
#JsonProperty("somethingcool") String somthingcool,
#JsonProperty("contact") ArrayList<String> contactPersonsForMyObject) {
this.myObjectID = myObjectId;
this.somethingcool = somethingcool;
this.contactPersonsForMyObject = contactPersonsForMyObject;
}
and change the return value for the getter to ArrayList