I have these 4 classes -
Component.java -> which will be annotated using custom annotation,
Properties.java -> the annotation class
Utilities.java -> class where the annotation is used
AnnotationProcessor.java -> where the annotation logic using reflection is built
What I want is, based on the annotation, I will set a property of the Component object.
This is my code -
//class which will be annotated using custom annotation
public class Component {
private String name;
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//annotation class
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Properties {
String compName();
}
//class where the annotation is used
public class Utility {
#Properties(compName = "myName")
Component comp1;
#Properties(compName = "myPassword")
Component comp2;
public void login() throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
comp1 = new Component();
comp2 = new Component();
comp1.setId("1111");
comp2.setId("2222");
AnnotationProcessor annotationProcessor = new AnnotationProcessor();
annotationProcessor.process(this, comp1);
annotationProcessor.process(this, comp2);
}}
//class where the annotation is processed
public class AnnotationProcessor {
public void process (Object callingClass, Component c){
System.out.println("In AnnotationProcessor:process");
Class<? extends Object> aClass = callingClass.getClass();
Field[] declaredFields = aClass.getDeclaredFields();
for (Field field: declaredFields) {
if(field.isAnnotationPresent(Properties.class)){
if (field.get(callingClass) != null) { //Added newly
Properties annotation = field.getAnnotation(Properties.class);
String val = annotation.compName();
c.setName(val);
field.set(callingClass, c); //Added newly
System.out.println("For field " + field.getName() + ", component id is : " + c.getId() + " and name is : --> " + c.getName());
break; //Added newly
}
}
}
}
I want to achieve to set the name of "comp1" to be "myName" and name of "comp2" to be "myPassword". Basically, the output I expect is this -
For field comp1, component id is : 1111 and name is : --> myName
For field comp2, component id is : 2222 and name is : --> myPassword
But the actual output is this (after //added newly changes)-
In AnnotationProcessor:process
For field comp1, component id is : 1111 and name is : --> myName
In AnnotationProcessor:process
For field comp1, component id is : 2222 and name is : --> myName
My problem is, how to assign the "name" of the component as per the annotation for that component object only and not for all the fields the Utility class has. I feel there is some if condition in the middle that might help achieve this, but not able to find it out.
Related
I have class hierarchy A <- B <- C. I don't want class A to be aware of C, so I intend to use A.type="B" to indicate it to be class B, and then B.type2="C" to indicate it to be class C.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
#JsonSubTypes({#JsonSubTypes.Type(value = B.class, name = "B")})
public abstract class A {
private final String type;
public A(String type) {
this.type = type;
}
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type2")
#JsonSubTypes({#JsonSubTypes.Type(value = C.class, name = "C")})
public class B extends A {
private final String type2;
private final String propertyB;
#JsonCreator
public B(#JsonProperty("type2") String type2,
#JsonProperty("propertyB") String propertyB) {
super("B");
this.type2 = type2;
this.propertyB = propertyB;
}
}
public class C extends B {
private final String propertyC;
#JsonCreator
public C(#JsonProperty("propertyB") String propertyB,
#JsonProperty("propertyC") String propertyC) {
super("C", propertyB);
this.propertyC = propertyC;
}
}
When I read JSON of C to class A, the actual Java object is class B but not C.
#Test
void whenReadCJsonToA_thenObjectIsInstanceOfC() throws JsonProcessingException {
String json = "{\n" +
" \"type\" : \"B\",\n" +
" \"type2\" : \"C\",\n" +
" \"propertyB\" : \"b\",\n" +
" \"propertyC\" : \"c\"\n" +
"}";
A obj = objectMapper.readValue(json, A.class);
assertTrue(obj instanceof B, "obj is not instance of B"); // pass
assertTrue(obj instanceof C, "obj is not instance of C"); // fail
}
One way to make above test pass is writing custom deserializer, but this solution is tedious if the class holds many fields.
Is it possible to make above test pass with a more elegant way? Is my indention to cascade #JsonTypeInfo and #JsonSubTypes completely wrong?
My maven project can be found in Github.
According to this Jackson issue, multiple level inheritance is supported with only one type discliminator property. In my code, keep only property type and remove property type2.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
#JsonSubTypes({#JsonSubTypes.Type(value = B.class, name = "B")})
public abstract class A {
private final String type;
public A(String type) {
this.type = type;
}
}
#JsonSubTypes({#JsonSubTypes.Type(value = C.class, name = "C")})
public class B extends A {
private final String propertyB;
#JsonCreator
public B(#JsonProperty("propertyB") String propertyB) {
super("B");
this.propertyB = propertyB;
}
}
public class C extends B {
private final String propertyC;
#JsonCreator
public C(#JsonProperty("propertyB") String propertyB,
#JsonProperty("propertyC") String propertyC) {
super(propertyB);
this.propertyC = propertyC;
}
}
See full code in this commit.
This commit uses enum as type discliminator property type.
I have some classes like below:
#Getter
#Setter
class Person{
#JsonProperty("cInfo")
private ContactInformation contactInfo;
private String name;
private String position;
}
#Getter
#Setter
class ContactInformation{
#JsonProperty("pAddress")
private Address address;
}
#Getter
#Setter
class Address{
private String street;
private String district;
}
And what I am going to do is writing an Utils method for the Person object that take one parameter which is the attributeName as String and return the getter value for this attribute.
Ex:
attributeName = name -> return person.getName()
attributeName = position -> return person.getPosition()
attributeName = cInfo.pAddress.street -> return person.getContactInfo().getAddress().getStreet()
attributeName = cInfo.pAddress.district -> return person.getContactInfo().getAddress().getDistrict()
Below is what I've done: I loop through all the fields in the Person object and check if the attributeName equal to either the JsonProperty's Name or the Field's Name then I will return this getter.
Object result;
Field[] fields = Person.class.getDeclaredFields();
for (Field field : fields) {
JsonProperty jsonProperty = field.getDeclaredAnnotation(JsonProperty.class);
if (jsonProperty != null && jsonProperty.value().equals(attributeName)) {
result = Person.class.getMethod("get" + capitalize(field.getName())).invoke(person);
} else {
if (field.getName().equals(attributeName)) {
result = person.class.getMethod("get" + capitalize(field.getName()))
.invoke(person);
}
}
}
This worked but only with the fields that locate direct in the Person class, ex: name, position. With the fields inside of contactInfo or address I am still getting stuck there. Can anyone give me some hint here how can I do it?
Thank you!
Because path like a.b.c related to different objects. So you need to. split by point and for each token call get and use obtained result for next token
UPDATE: something like:
private static Object invkGen(Object passedObj, String attributeName) throws Exception {
final String[] split = attributeName.split("\\.");
Object result = passedObj;
for (String s : split) {
if (result == null) {
break;
}
result = invk(result, s);
}
return result;
}
private static Object invk(Object passedObj, String attributeName) throws Exception {
Object result = null;
final Field[] fields = passedObj.getClass().getDeclaredFields();
for (Field field : fields) {
JsonProperty jsonProperty = field.getDeclaredAnnotation(JsonProperty.class);
if (jsonProperty != null && jsonProperty.value().equals(attributeName)) {
result = Person.class.getMethod("get" + capitalize(field.getName())).invoke(passedObj);
} else {
if (field.getName().equals(attributeName)) {
result = passedObj.getClass().getMethod("get" + capitalize(field.getName()))
.invoke(passedObj);
}
}
}
return result;
}
I'm wondering if there is any legit way to dynamically allocate name of JsonProperty so I would change it over time when needed ? With that being said I mean having :
#JsonIgnoreProperties(ignoreUnknown = true)
public class Record
{
public String Name;
#JsonIgnoreProperties(ignoreUnknown = true)
public static class QueryResult<T>
{
public List<T> records;
}
public static class QueryResultRecord extends QueryResult<Record>
{
}
}
Like above, I have a property Name, which by default will be named "Name" like this:
[
{
Name: "Test",
},
{
Name: "test",
},
]
Even though I have flexibility to use #JsonProperty("name") that's not a solution. What I am after is changing it multiple times when needed as I have some parameterized query which relies on it. So I would like to have Name, FirstName, LastName and so on. Is refletion api the right thing to use it here ?
The easiest legit way is to write custom AnnotationIntrospector:
import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
public class MyJacksonAnnotationIntrospector extends JacksonAnnotationIntrospector
{
private static final long serialVersionUID = 1L;
#Override
public PropertyName findNameForSerialization(Annotated a) {
PropertyName pn = super.findNameForSerialization(a);
if (pn.getSimpleName().equals("Name")) {
return pn.withSimpleName("LastName"); // set property name to your heart's content...
}
return pn;
}
}
and then pass it to the jackson mapper:
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new MyJacksonAnnotationIntrospector());
Record r1 = new Record();
mapper.writeValue(System.out, r1);
Note: the same introspector is used during deserialization.
I didn't found any simple way to do it but you can use a custom JsonSerializer and implement your logic in it :
// Record class
#JsonIgnoreProperties(ignoreUnknown = true)
public class Record {
protected String name;
public Record(String name) {
this.name = Name;
}
// ...
}
// RecordJsonSerializer class
public static class RecordJsonSerializer extends JsonSerializer<Record> {
private static final String[] NAMES = new String[]{
"Name",
"FirstName"
// ...
};
protected int idx;
public RecordJsonSerializer() {
idx = 0;
}
#Override
public void serialize(Record r, JsonGenerator jg, SerializerProvider sp) throws IOException, JsonProcessingException {
jg.writeStartObject();
jg.writeObjectField(NAMES[idx++], r.name); // Change the field name
jg.writeEndObject();
}
}
// Use case
Record[] records = new Record[]{
new Record("r0"),
new Record("r1")
};
ObjectMapper mapper = new ObjectMapper()
.registerModule(
new SimpleModule("Record")
.addSerializer(Record.class, new RecordJsonSerializer())); // Register the serializer instance
System.out.println(mapper.writeValueAsString(records));
The output of this is: [{"Name":"r0"},{"FirstName":"r1"}]
Of course you must change the logic to define the property name to use when serializing the object (mine will crash with 3 records but it's just a simple example).
I created a custom annotation called CrudSearchable and have defined some attributes there. However, the attributes I am assigning are already visible from the bean. Is there a way I can grab these values without having to redefine them manually?
// Bean
public class MockUser {
#CrudSearchable(attribute = "name",
parentClass = MockUser.class)
private String name;
#CrudSearchable(attribute = "group",
parentClass = MockUser.class,
mappedClass = MockGroup.class)
private MockGroup group;
// Get/Set, Equals/Hashcode, etc...
}
// Annotation Class
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface CrudSearchable {
String attribute();
boolean searchable() default true;
Class<?> mappedClass() default CrudSearchable.class;
Class<?> parentClass() default Object.class;
}
Where attribute is the attribute name, parentClass is the class literal using the annotation, and mapped class is the nested class object if applicatable.
MockUser obj = new MockUser();
Class<?> c = obj.getClass();
Field[] fields = c.getDeclaredFields();
CrudSearchable annotation = fields[0].getAnnotation(CrudSearchable.class);
System.out.println("attribute: " + annotation.attribute() +
"searchable: " + annotation.searchable());
Hope this helps
I found the answer on another Question. This is what I was looking for.
// Put in Reflector.java
/**
* Changes the annotation value for the given key of the given annotation to newValue and returns
* the previous value.
*/
#SuppressWarnings("unchecked")
public static Object changeAnnotationValue(Annotation annotation, String key, Object newValue){
Object handler = Proxy.getInvocationHandler(annotation);
Field f;
try {
f = handler.getClass().getDeclaredField("memberValues");
} catch (NoSuchFieldException | SecurityException e) {
throw new IllegalStateException(e);
}
f.setAccessible(true);
Map<String, Object> memberValues;
try {
memberValues = (Map<String, Object>) f.get(handler);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
Object oldValue = memberValues.get(key);
if (oldValue == null || oldValue.getClass() != newValue.getClass()) {
throw new IllegalArgumentException();
}
memberValues.put(key,newValue);
return oldValue;
}
Once I have this I can call the method below in the Constructor to alter the annotations I use.
// Put in Service Class
public void modifySearchable(Class<?> clazz) {
for(Field f : clazz.getDeclaredFields()){
CrudSearchable[] searchableArray = f.getDeclaredAnnotationsByType(CrudSearchable.class);
for(CrudSearchable searchable : searchableArray){
if(searchable == null){
continue;
}
Reflector.alterAnnotation(searchable, "attribute", f.getName());
Reflector.alterAnnotation(searchable, "parentClass", clazz);
if(!(searchable.mappedAttribute().equals(""))){
String mappedGetter = "get" +
searchable.mappedAttribute().substring(0, 1).toUpperCase() +
searchable.mappedAttribute().substring(1);
Reflector.alterAnnotation(searchable, "mappedClass", f.getType());
Reflector.alterAnnotation(searchable, "mappedGetter", mappedGetter);
}
}
}
}
// Changed Bean
#Id
#GeneratedValue
#Column(name = "MOCK_USER_ID")
private Long id;
#Column(name = "MOCK_USER_NAME")
#CrudSearchable
private String name;
#ManyToOne
#JoinColumn(name = "MOCK_GROUP", nullable = false)
#CrudSearchable(mappedAttribute = "name")
private MockGroup group;
public MockUser(){
super();
new Searchable<>().modifySearchable(this.getClass());
}
Seems like a lot to change the values instead of having the user define them, but I believe that it will make the code more user friendly.
Hope this helps someone. I found the answer on this post: Modify a class definition's annotation string parameter at runtime. Check it out!
I have a specific json response from server, where under a key the content would be of different models also at a time only one of the model data would be present under the key.
While parsing the response into POJO how can I specify object type at runtime based on other field of contentType on same model.
Following is the code for better understanding of scenario.
Here content_type is type A and so under "content" key there would be model for object of class TypeA
"scheduled_content": {
"some_field": "value",
"content_type": "typeA",
"content" : {
"some_field" : "value"
"more_feilds" : "value"
}
}
Here content_type is type B and so under "content" key there would be model for object of class TypeB
"scheduled_content": {
"some_field": "value",
"content_type": "typeB",
"content" : {
"some_field_b" : "value"
"more_fields_for_b" : "value"
}
}
How can I write POJO classes to parse such json response?
The type classes are completely different models they don't have any field in common.
I believe that what you are looking for is called, in Jackson JSON terms, polymorphic deserialization by property name.
Here is how I do it with Jackson 2.1.4:
First create an abstract class ScheduledContent with common members and an abstract method that would operate on the content. Use the JsonTypeInfo annotation to mark the JSON property that would resolve the specific implementation and the JsonSubTypes annotation to register the subtypes by the values of the property previously specified:
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "content_type")
#JsonSubTypes({
#JsonSubTypes.Type(name = "typeA", value = ScheduledAContent.class),
#JsonSubTypes.Type(name = "typeB", value = ScheduledBContent.class)
})
public abstract class ScheduledContent {
private String someField;
#JsonSetter("some_field")
public void setSomeField(String someField) {
this.someField = someField;
}
public abstract void doSomethingWithContent();
}
The subtypes registration can also be done on the ObjectMapper as you will see later.
Then add the specific implementation for the ScheduledAContent class:
public class ScheduledAContent extends ScheduledContent {
private TypeAContent content;
public void setContent(TypeAContent content) {
this.content = content;
}
#Override
public void doSomethingWithContent() {
System.out.println("someField: " + content.getSomeField());
System.out.println("anotherField: " + content.getAnotherField());
}
}
with TypeAContent being:
import com.fasterxml.jackson.annotation.JsonSetter;
public class TypeAContent {
private String someField;
private String anotherField;
#JsonSetter("some_field")
public void setSomeField(String someField) {
this.someField = someField;
}
public String getSomeField() {
return someField;
}
#JsonSetter("another_field")
public void setAnotherField(String anotherField) {
this.anotherField = anotherField;
}
public String getAnotherField() {
return anotherField;
}
}
and also for the ScheduledBContent class:
public class ScheduledBContent extends ScheduledContent {
private TypeBContent content;
public void setContent(TypeBContent content) {
this.content = content;
}
#Override
public void doSomethingWithContent() {
System.out.println("someField: " + content.getSomeField());
System.out.println("anotherField: " + content.getAnotherField());
}
}
with TypeBContent being:
import com.fasterxml.jackson.annotation.JsonSetter;
public class TypeBContent {
private String someField;
private String anotherField;
#JsonSetter("some_field_b")
public void setSomeField(String someField) {
this.someField = someField;
}
public String getSomeField() {
return someField;
}
#JsonSetter("another_field_b")
public void setAnotherField(String anotherField) {
this.anotherField = anotherField;
}
public String getAnotherField() {
return anotherField;
}
}
And a simple Test class:
import java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.NamedType;
public class Test {
public static void main(String[] args) {
String jsonA = "{" +
"\"some_field\": \"main_some_field1\"," +
"\"content_type\": \"typeA\"," +
"\"content\" : {" +
" \"some_field\" : \"content_some_field\"," +
" \"another_field\" : \"content_another_field\"" +
"}}";
String jsonB = "{" +
"\"some_field\": \"main_some_field2\"," +
"\"content_type\": \"typeB\"," +
"\"content\" : {" +
" \"some_field_b\" : \"content_some_field_b\"," +
" \"another_field_b\" : \"content_another_field_b\"" +
"}}";
ObjectMapper mapper = new ObjectMapper();
/*
* This is another way to register the subTypes if you want to do it dynamically without the use of the
* JsonSubTypes annotation in the ScheduledContent class
*/
// mapper.registerSubtypes(new NamedType(ScheduledAContent.class, "typeA"));
// mapper.registerSubtypes(new NamedType(ScheduledBContent.class, "typeB"));
try {
ScheduledContent scheduledAContent = mapper.readValue(jsonA, ScheduledContent.class);
scheduledAContent.doSomethingWithContent();
ScheduledContent scheduledBContent = mapper.readValue(jsonB, ScheduledContent.class);
scheduledBContent.doSomethingWithContent();
} catch (IOException e) {
e.printStackTrace();
}
}
}
that will produce the output:
someField: content_some_field
anotherField: content_another_field
someField: content_some_field_b
anotherField: content_another_field_b
Using #JsonSetter in the setter methods may help. But in this case you will need to create setter methods for each type of fields in "content".
#JsonSetter("some_field")
public void setSomeField1(String field1) {
this.field1 = field1;
}
#JsonSetter("some_field_b")
public void setSomeField2(String field2) {
this.field1 = field1;
}