Jackson Generics with variable JsonProperty (usage with generics) - java

I have class that looks like this:
public class Data<U> {
#JsonProperty("difficulties")
private U[] data;
// ... geter setter constructor
}
And I don't want to create 10 more similar classes just because I need to change only one line of code (#JsonProperty("difficulties") in this case). The property value depends on Type. Is it possible to write it in one class?

Based on response of Jackson - Modify an attribute at runtime without annotation by Michał Ziober here I was able to change default field name values
by overriding PropertyNamingStrategy:
These are my received JSON examples (simplified):
{"status": "OK","error": null,"data": {
"difficulties": [{"value":"easy"},{"value":"medium"}]
}}
{"status": "ok", "error": null, "data": {
"countries": [{"code": "AT"},{"code": "BE"}]
}}
see the difference in second line where data object contains either difficulties
or countries (or many other names based on context).
Response class based on JSON response:
public class Response<T>{
private String status;
private String error;
private Data<T> data;
// Getters Setters Constructors
}
Data class based on JSON response:
public class Data<T> {
// property name, that will be changed
#JsonProperty(DataNamingStrategy.DATA_FIELD)
private T[] data;
// Getters Setters Constructors
}
And this is Naming strategy, that changes default value to runtime specified value
public class DataNamingStrategy extends PropertyNamingStrategy{
// used by other classes (this will be default field name that should be changed)
public static final String DATA_FIELD = "variable:data";
private String fieldName;
public DataNamingStrategy(String fieldName) {
this.fieldName = fieldName;
}
// use this to change field name (format "variable":"value") not needed in my case
#Override
public String nameForField(MapperConfig<?> config, AnnotatedField field,
String defaultName) {
return (defaultName.equals(DATA_FIELD))?
fieldName :
super.nameForField(config, field, defaultName);
}
// use this to change setter method field name (JSON -> Object with format "variable":{})
#Override
public String nameForSetterMethod(MapperConfig<?> config,
AnnotatedMethod method, String defaultName) {
return (defaultName.equals(DATA_FIELD))?
fieldName :
super.nameForGetterMethod(config, method, defaultName);
}
// use this to change getter method field name (Object -> JSON with format "variable":{})
// should be same as nameForSetterMethod
#Override
public String nameForGetterMethod(MapperConfig<?> config,
AnnotatedMethod method, String defaultName) {
return nameForSetterMethod(config, method, defaultName);
}
}
And usage should look like this:
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new DataNamingStrategy(tableName));
JavaType type = mapper.getTypeFactory().constructParametricType(Response.class, dataClass);
Response<U> u = mapper.readValue(result, type);
Where result is Json as String, tableName is String that will be used in JSON instead of default value and dataClass is class for U (for example Difficulty.class).
Better usage of PropertyNamingStrategy should be Map instead of one String. But I just needed to change one particular value.
Also have a look at PropertyNamingStrategy documentation or again at Michał Ziober's answer

You can use #JsonAnyGetter annotation.
public class Data<U> {
#JsonIgnore
private U[] data;
#JsonIgnore
private String propertyName;
public Data(String propertyName) {
this.propertyName = propertyName;
}
// ... geter setter
#JsonAnyGetter
public Map<String, Object> any() {
return Collections.singletonMap(propertyName, data);
}
}
And use it like below:
Data<Difficulties> difficulties = new Data<>("difficulties");
write whatever you want instead of "difficulties" string. Set your list to Data generic class instead of Difficulties object if you want

Related

Using #JsonAnyGetter /#JsonAnySetter to Serialize JSON and change property name (Jackson)

I have a JSON that looks like:
{"pfm1":{"status":true,"candid":true},
"pfm2":{"status":false,"candid":true},
"pfm3":{"status":false,"candid":true}}
I want to map it and change the property naming for pfm1,pfm2,pfm3 with a variable string name. Could you please indicate how it can be done in the class below.
The output shall be:
{"Idname01":{"status":true,"candid":true},
"Idname02":{"status":false,"candid":true},
"Idname03":{"status":false,"candid":true}}
Msg jsonobject=mapper.readValue(input, Msg.class);
Class
static class Msg {
#JsonIgnore
private Object pfm1;
#JsonIgnore
private Object pfm2;
#JsonIgnore
private Object pfm3;
private Map<String, Object> pfm = new HashMap<String, Object>();
public Object getPfm1() {return pfm1;}
public void setMpfm1(Object pfm1) {this.pfm1 = pfm1;}
public Object getPfm2() {return mpfm2;}
public void setPfm2(Object pfm2) {this.pfm2 = pfm2;}
public Object getPfm3() {return mpfm3;}
public void setPfm3(Object pfm3) {this.pfm3 = pfm3;}
#JsonAnySetter
public void set(String name, Object value) {
mpfm.put(name, value);
}
public Msg(){
}
}
Found solution for that without using getters, setters and #jsonanygetter. Just by using Object node. it is possible to manipulate your json structure including changing name property. it's much simpler and straightforward.
newNode.set("newname",node);
///convert constructed newNode to json
String jsonout = newNode.toString();
"newname" can be set as a stored variable. node is Object node extracted from top structure.

