I am new to Avro serialization. My requirement is to serialize a class having a Map<String, Object> as its property. This object could be any type of class like Student, Teacher, Principal.
class Payload
{
private string transactionNo,
private Map<String,Object> map,
}
Please suggest to me a way to write a schema for the above scenario.
Related
I have a simple POJO class looking like this:
public class EventPOJO {
public EventPOJO() {
}
public String id, title;
// Looking for an annotation here
public Timestamp startDate, endDate;
}
This is how I create a Map using ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> event = mapper.convertValue(myPOJO, new TypeReference<Map<String, Object>>() {});
I need mapper to contain the key "startDate" with the value of Type Timestamp.
(com.google.firebase.Timestamp)
Instead however mapper contains the key "startDate" with the value of Type LinkedHashMap containing the nanoseconds and seconds.
So basically I need the mapper to stop when seeing an object of type Timestamp and put it in the map as is.
I want to convert a Hashmap of type to a POJO. I am using jackson to convert it currently, however, since the request is a API request, the user might provide more fields than needed.
For example,
The hashmap could be :
{
field1ID:"hi",
field2ID:["HI","HI","JO"],
field3ID:"bye"
}
while the pojo is simply
{
field1ID:"hi",
field3ID:"bye"
}
When using ObjectMapper.convertValue, unless there is a one to one mapping from hashmap to pojo, a IllegalArguemetException will be throw. What I wan to do is, if the field is there then map the field. Else leave it as null.
As you didn't provide any code in your question, consider, for example, you have a map as shown below:
Map<String, Object> map = new HashMap<>();
map.put("firstName", "John");
map.put("lastName", "Doe");
map.put("emails", Arrays.asList("johndoe#mail.com", "john.doe#mail.com"));
map.put("birthday", LocalDate.of(1990, 1, 1));
And you want to map it to and instance of Contact:
#Data
public class Contact {
private String firstName;
private String lastName;
private List<String> emails;
}
It could be achieved with the following code:
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
Contact contact = mapper.convertValue(map, Contact.class);
In the example above, the map has a birthday key, which cannot be mapped to any field of the Contact class. To prevent the serialization from failing, the FAIL_ON_UNKNOWN_PROPERTIES feature has been disabled.
Alternatively, you could annotate the Contact class with #JsonIgnoreProperties and set ignoreUnknown to true:
#Data
#JsonIgnoreProperties(ignoreUnknown = true)
public class Contact {
...
}
And then perform the conversion:
ObjectMapper mapper = new ObjectMapper();
Contact contact = mapper.convertValue(map, Contact.class);
To convert the Contact instance back to a map, you can use:
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = mapper.convertValue(contact,
new TypeReference<Map<String, Object>>() {});
There are several previous questions around using JaxB to marshall/unmarshall a java.util.Map, many of which get pointed back to this example, which works great:
http://blog.bdoughan.com/2013/03/jaxb-and-javautilmap.html
However, I can't get JaxB to be able to marshall/unmarshall instances of Map if the map is not a member of the #XmlRootElement. For example, here's a root element class,
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public static class Customer {
private MyField myField
MyField getMyField() {
return myField
}
void setMyField(MyField myField) {
this.myField = myField
}
}
The definition of it's field class:
#XmlAccessorType(XmlAccessType.FIELD)
public static class MyField{
Map<String, String> getSomeMap() {
return someMap
}
void setSomeMap(Map<String, String> someMap) {
this.someMap = someMap
}
#XmlElement
private Map<String, String> someMap = new HashMap<String, String>()
}
And some code to drive the marshalling:
JAXBContext jc = JAXBContext.newInstance(Customer.class)
Customer customer = new Customer()
MyField myField1 = new MyField()
myField1.someMap.put("foo", "bar")
myField1.someMap.put("baz", "qux")
customer.myField = myField1
Marshaller marshaller = jc.createMarshaller()
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true)
marshaller.marshal(customer, System.out)
This example results in:
java.util.Map is an interface, and JAXB can't handle interfaces.
java.util.Map does not have a no-arg default constructor.
I am writing my code in Groovy rather than Java, but I don't think it should make much of a difference.
I was able to encounter the same behavior using JAXB by creating a TestController of type #RestController, using Spring Boot.
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
#RestController
#RequestMapping(value = "test")
class TestController {
#RequestMapping(value = "findList")
List findList() {
["Test1", "Test2", "Test3"] as ArrayList<String>
}
#RequestMapping(value = "findMap")
Map findMap() {
["T1":"Test1", "T2":"Test2", "T3":"Test3"] as HashMap<String,String>
}
#RequestMapping(value = "")
String find(){
"Test Something"
}
}
With JAXB as the default implementation in SpringBoot, I could reproduce the issue that the /test/findList would correctly render XML, but /test/findMap would generate an error as described in the initial posting.
For me, the solution to the problem is to switch the XML rendering library to Jackson (there are others like XStream as well).
Using Gradle for the build file (build.gradle), I simply add the Jackson dependencies, very similar to how you would if using Maven:
'com.fasterxml.jackson.core:jackson-core:2.7.1',
'com.fasterxml.jackson.core:jackson-annotations:2.7.1',
'com.fasterxml.jackson.core:jackson-databind:2.7.1-1',
'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.7.1',
'org.codehaus.woodstox:woodstox-core-asl:4.4.1',
I have experienced this before myself. Bottom line is that the warning is telling you exactly the problem. You have defined your field as type java.util.Map. JAXB does not support interfaces. To correct your problem, you need to change the declaration of your field to a concrete Map type like:
private HashMap<String, String> someMap = new HashMap<String, String>()
Your other option is described in the link you referenced. You need to have a
MapAdapter class as referenced in the link you provided and then include that in the annotation, hinting to JAXB how it should marshal/unmarshal the Map type.
I think this link gives a clearer example of how to create and implement the MapAdapter:
JAXB: how to marshall map into <key>value</key>
The answer to the specific issue I was having ended up being removing the #XmlElement annotation from the Map field like so:
#XmlAccessorType(XmlAccessType.FIELD)
public static class MyField{
Map<String, String> getSomeMap() {
return someMap
}
void setSomeMap(Map<String, String> someMap) {
this.someMap = someMap
}
//#XmlElement Remove this annotation
private Map<String, String> someMap = new HashMap<String, String>()
}
Without that annotation, the marshalling/unmarshalling works fine, and still interprets the Map as an XmlElement - there seems to be a bug with that annotation specifically. However, as #dlcole points out, an alternative (that would allow you to have more control over the format of your serialized representation) is to use Jackson rather than JAXB.
I would like to clarify if it is possible to create Map<String, Object> from JPA entity through Hibernate. I mean is it possible to convert persistent object (entity) to Map that contains all entity properties as keys and properties' values as values. I understand that properties can be retrieved through Reflections but I can't figure out how to map it with proper values. I found only one solution and it is to use Spring's JdbcTemplate but it is not an option in my case. If anyone have possible solution please let me know. Thank you in advance.
You could try using #Converter as follows
#Entity
public class SomeEntity{
#Id
//...
#Convert(converter = MyConverter.class)
Map<String,Object> map;
}
and build up your converter as you wish for example convert to/from json
#Converter
public class MyConverter implements
AttributeConverter<Map<String, Object>, String> {
#Override
public String convertToDatabaseColumn(Map<String, Object> map) {
return jsonStr(map);
}
#Override
public Map<String, Object> convertToEntityAttribute(String s) {
return mapFromJson(s);
}
You can access the properties through reflection with the assistance of the Apache BeanUtil library, if your entity follows JavaBean naming conventions for all of its properties.
Step one: Wrap your entity in a WrapDynaBean object.
Step two: Create a DynaBeanPropertyMapDecorator, further wrapping the DynaBean.
Step three: Well, that's it. DynaBeanPropertyMapDecorator implemements Map<String, Object> so your job is done there. getFoo() on your original object may now be found by decorator.get("foo"). (Note that you've lost type-safety here, but you did ask for a Map<String, Object>...)
I have annotated an endpoint with swagger annotations. In the #ResponseHeader I set the returning class as response. This class contains a property which is annotated with #XmlJavaTypeAdapter. The adapter is changing the data type of the property. Unfortunately Swagger shows the type of the property, not the return type of the Adapter. Is it possible to do this?
What I already tried is to annotate the property with #ApiModelProperty(). But it was not possible for me to set the dataType to List (Primitive data types or just a list was working).
Thanks :)
The following was not working:
#ApiModelProperty(dataType = "List<Map<String, String>>")
public Map<String, String> someMap = new HashMap<>();
I had to create an Interface
public interface ListOfMap extends List<Map<String, String>> {}
And then I used this interface in the ApiModelProperty:
#ApiModelProperty(dataType = "ListOfMap")
public Map<String, String> someMap = new HashMap<>();
With this, everything worked :)