At runtime I want to create a POJO with attributes that are same as keys of the map and populate it with the values of the map. I don't know the content of the map or what the POJO will look like.
Example-
Map<String,String> map = new HashMap<>();
map.add("attr1", obj1);
map.add("attr2", obj2);
...
From this map I want to create a POJO-
class POJO
{
String attr1;
public void setAttr1(String attr1) {
this.attr1 = attr1;
}
public String getAttr1() {
return attr1;
}
String attr2;
public void setAttr2(String attr2) {
this.attr2 = attr2;
}
public String getAttr2() {
return attr2;
}
.....
}
and populate it as well.
All of this should happen at runtime.
Something like-
Object object = getPopulatedPOJO(map)
or
Class type = getPOJOType(map);
Object object = type.newInstance();
object = getPopulatedPOJO(map)
This is not the final answer to your problem. But I hope this gives the direction you might want to continue with. Note that, this direction modifies bytecode instructions at runtime and can cause crashes and failures.
You can use javassist which is a bytecode manipulator. Please see their official site for more info.
Two important things to note
In Java, multiple class loaders can coexist and each class loader creates its own name space. Different class loaders can load different class files with the same class name
The JVM does not allow dynamically reloading a class. Once a class loader loads a class, it cannot reload a modified version of that class during runtime. Thus, you cannot alter the definition of a class after the JVM loads it. However, the JPDA (Java Platform Debugger Architecture) provides limited ability for reloading a class
So you can achieve what you want in two different ways.
Create bytecode at runtime, write the class, use a custom classloader to create your pojo from the written class. javassist can help you for this, but this is way too complicated for me to consume at the moment.
Use javassist to modify an existing class and use reflection to set the values.
For option 2, the easier one, here is how you can achieve this.
Add javassist in your classpath. If you are using maven, add the following dependency in your pom.xml.
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.21.0-GA</version>
</dependency>
Create a dummy empty pojo class that you need to work with. Let us call it Pojo.
package com.test;
public class Pojo {
//Nothing in the source file.
}
Modify the class body to add the fields from the HashMap. Here is a sample of how I did it using the map you gave.
Map<String, String> map = new HashMap<String, String>();
map.put("firstname", "John");
map.put("lastname", "Doe");
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("com.test.Pojo");
// Used for non-primitive data types. If primitive, use CtClass.<inttype, floattype, etc..>
CtClass strClass = ClassPool.getDefault().get("java.lang.String");
//Iterate and add all the fields as per the keys in the map
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
CtField field = new CtField(strClass, key, cc);
field.setModifiers(Modifier.PUBLIC);
cc.addField(field);
}
// Instantiate from the updated class
Class<Pojo> clazz = cc.toClass();
Pojo newInstance = clazz.newInstance();
//Use the map again to set the values using reflection.
iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
newInstance.getClass().getField(key).set(newInstance, map.get(key));
}
newInstance is the instance of Pojo but with fields added based on keys of the map and set based on the values in the map. A simple test to print the newInstance using jackson ObjectMapper yields this.
ObjectMapper objMapper = new ObjectMapper();
String writeValueAsString = objMapper.writeValueAsString(newInstance);
System.out.println(writeValueAsString);
{"firstname":"John","lastname":"Doe"}
Hope this helps.
EDIT
If you want to add get/set methods, you can create methods using CtMethod in javassist. However, you can only access them using reflection since these methods are added at runtime.
See the answer in a similar question, using Jackson objectMapper.convertvalue method seems most reasonable.
Related
If I have a class
class DTO {
final MySet<Types> values = MySetWrapper(EnumSet.of(Types.class));
public MySet getValues() {
return values;
}
}
where MySet extends Set. Jackson complains that
Cannot find a deserializer for non-concrete Collection type MySet
which I understand, but I already instantiated the collection. What I want is for jackson to just call add for each value after it created an instance, something like:
DTO o = new DTO();
MySet<Types> values = o.getValues();
for (Types type : jsonArray) {
values.add(type );
}
I don't want it to try to create a new collection itself.
That error message means that the DTO class is configured (by default or explicitly) to deserialize the values part of the JSON input into the DTO values field of DTO :
Cannot find a deserializer for non-concrete Collection type MySet
If you consider that Jackson should not perform the deserialization directly on this field, you could define a constructor to set values and also make sure that Jackson will not perform automatically the deserialization work : to achieve it, remove setter for that field (or add #JsonIgnore on it) and any jackson module that will use reflection to deserialize to fields.
It would give :
final MySet<Types> values = MySetWrapper(EnumSet.of(Types.class));
#JsonCreator
public MyFoo(Set<Types> values) {
this.values.addAll(values);
}
Note that I specified in the constructor Set and not MySet (should not be an issue as interface doesn't declare fields), otherwise you would get the same issue since you didn't define a deserializer for MySet.
But if you implement a deserializer for that you could of course do :
public MyFoo(MySet<Types> values) {
this.values.addAll(values);
}
Found an answer using #JsonProperty:
#JsonProperty
private void setValues(Set<Types> types) {
values.addAll(types);
}
Pretty short and simple thankfully.
Edit: seems like you don't even need the annotation.
I'm using JAXB to save objects to xml files.
#XmlRootElement(name="jaxbobjt")
#XmlAccessorType(XmlAccessType.FIELD)
public class SomeJAXBObject
{
#XmlElementWrapper(name="myEntry")
private Map<Integer, AnotherJAXBObject> map = Collections.synchronizedMap(new LinkedHashMap<Integer, AnotherJAXBObject>());
}
Note the fact that I'm using a synchronizedMap(...) wrapper.
The above results in the following xml:
<jaxbobjt>
<map>
<myEntry>
<key>key</key>
<value>value</value>
</myEntry>
</map>
</jaxbobjt>
Actually I thought that I would need an XmlAdapter to get this working.
But to my surprise this marshals and unmarshals fine. Tests revealed that it correctly uses a java.util.Collections$SynchronizedMap containing a LinkedHashMap$Entry object.
So, if I understand correctly. JAXB's unmarshaller, just instantiates my object using the constructor. Since there's already an instance for the map after instantiation of the object, it does not instantiate the map itself. It uses the putAll I assume ?
I'm just trying to get a deeper understanding of what is going on. It would be nice of somebody could give me some more background information about this. Are my assumptions correct ?
If I am correct, I assume the following implementation would have failed:
#XmlRootElement(name="jaxbobjt")
#XmlAccessorType(XmlAccessType.FIELD)
public class SomeJAXBObject
{
// no instance yet.
#XmlElementWrapper(name="myEntry")
private Map<Integer, AnotherJAXBObject> map = null;
public synchronized void addObject(Integer i, AnotherJAXBObject obj)
{
// instantiates map on-the-fly.
if (map == null) map = Collections.synchronizedMap(new LinkedHashMap<Integer, AnotherJAXBObject>());
map.put(i, obj);
}
}
The strategy used by JAXB is to create container classes only when it is necessary. For anything that is bound to a List, JAXB's xjc creates
protected List<Foo> foos;
public List<Foo> getFoos(){
if( foos == null ) foos = new ArrayList<>();
return foos;
}
and thus, unmarshalling another Foo to be added to this list, does essentially
parent.getFoos().add( foo );
As for maps: presumably the working version of your class SomeJAXBObject contains a getMap method, and that'll work the same way. Setters for lists and maps aren't necessary, and they'll not be used if present. A put method in the parent class isn't expected either; if present it'll not be used because JAXB wouldn't have a way of knowing what it does.
In my web application that is using Spring, we want use a custom JSON structure. Spring by default takes a POJO like this:
public class Model {
private int id;
private String name;
public Model(){}
public Model(int id, String name){
this.id = id;
this.name = name;
}
}
and turns it into this:
{"id":1, "name":"Bob"}
With our application, we want to turn it into this instead:
[1, "Bob"]
I want to use Spring's default serialization logic that detects the Java type (int, String, Collection, etc.) and maps to the appropriate JSON type, but just change the wrapping object to an array rather than and object with fields.
This is the Serializer I have so far (which will be implemented in the model with #JsonSerialize(using = Serializer.class)), but would prefer not to rewrite all the logic Spring already has implemented.
public class Serializer extends JsonSerializer<Model> {
#Override
public void serialize(Model value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartArray();
jgen.writeString(value.id);
.... other values ...
jgen.writeEndArray();
}
}
How can I hook into the pre-existing Serializer so that this new serializer will work with any POJO as the default one does (not just the Model class, but any similar or child class we need to serialize to an array)? This could have mixed properties and no specific naming convention for the properties.
I want to avoid writing a custom serializer for every different Model class (the ... other values ...) section.
Take a look at Apache BeanUtils library, in particular, pay attention to the BeanUtils.populate() method.
What that method does is to convert any given Object to a Map<String, Object>, based on JavaBeans conventions. In the keys you'd have the attribute names, while in the values you'd have every attribute's value. That method should be enough for standard cases. Read the documentation carefully, to check how to handle special cases.
Model model = ...; // get your model from some place
Map<String, Object> properties = new HashMap<>();
BeanUtils.populate(model, properties);
// Exception handling and special cases left as an excercise
The above recursively fills the properties map, meaning that if your Model has an attribute named otherModel whose type is OtherModel, then the properties map will have another map at the entry that matches the otherModel key, and so on for other nested POJOs.
Once you have the properties map, what you want to serialize as the elements of your array will be in its values. So, something like this should do the job:
public List<Object> toArray(Map<String, Object> properties) {
List<Object> result = new ArrayList<>();
for (Object obj : properties.values()) {
Object elem = null;
if (obj != null) {
Class<?> clz = obj.getClass();
if (Map.class.isAssignableFrom(clz)) {
elem = toArray((Map<String, Object>) obj); // recursion!
} else {
elem = obj;
}
}
result.add(elem); // this adds null values
// move 1 line up if you don't
// want to serialize nulls
}
return result;
}
Then, after invoking the toArray() method, you'd have a List<Object> ready to serialize using the standard Spring mechanisms. I even believe you won't need a specific serializer:
List<Object> array = toArray(properties);
return array; // return this array, i.e. from a Controller
Disclaimer:
Please use this as a guide and not as a final solution. I tried to be as careful as possible, but the code might have errors. I'm pretty sure it needs special handling for arrays and Iterables of POJOs. It's undoubtedly lacking exception handling. It works only for POJOs. It might explode if the supplied object has circular references. It's not tested!
You could use #JsonValue annotation for this.
Example:
public class Model {
private int id;
public Model(){}
public Model(int id){
this.id = id;
}
#JsonValue
public int[] getValue() {
return new int[]{this.id};
}
}
I'm calling a rest service that returns a json object. I'm trying to deserialize the responses to my Java Beans using Jackson and data-binding.
The example Json is something like this:
{
detail1: { property1:value1, property2:value2},
detail2: { property1:value1, property2:value2},
otherObject: {prop3:value1, prop4:[val1, val2, val3]}
}
Essentially, detail1 and detail2 are of the same structure, and thus can be represented by a single class type, whereas OtherObject is of another type.
Currently, I've set up my classes as follows (this is the structure I would prefer):
class ServiceResponse {
private Map<String, Detail> detailMap;
private OtherObject otherObject;
// getters and setters
}
class Detail {
private String property1;
private String property2;
// getters and setters
}
class OtherObject {
private String prop3;
private List<String> prop4;
// getters and setters
}
Then, just do:
String response = <call service and get json response>
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(response, ServiceResponse.class)
The problem is I'm getting lost reading through the documentation about how to configure the mappings and annotations correctly to get the structure that I want. I'd like detail1, detail2 to create Detail classes, and otherObject to create an OtherObject class.
However, I also want the detail classes to be stored in a map, so that they can be easily distinguished and retrieved, and also the fact that the service in in the future will return detail3, detail4, etc. (i.e., the Map in ServiceResponse would look like
"{detail1:Detail object, detail2:Detail object, ...}).
How should these classes be annotated? Or, perhaps there's a better way to structure my classes to fit this JSON model? Appreciate any help.
Simply use #JsonAnySetter on a 2-args method in ServiceResponse, like so:
#JsonAnySetter
public void anySet(String key, Detail value) {
detailMap.put(key, value);
}
Mind you that you can only have one "property" with #JsonAnySetter as it's a fallback for unknown properties. Note that the javadocs of JsonAnySetter is incorrect, as it states that it should be applied to 1-arg methods; you can always open a minor bug in Jackson ;)
I would like to know if it is possible to have snakeyaml load a yaml document into a javabean and if it is unable to find a match for the entry in the document as a javabean property it will place it into a generic map within the javabean...
Ex.
public class Person {
private String firstName;
private String lastName;
private Map<String, Object> anythingElse;
//Getters and setters...
}
If I load a document that looks like:
firstName: joe
lastName: smith
age: 30
Since age is not a property in the bean I would like {age, 30} to be added to the anythingElse map.
Possible?
Thanks.
No it wouldn't be possible.
From my experience and attempts it doesn't work. If you would want to load a file into a object than all attributes in that objectclass would have to have a getter and setter (I.E. the class have to be JavaBean, see Wikipedia).
I used your Person Class (See the wiki page for a proper JavaBeanClass) and this code: http://codepaste.net/dbtzqb
My error message was: "Line 3, column 4: Unable to find property 'age' on class: Person" thus proving that this simple program cannot have "unexpected" attributes. This is my fast and easy conclusion. I've not tried extensively so it may be possible but I don't know such a way (you'll have to bypass the readingmethods and JavaBean). I've used YamlBeans (https://code.google.com/p/yamlbeans/) so it's a little different but I find it easier and working ;]
Hope it's helping!
Edit
Sorry for bumping this, better late than never! I didn't see the postdate until after I posted my answer.. But hopefully it would help others seeking help assweel :3
I haven't tried the following (semi-kludgy hack) using SnakeYaml, but I have it working using YamlBeans:
Basically the idea is to define a class that extends one of the concrete implementations of java.util.Map. Then define getters that pick out distinct values and a general getter that returns everything else:
public class Person extends HashMap<String, Object>
{
public String getFirstName()
{
return (String)this.get("firstName");
}
public String getLastName()
{
return (String)this.get("lastName");
}
public Map<String, Object> getExtensions()
{
Map<String, Object> retVal = (Map<String, Object>)this.clone();
retVal.remove("firstName");
retVal.remove("lastName");
return retVal;
}
}
I'm not sure how either SnakeYaml or YamlBeans prioritizes the different type information you see when introspecting on this class, but YamlBeans (at least) is content to deserialize info into this class as if it were any other Map and doesn't seem to get confused by the addition getters (i.e. doesn't trip up on the "getExtensions").
It is possible:
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.representer.Representer;
public class YamlReader {
public static <T> T readYaml(InputStream is, Class<T> clazz){
Representer representer = new Representer();
// Set null for missing values in the yaml
representer.getPropertyUtils().setSkipMissingProperties(true);
Yaml yaml = new Yaml(representer);
T data = yaml.loadAs(is, clazz);
return data;
}
}