How to map an arraylist of hashmap to anothoer hashmap arraylist

I have a Switch that contains 13 case, each case executes a different sql request. I got the result in an ArrayList<HashMap<String, String>>. This result is supposed to be displayed with angular , for now i'm using this this.respTest = JSON.stringify(response); so it displays a list of "key":"value" .
My problem is since each request gets me different database fields and values ,so I want to merge some fields .
I created this class :
public class DataCollect {
private String type ;
private String entity ;
private String modPar ;
private String dateModif ;
private String numVersion ;
public DataCollect(String type, String entity, String modPar, String dateModif, String numVersion) {
this.type = type;
this.entity = entity;
this.modPar = modPar;
this.dateModif = dateModif;
this.numVersion = numVersion;
}
public DataCollect() {
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getEntity() {
return entity;
}
public void setEntity(String entity) {
this.entity = entity;
}
public String getModPar() {
return modPar;
}
public void setModPar(String modPar) {
this.modPar = modPar;
}
public String getDateModif() {
return dateModif;
}
public void setDateModif(String dateModif) {
this.dateModif = dateModif;
}
public String getNumVersion() {
return numVersion;
}
public void setNumVersion(String numVersion) {
this.numVersion = numVersion;
} }
In this class I'm supposed to affect the fields' names to the variables that I created and as a return an arraylist of hashmap with the data I extracted from the data base.
I mean I used to return for example "field-name":"value" , I want to return "type":"value","entity":"value" ..etc
I'm using springboot for the backend and angular 5 for the front.
Any help would be appreciated.
What you essentially want is a way to map keys in [each of] your hashmap to the corresponding member variable in the "DataCollect" POJO.
If there is a one to one mapping between the key present and corresponding member variable, you can expose a public constructor in "DataCollect" that takes in the hash map and constructs the corresponding object.
public DataCollect(Map<String, String> result) {
this.type = result.get("type");
this.entity = result.get("db_entity_key");
...
}
If there is no one on one mapping, you'd have to create a factory class, which takes your Map as an input and some context, and returns you the constructed DataCollect object.
Once you have the constructor or the factory class, you only need to iterate over your results list and do the needful to convert each Map into the DataCollect object.
Your controller should automatically serialise the DataCollect objects to corresponding JSON, or you can even use Jackson's ObjectMapper to achieve the same.

In fasterxml, after deserialization json, if enum is first property in class, other fields are null

In fasterxml, after deserialization json, if enum (with JsonFormat.Shape.OBJECT) is first property in class, other fields are null.
Why enum should be last declared property in class to deserialize other fields properly?
Maybe this could be a bug in fasterxml?
Example class MyClass:
public class MyClass {
// >>>
// >>> element field is null after deserialization
// >>>
private MyEnum option; // first
private String element; // --> null
// >>>
// >>> correctly deserialized if enum is last in order
// >>>
// private String element; // --> "elem"
// private MyEnum option; // last
public MyEnum getOption() {
return option;
}
public void setOption(MyEnum option) {
this.option = option;
}
public String getElement() {
return element;
}
public void setElement(String element) {
this.element = element;
}
}
Example enum MyEnum:
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum MyEnum {
FIRST;
#JsonProperty
public String getOption() {
return name();
}
#JsonCreator
public static MyEnum forValue(String option) {
return FIRST;
}
}
Example main test class Main:
public class Main {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MyClass myClass = new MyClass();
myClass.setElement("elem");
myClass.setOption(MyEnum.FIRST);
String serialized = mapper.writer().withDefaultPrettyPrinter().writeValueAsString(myClass);
System.out.println(String.format("serialized - %s", serialized));
MyClass deserialized = mapper.readValue(serialized, MyClass.class);
String deserializedResult = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(deserialized);
System.out.println(String.format("deserialized - %s", deserializedResult));
}
}
Output showing field is null after deserialization:
serialized - {
"option" : {
"option" : "FIRST"
},
"element" : "elem"
}
deserialized - {
"option" : {
"option" : "FIRST"
},
"element" : null
}
Output after fixing order (uncommented lines in MyClass):
serialized - {
"element" : "elem",
"option" : {
"option" : "FIRST"
}
}
deserialized - {
"element" : "elem",
"option" : {
"option" : "FIRST"
}
}
I couldn't tell you if it's a bug, you can debug and step through the code to understand how Jackson "fails" in this scenario. Your use of FAIL_ON_UNKNOWN_PROPERTIES hides the problem, which is using String as the parameter type of your forValue factory method. In short, Jackson gets "stuck" in traversing the tokens of the JSON content.
To fix it properly, ie. not rely on order, you have a couple of options. First, get rid of the JsonFormat.Shape.OBJECT shape for serializing the enum type and its corresponding #JsonCreator. The default serialization/deserialization for an enum is to use its name anyway.
Second, if you really want to keep the OBJECT shape, you'll need to change your #JsonCreator method to receive an ObjectNode, since that's what the JSON contains, not a String. From there, you can perform the deserialization yourself (assuming you have more enum constants)
#JsonCreator
public static MyEnum forValue(ObjectNode object) {
return MyEnum.valueOf(object.get("option").asText());
}

How can I find the JSON key of a field (jackson)?

I have the following problem: I export my user object as usual with the jackson.databind.ObjectMapper and that works fine: ({"address":{"village":"NY"},"prename":"Joe"}).
Now I have to get the key (for address and prename) with Java reflection.
If the field has the annotation #JsonProperty, there is no problem to get this key. But this annotation isn't pressent on all fields (for example the m_address field).
At How does the Jackson mapper know what field in each Json object to assign to a class object? I read that the ObjectMapper tries to call the getter or so.
But I have no clue how I can find the right getter to my field.
I know that this isn't probably the most beautiful way to solve my problem, but I haven't found any method on the ObjectMapper like: mapper.getJSONKeyByName(field).
If something like that exist even better. :)
Is there a way to find the right getter to a field and does something like mapper.getJSONKeyByName(field) exist on the ObjectMapper?
Main.java
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
// Object to JSON as usual
mapper.writeValue(System.out, new User("Joe", new Address("NY")));
// {"address":{"village":"NY"},"prename":"Joe"}
// Lookup with reflection
for (Field field : User.class.getDeclaredFields()) {
field.setAccessible(true);
try {
if (field.isAnnotationPresent(JsonProperty.class)) {
System.out.println("JSON-Key with annotation: " +
field.getAnnotation(JsonProperty.class).value());
// JSON-Key with annotation: prename
} else {
//TODO do something to get "JSON-Key without annotation: address
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
User.java
public class User implements Serializable {
#JsonProperty(value="prename")
#Validationinfo(name="prename", required=true, maxlenght=20)
private String m_name;
private Address m_address;
public User(String name, Address a) {
m_name = name;
m_address = a;
}
#JsonIgnore
public String getName() {
return m_name;
}
public void setName(String name) {
m_name = name;
}
public Address getAddress() {
return m_address;
}
public void setAddress(Address address) {
m_address = address;
}
}
Address.java
public class Address implements Serializable {
#JsonProperty(value="village")
#Validationinfo(name="village", required=false, maxlenght=10)
private String m_village;
public Address(String village) {
m_village = village;
}
public String getVillage() {
return m_village;
}
public void setVillage(String village) {
m_village = village;
}
}
EDIT:
The code is simplified. I have a REST service which does the writeValue part. The reflection part is done in a static recursive method on the User.
The thing is I have a custom Validationinfo annotation (with things like required, maxlength and so on) on my fields and also a name parameter. This name is the same as it is on the #JsonProperty annotation.
On an JavaScript application I want merge the value of the user fields with the ValidationInfos. For that I have to ensure that every validationInfos->name is unique. So I have to prefix the annotated Vaditioninfo->name whith the JSON serialized name/key of its parent (see in the REST respons "address.village").
The rest response I am locking for:
{
"user": {
"prename": "Joe",
"address" : {
"village": "NY"
}
}, "validationInfos": [{
"name": "prename",
"required": true,
"maxlenght": 10
}, {
"name": "address.village",
"required": false,
"maxlenght": 20
}]
}
In JavaScript I planning to do something like:
for (var i = 0; i < data.validationInfos.length;; i++) {
var element = data.validationInfos;
element.value = eval ("data.user." + element.name);
}
You should use jackson introspection instead of pure java reflection. It will allow you to discover json properties mapped to java fields/methods according to your serialization config.
JavaType userType = mapper.getTypeFactory().constructType(User.class);
BeanDescription introspection =
mapper.getSerializationConfig().introspect(userType);
List<BeanPropertyDefinition> properties = introspection.findProperties();
// do some processing over properties...

json request enum

We have RestWS where need to pass request in JSON format. This request contains different type of values such as String, List, enum etc.
We figured out how need to pass String and List (see below) but not sure how to pass enum in JSON request object.
Sample JSON Request for List and String in request:
{"firstparam":["195","196"],"secondparam":"test"}
First param is List and second param is String. Similarly we need to know how we can pass enum (also in the above request).
Sample enum class:
#XmlType(name = "Type")
#XmlEnum
public enum Type {
#XmlEnumValue("New")
NEW("New"),
#XmlEnumValue("Delete")
DELETE("Delete"),
#XmlEnumValue("Process")
PROCESS("Process");
private final String value;
WorkingStatusType(String v) {
value = v;
}
public String value() {
return value;
}
public static WorkingStatusType fromValue(String v) {
for (WorkingStatusType c: WorkingStatusType.values()) {
if (c.value.equals(v)) {
return c;
}
}
throw new IllegalArgumentException(v);
}
This Google JSON style guide might help you.

Categories