JAXB complex mapping - java

I have the following code:
#XmlRootElement(name = "repository")
#XmlAccessorType(XmlAccessType.FIELD)
public class Repository
{
#XmlElement (name = "id")
private String id;
#XmlElement (name = "policy")
private String policy;
...
}
#XmlRootElement(name = "storage")
#XmlAccessorType(XmlAccessType.FIELD)
public class Storage
{
#XmlElement (name = "id")
private String id;
/**
* K: repository.id
* V: Repository
*/
#XmlElement (name = "repositories")
#XmlJavaTypeAdapter(RepositoryMapAdapter.class)
private Map<String, Repository> repositories = new LinkedHashMap<String, Repository>();
...
}
public class DataCenter
{
/**
* K: storageId
* V: storage
*/
#XmlElement(name = "storages")
#XmlJavaTypeAdapter(StorageMapAdapter.class)
//#XStreamAlias(value = "storages")
private Map<String, Storage> storages = new LinkedHashMap<String, Storage>();
...
}
I have the following two adapters:
public class RepositoryMapAdapter
extends XmlAdapter<RepositoryMapAdapter.RepositoryMap, Map<String, Repository>>
{
public static class RepositoryMap
{
#XmlVariableNode("id")
List<RepositoryMapEntry> entries = new ArrayList<RepositoryMapEntry>();
}
public static class RepositoryMapEntry
{
#XmlAttribute
public String id;
#XmlValue
public Repository repository;
}
#Override
public RepositoryMap marshal(Map<String, Repository> map)
throws Exception
{
RepositoryMap repositoryMap = new RepositoryMap();
for (Map.Entry<String, Repository> entry : map.entrySet())
{
RepositoryMapEntry repositoryMapEntry = new RepositoryMapEntry();
repositoryMapEntry.id = entry.getKey();
repositoryMapEntry.repository = entry.getValue();
System.out.println("Writing repository " + entry.getValue().getId());
repositoryMap.entries.add(repositoryMapEntry);
}
return repositoryMap;
}
#Override
public Map<String, Repository> unmarshal(RepositoryMap repositoryMap)
throws Exception
{
List<RepositoryMapEntry> adaptedEntries = repositoryMap.entries;
Map<String, Repository> map = new LinkedHashMap<String, Repository>(adaptedEntries.size());
for (RepositoryMapEntry repositoryMapEntry : adaptedEntries)
{
System.out.println("Reading repository " + repositoryMapEntry.id);
map.put(repositoryMapEntry.id, repositoryMapEntry.repository);
}
return map;
}
}
public class StorageMapAdapter
extends XmlAdapter<StorageMapAdapter.StorageMap, Map<String, Storage>>
{
public static class StorageMap
{
#XmlVariableNode("id")
List<StorageMapEntry> entries = new ArrayList<StorageMapEntry>();
}
public static class StorageMapEntry
{
#XmlAttribute
public String id;
#XmlValue
public Storage storage;
}
#Override
public StorageMap marshal(Map<String, Storage> map)
throws Exception
{
StorageMap storageMap = new StorageMap();
for (Map.Entry<String, Storage> entry : map.entrySet())
{
StorageMapEntry storageMapEntry = new StorageMapEntry();
storageMapEntry.id = entry.getKey();
storageMapEntry.storage = entry.getValue();
System.out.println("Writing storage " + entry.getValue().getId());
storageMap.entries.add(storageMapEntry);
}
return storageMap;
}
#Override
public Map<String, Storage> unmarshal(StorageMap storageMap)
throws Exception
{
List<StorageMapEntry> adaptedEntries = storageMap.entries;
Map<String, Storage> map = new LinkedHashMap<String, Storage>(adaptedEntries.size());
for (StorageMapEntry storageMapEntry : adaptedEntries)
{
System.out.println("Reading storage " + storageMapEntry.id);
map.put(storageMapEntry.id, storageMapEntry.storage);
}
return map;
}
}
I'd like to have the following XML:
<storages>
<storage id="storage0">
<repositories>
<repository id="repository1" policy="policy1"/>
<repository id="repository2" policy="policy2"/>
</repositories>
</storage>
<storage id="storage1">
<repositories>
<repository id="repository3" />
<repository id="repository4" />
</repositories>
</storage>
</storages>
Based on the above XML, what more do I need to do in order to have my code read/write such XML using JAXB? As far as I understand, I need to use XmlAdapter, but I'm not quite sure how to apply it to this case.

Something in the lines of:
#XmlRootElement(name = "datacenter")
public class DataCenter {
/**
* K: storageId V: storage
*/
#XmlElement(name = "storages")
#XmlJavaTypeAdapter(StorageMapAdapter.class)
private final Map<String, Storage> storages = new LinkedHashMap<String, Storage>();
}
#XmlJavaTypeAdapter(StorageMapAdapter.class)
#XmlAccessorType(XmlAccessType.FIELD)
public class Storage {
#XmlAttribute(name = "id")
private String id;
/**
* K: repository.id V: Repository
*/
#XmlElement(name = "repositories")
#XmlJavaTypeAdapter(RepositoryMapAdapter.class)
private final Map<String, Repository> repositories = new LinkedHashMap<String, Repository>();
}
public class StorageMap {
#XmlElement(name = "storage")
List<Storage> entries = new ArrayList<Storage>();
public List<Storage> getEntries() {
return entries;
}
}
public class StorageMapAdapter extends XmlAdapter<StorageMap, Map<String, Storage>> {
#Override
public StorageMap marshal(Map<String, Storage> map) throws Exception {
StorageMap storageMap = new StorageMap();
for (Map.Entry<String, Storage> entry : map.entrySet()) {
storageMap.getEntries().add(entry.getValue());
}
return storageMap;
}
#Override
public Map<String, Storage> unmarshal(StorageMap storageMap) throws Exception {
List<Storage> adaptedEntries = storageMap.entries;
Map<String, Storage> map = new LinkedHashMap<String, Storage>(adaptedEntries.size());
for (Storage storage : adaptedEntries) {
map.put(storage.getId(), storage);
}
return map;
}
}
#XmlRootElement(name = "repository")
#XmlAccessorType(XmlAccessType.FIELD)
public class Repository {
#XmlAttribute(name = "id")
private String id;
#XmlAttribute(name = "policy")
private String policy;
}
public class RepositoryMap {
#XmlElement(name = "repository")
List<Repository> entries = new ArrayList<Repository>();
public List<Repository> getEntries() {
return entries;
}
}
public class RepositoryMapAdapter extends XmlAdapter<RepositoryMap, Map<String, Repository>> {
#Override
public RepositoryMap marshal(Map<String, Repository> map) throws Exception {
RepositoryMap repositoryMap = new RepositoryMap();
for (Map.Entry<String, Repository> entry : map.entrySet()) {
repositoryMap.getEntries().add(entry.getValue());
}
return repositoryMap;
}
#Override
public Map<String, Repository> unmarshal(RepositoryMap repositoryMap) throws Exception {
List<Repository> adaptedEntries = repositoryMap.entries;
Map<String, Repository> map = new LinkedHashMap<String, Repository>(adaptedEntries.size());
for (Repository repository : adaptedEntries) {
System.out.println("Reading repository " + repository.getId());
map.put(repository.getId(), repository);
}
return map;
}
}
Running demo here: http://ideone.com/NyOGVQ
Demo updated with marshalling here: http://ideone.com/NzvRzX

Related

jackson mapping nested hashmap to nested pojo class in java

I have a nested java map like this
inputMap: {jobId={EndpointReference={ReferenceParameters={ResourceURI=http://schemas.com/wbem/wscim/1/cim-schema/2/Job, SelectorSet={Selector=[JID_502260561923, root/im]}}, Address=http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous}}, returncode=4096, messageId=null, arguments=null, message=null}
which I want to map to java pojo and here is my pojo classes.
#Getter
#Setter
#ToString
public class DMResponseMapper {
#Getter
#Setter
#ToString
public static class GetSysConfigDMResponseMapper {
#JsonProperty("jobId")
private EndpointReferenceMapper endpointReferenceMapper;
private Integer returnCode;
private String messageId;
private String arguments;
private String message;
#Getter
#Setter
#ToString
public static class EndpointReferenceMapper {
#JsonProperty("ReferenceParameters")
private ReferenceParametersMapper referenceParametersMapper;
#JsonProperty("Address")
private String address;
#Getter
#Setter
#ToString
public static class ReferenceParametersMapper {
#JsonProperty("ResourceURI")
private String resourceURI;
#JsonProperty("SelectorSet")
private SelectorSetMapper selectorSetMapper;
#Getter
#Setter
#ToString
public static class SelectorSetMapper {
#JsonProperty("Selector")
private List<String> selector;
}
}
}
}
}
but objectMapper.convertValue(inputMap, GetSysConfigDMResponseMapper.class) is NOT mapping the nested classes.. just the top level fields.
My objectMapper is instantiated like this:
static {
objectMapper = new ObjectMapper();
objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
Response Object is :
DMResponseMapper.GetSysConfigDMResponseMapper(endpointReferenceMapper=DMResponseMapper.GetSysConfigDMResponseMapper.EndpointReferenceMapper(referenceParametersMapper=null, address=null), returnCode=4096, messageId=null, arguments=null, message=null)
Can anyone please suggest, what is wrong here?
Upon debugging this is what I see:
Converted endpointReferenceMapper to type Object.
DMResponseMapper.GetSysConfigDMResponseMapper(endpointReferenceMapper={EndpointReference={ReferenceParameters={ResourceURI=http://schemas.com/wbem/wscim/1/cim-schema/2/Job, SelectorSet={Selector=[JID_502318722705, root/dcim]}}, Address=http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous}}, returnCode=4096, messageId=null, arguments=null, message=null)
The DMResponseMapper pojo needs to follow the structure of your source data more closely.
Your source Map object has the following structure, based on the info in the question:
inputMap:
{
jobId={
EndpointReference={
ReferenceParameters={
ResourceURI=http://schemas.com/wbem/wscim/1/cim-schema/2/Job,
SelectorSet={
Selector=[JID_502260561923, root/im]
}
},
Address=http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
}
},
returncode=4096,
messageId=null,
arguments=null,
message=null
}
So, I adapted your DMResponseMapper pojo class to more closely map to that structure - and I changed the nested class names as well. Here is a summary of the nested classes with their fields for your data:
//
// NOT the actual class - just an overview of the structure!
//
class DMResponseMapper {
private JobId jobId;
private Integer returncode;
private Object messageId;
private Object arguments;
private Object message;
class JobId {
private EndpointReference endpointReference;
class EndpointReference {
private ReferenceParameters referenceParameters;
private String address;
class ReferenceParameters {
private String resourceURI;
private SelectorSet selectorSet;
class SelectorSet {
private List<String> selector = null;
}
}
}
}
}
This gave me the following, when fleshed out with annotations and getters/setters:
//
// Here is the actual class, based on the above structure.
//
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class DMResponseMapper {
#JsonProperty("jobId")
private JobId jobId;
#JsonProperty("returncode")
private Integer returncode;
#JsonProperty("messageId")
private Object messageId;
#JsonProperty("arguments")
private Object arguments;
#JsonProperty("message")
private Object message;
#JsonProperty("jobId")
public JobId getJobId() {
return jobId;
}
#JsonProperty("jobId")
public void setJobId(JobId jobId) {
this.jobId = jobId;
}
#JsonProperty("returncode")
public Integer getReturncode() {
return returncode;
}
#JsonProperty("returncode")
public void setReturncode(Integer returncode) {
this.returncode = returncode;
}
#JsonProperty("messageId")
public Object getMessageId() {
return messageId;
}
#JsonProperty("messageId")
public void setMessageId(Object messageId) {
this.messageId = messageId;
}
#JsonProperty("arguments")
public Object getArguments() {
return arguments;
}
#JsonProperty("arguments")
public void setArguments(Object arguments) {
this.arguments = arguments;
}
#JsonProperty("message")
public Object getMessage() {
return message;
}
#JsonProperty("message")
public void setMessage(Object message) {
this.message = message;
}
public static class JobId {
#JsonProperty("EndpointReference")
private EndpointReference endpointReference;
#JsonProperty("EndpointReference")
public EndpointReference getEndpointReference() {
return endpointReference;
}
#JsonProperty("EndpointReference")
public void setEndpointReference(EndpointReference endpointReference) {
this.endpointReference = endpointReference;
}
public static class EndpointReference {
#JsonProperty("ReferenceParameters")
private ReferenceParameters referenceParameters;
#JsonProperty("Address")
private String address;
#JsonProperty("ReferenceParameters")
public ReferenceParameters getReferenceParameters() {
return referenceParameters;
}
#JsonProperty("ReferenceParameters")
public void setReferenceParameters(ReferenceParameters referenceParameters) {
this.referenceParameters = referenceParameters;
}
#JsonProperty("Address")
public String getAddress() {
return address;
}
#JsonProperty("Address")
public void setAddress(String address) {
this.address = address;
}
public static class ReferenceParameters {
#JsonProperty("ResourceURI")
private String resourceURI;
#JsonProperty("SelectorSet")
private SelectorSet selectorSet;
#JsonProperty("ResourceURI")
public String getResourceURI() {
return resourceURI;
}
#JsonProperty("ResourceURI")
public void setResourceURI(String resourceURI) {
this.resourceURI = resourceURI;
}
#JsonProperty("SelectorSet")
public SelectorSet getSelectorSet() {
return selectorSet;
}
#JsonProperty("SelectorSet")
public void setSelectorSet(SelectorSet selectorSet) {
this.selectorSet = selectorSet;
}
public static class SelectorSet {
#JsonProperty("Selector")
private List<String> selector = null;
#JsonProperty("Selector")
public List<String> getSelector() {
return selector;
}
#JsonProperty("Selector")
public void setSelector(List<String> selector) {
this.selector = selector;
}
}
}
}
}
}
This is invoked as follows:
First, some test data:
List<String> selector = new ArrayList();
selector.add("JID_502260561923");
selector.add("root/im");
Map<String, Object> selectorSet = new HashMap();
selectorSet.put("Selector", selector);
String resourceURI = "http://schemas.com/wbem/wscim/1/cim-schema/2/Job";
Map<String, Object> referenceParameters = new HashMap();
referenceParameters.put("ResourceURI", resourceURI);
referenceParameters.put("SelectorSet", selectorSet);
String address = "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous";
Map<String, Object> endpointReference = new HashMap();
endpointReference.put("ReferenceParameters", referenceParameters);
endpointReference.put("Address", address);
Map<String, Object> jobId = new HashMap();
jobId.put("EndpointReference", endpointReference);
Map<String, Object> inputMap = new HashMap();
inputMap.put("jobId", jobId);
inputMap.put("returncode", 4096);
inputMap.put("messageId", "foo");
inputMap.put("arguments", "bar");
inputMap.put("message", "baz");
Note I replaced your null values with strings, for testing and demonstration.
Then the code to perform the mapping:
ObjectMapper objectMapper = new ObjectMapper();
DMResponseMapper mapper = objectMapper.convertValue(inputMap, DMResponseMapper.class);
The resulting mapper object contains the test data:

Jersey rest service not returning XMLresponse

I am building a rest API using Jersey where XML and JSON outputs are allowed depending on what format client prefers(using Accept header).The service sends the below class as an output which looks like this
#XmlRootElement
public class ProjectDetails{
private List<Attachment> attachments;
private Map<String, List<Attachment>> imageCategory;
#XmlTransient
public List<Attachment> getAttachments() {
return attachments;
}
public void setAttachments(List<Attachment> attachments) {
this.attachments = attachments;
}
public Map<String, List<Attachment>> getImageCategory() {
if(attachments == null || attachments.size() == 0){
return null;
}
Map<String, List<Attachment>> map = new HashMap<String, List<Attachment>>();
for (Attachment img : attachments){
String key = img.getCategory();
if(BaseUtil.hasText(key)){
List<Attachment> values = map.get(key);
if (values == null){
values = new ArrayList<Attachment>();
}
values.add(img);
map.put(key, values);
}
}
this.imageCategory = map ;
return imageCategory;
}
public void setImageCategory(Map<String, List<Attachment>> imageCategory) {
this.imageCategory = imageCategory;
}
}
I don't want attachments field as an output so marked it with #XmlTransient rather I want to form a Map using the attachments field and send it as an output.
In case of JSON format, I am getting the correct response.But in case of XML, I am not getting any output when I hit the service.
I think it is related to this Map field because if I remove Map field and add some other field like String then I get that field in response.
Please let me know how to resolve this.
Update:
After some googling, i found XmlAdapter solution and implemented as below
public class MapAdapter extends
XmlAdapter<MapAdapter.AdaptedMap, Map<String, List<Attachment>>> {
public static class AdaptedEntry {
public String key;
public List<Attachment> value = new ArrayList<Attachment>();
}
public static class AdaptedMap {
List<AdaptedEntry> entries = new ArrayList<AdaptedEntry>();
}
#Override
public AdaptedMap marshal(Map<String, List<Attachment>> map)
throws Exception {
AdaptedMap adaptedMap = new AdaptedMap();
for (Entry<String, List<Attachment>> entry : map.entrySet()) {
AdaptedEntry adaptedEntry = new AdaptedEntry();
adaptedEntry.key = entry.getKey();
adaptedEntry.value = entry.getValue();
adaptedMap.entries.add(adaptedEntry);
}
return adaptedMap;
}
#Override
public Map<String, List<Attachment>> unmarshal(AdaptedMap adaptedMap)
throws Exception {
List<AdaptedEntry> adapatedEntries = adaptedMap.entries;
Map<String, List<Attachment>> map = new HashMap<String, List<Attachment>>(
adapatedEntries.size());
for (AdaptedEntry adaptedEntry : adapatedEntries) {
map.put(adaptedEntry.key, adaptedEntry.value);
}
return map;
}
And then
#XmlJavaTypeAdapter(MapAdapter.class)
public Map<String, String> getImageCategory() {
But still it's not working..Anything I missed?
I have used your ProjectDetails class a little bit changes I've made, and it provides response for both XML and JSON. Can you try this?
#XmlRootElement
public class ProjectDetails {
private List<Attachment> attachments;
private Map<String, ArrayList<Attachment>> imageCategory;
#XmlTransient
public List<Attachment> getAttachments() {
return attachments;
}
public void setAttachments(List<Attachment> attachments) {
this.attachments = attachments;
}
#XmlJavaTypeAdapter(MapAdapter.class)
public Map<String, ArrayList<Attachment>> getImageCategory() {
if(attachments == null || attachments.size() == 0){
return null;
}
Map<String, ArrayList<Attachment>> map = new HashMap<String, ArrayList<Attachment>>();
for (Attachment img : attachments){
String key = img.getCategory();
if(!key.equals("")){
ArrayList<Attachment> values = map.get(key);
if (values == null){
values = new ArrayList<Attachment>();
}
values.add(img);
map.put(key, values);
}
}
this.imageCategory = map ;
return imageCategory;
}
public void setImageCategory(Map<String, ArrayList<Attachment>> imageCategory) {
this.imageCategory = imageCategory;
}
}
And the Adapter class you can use the following
public class MapAdapter extends XmlAdapter<MapElement[], Map<String, ArrayList<Attachment>>>{
public MapElement[] marshal(Map<String, ArrayList<Attachment>> arg0) throws Exception {
MapElement[] mapElements = new MapElement[arg0.size()];
int i = 0;
for (Map.Entry<String, ArrayList<Attachment>> entry : arg0.entrySet()){
mapElements[i++] = new MapElement(entry.getKey(), entry.getValue());
}
return mapElements;
}
public Map<String, ArrayList<Attachment>> unmarshal(MapElement[] arg0) throws Exception {
Map<String, ArrayList<Attachment>> r = new HashMap<String, ArrayList<Attachment>>();
for (MapElement mapelement : arg0)
r.put(mapelement.key, mapelement.value);
return r;
}
}
I've changed the MapElement also
public class MapElement {
#XmlElement
public String key;
#XmlElement
public ArrayList<Attachment> value;
private MapElement() {
}
public MapElement(String key, ArrayList<Attachment> value) {
this.key = key;
this.value = value;
}
}
And the Attachement class should have getter setter methods
public class Attachment {
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
private String category;
public Attachment(String cat){
this.category = cat;
}
}

Unmarshalling complex XML using JAXB (Moxy)

I want to unmarshal the following xml using EclipseLink JAXB (MOXy)
<MyObject>
<Value1>1</Value1>
<Value2>2</Value2>
...
<ValueN>N</ValueN>
<Items>
<Item>
<Value4>4</Value4>
...
<ValueM>M</ValueM>
</Item>
...
</Items>
</MyObject>
Where nodes Value1, Value2, etc, are variables.
For this I use the following class
#XmlRootElement(name="MyObject")
public class MyObject {
public MyObject() {
this.items = new ArrayList<Item>();
}
#XmlPath(".")
#XmlJavaTypeAdapter(MyMapAdapter.class)
private Map<String, String> map = new HashMap<String, String>();
private List<Item> items;
public List<Item> getItems() {
return items;
}
#XmlElementWrapper(name="Items")
#XmlElement(name="Item")
public void setItems(List<Item> items) {
this.items = items;
}
}
Where in MyMapAdapter I get a list of Value nodes and their "values". If I remove the "map" property, then the values of the list are loaded correctly.
This is MyMapAdapter:
public class MapAdapter extends XmlAdapter<MapAdapter.AdaptedMap, Map<String, String>> {
public static class AdaptedMap {
#XmlVariableNode("key")
List<AdaptedEntry> entries = new ArrayList<AdaptedEntry>();
}
public static class AdaptedEntry {
#XmlTransient
public String key;
#XmlValue
public String value;
}
#Override
public AdaptedMap marshal(Map<String, String> map) throws Exception {
AdaptedMap adaptedMap = new AdaptedMap();
for(Entry<String, String> entry : map.entrySet()) {
AdaptedEntry adaptedEntry = new AdaptedEntry();
adaptedEntry.key = entry.getKey();
adaptedEntry.value = entry.getValue();
adaptedMap.entries.add(adaptedEntry);
}
return adaptedMap;
}
#Override
public Map<String, String> unmarshal(AdaptedMap adaptedMap) throws Exception {
List<AdaptedEntry> adaptedEntries = adaptedMap.entries;
Map<String, String> map = new HashMap<String, String>(adaptedEntries.size());
for(AdaptedEntry adaptedEntry : adaptedEntries) {
if(!"Items".equals(adaptedEntry.key)){
map.put(adaptedEntry.key, adaptedEntry.value);
}
}
return map;
}
}
What am I doing wrong? How this can be done?
Any help will be greatly appriciated

Spring doesn't save object to MongoDB correctly

Following question has been separated from this one:
ArrayIndexOutOfBoundsException while Spring save data to MongoDB
I have problem with saving Object to MongoDB. I've noticed that problem might be caused by too complex object. I have following class hierarchy:
ClassA is superclass for ClassB and ClassC. ClassD contains map of maps. ClassC contains ClassB.
Code which I invoke is following:
ClassC c = new ClassC()
c.setName("NAME");
mongoOperation.save(c, "Mongo"); // MongoOperations object
The problem is that Mongo doesn't save object's data. It saves only _id and _class.
Actual data
{
"_id" : ObjectId("53e86cd9c506f66eafaa03cb"),
"_class" : "com.sample.ClassC"
}
Expected data
{
"_id" : ObjectId("53e86cd9c506f66eafaa03cb"),
"_class" : "com.sample.ClassC",
"name" : "NAME"
}
Funny thing is that when I comment out map field in ClassD everything works fine.
Is it possible to be caused by too complex object which I try to serialize?
EDIT
When I remove bObject from ClassC it also works fine.
EDIT 2
All classes are simple beans with setters and getters.
e.g.
public class ClassD{
private TreeMap<String, TreeMap<String,String>> map;
public TreeMap<String, TreeMap<String, String>> getMap() {
return map;
}
public void setMap(TreeMap<String, TreeMap<String, String>> map) {
this.map = map;
}
}
EDIT 3
Full example below, it has same class hierarchy as picture above.
public class Application implements CommandLineRunner {
#Autowired
private MongoTemplate mongoTemplate;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... args) throws Exception {
ClassC cObject = new ClassC();
cObject.setName("Jon");
try {
mongoTemplate.save(cObject);
}catch(Exception e){
e.printStackTrace();
}
mongoTemplate.save(cObject);
}
}
class ClassA{
private String name;
private ClassD dObject;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ClassD getdObject() {
return dObject;
}
public void setdObject(ClassD dObject) {
this.dObject = dObject;
}
}
class ClassB extends ClassA {
}
class ClassC extends ClassA {
private ClassB b;
public ClassB getB() {
return b;
}
public void setB(ClassB b) {
this.b = b;
}
}
class ClassD {
private TreeMap<String, TreeMap<String, String>> map = new TreeMap<>();
public TreeMap<String, TreeMap<String, String>> getMap() {
return map;
}
public void setMap(TreeMap<String, TreeMap<String, String>> map) {
this.map = map;
}
}
I guess the MongoConverter in specific version of your spring-data-mongodb.jar works incorrectly.
Spring must convert your ClassC instance into DBObject format, then call DBCollection.save to save data into database. You can check the content of DBObject parameter in method "com.mongodb.DBCollection.save" whether it contains correct data as you expect.
I copy your ClassC with complete structure and test, it's fine and cannot reproduce what you described above. I use spring-data-mongdb-1.2.3-RELEASE.jar. What's the version you adopt?
The following code seems to work:
#EnableAutoConfiguration
public class Application implements CommandLineRunner {
#Autowired
private MongoTemplate mongoTemplate;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... args) throws Exception {
Customer customer = new Customer("myself");
ClassB classB = new ClassB();
TreeMap<String, TreeMap<String, String>> map = new TreeMap<String, TreeMap<String, String>>();
TreeMap<String, String> innermap = new TreeMap<String, String>();
innermap.put("iam", "cool");
map.put("innermap", innermap);
TreeMap<String, String> innermap2 = new TreeMap<String, String>();
innermap2.put("youare", "yellow");
map.put("innermap2", innermap2);
classB.setMap(map);
customer.setClassB(classB);
try {
mongoTemplate.save(customer);
} catch (Exception e) {
e.printStackTrace();
}
mongoTemplate.save(customer);
System.out.println(mongoTemplate.findAll(Customer.class));;
}
}
public class ClassB {
private TreeMap<String, TreeMap<String, String>> map = new TreeMap<String, TreeMap<String, String>>();
public TreeMap<String, TreeMap<String, String>> getMap() {
return map;
}
public void setMap(TreeMap<String, TreeMap<String, String>> map) {
this.map = map;
}
}
#Document(collection ="customer")
public class Customer {
#Id
private String id;
private String name;
private ClassB classB;
public Customer() {
}
public Customer(String name) {
this.name = name;
}
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;
}
public ClassB getClassB() {
return classB;
}
public void setClassB(ClassB classB) {
this.classB = classB;
}
#Override
public String toString() {
return "Customer [id=" + id + ", name=" + name + ", classB=" + classB
+ "]";
}
}
But the ArrayIndexOutOfBoundsException-issue is still present.

Jackson Custom Deserialize

I would like deserialize my custom serialized objects. My objects are basically consisting a simple Pair implementation.
class School{
Integer id;
String schoolName;
}
class Student{
Integer id;
Integer schoolId;
String studentName;
}
#JsonSerialize(using=PairSerializer.class)
public class Pair<V,K>{
V v;
K k;
}
Here is the result
[
{
"v":{
"id":1,
"schoolId":3,
"studentName":"O. Bas"
},
"k":{
"id":3,
"schoolName":"School 3"
}
},
{
"v":{
"id":2,
"schoolId":3,
"studentName":"C. Koc"
},
"k":{
"id":3,
"schoolName":"School 3"
}
}
]
v and k as field name in json is pretty ugly. That is why I have written a custom serializer as this:
#Override
public void serialize(Pair pair, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeStartObject();
jsonGenerator.writeObjectField(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL,pair.getK().getClass().getSimpleName() ), pair.getK());
jsonGenerator.writeObjectField(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL,pair.getV().getClass().getSimpleName() ), pair.getV());
jsonGenerator.writeEndObject();
}
The result is exactly what I want. v and k field names are replaced by their class names.
[
{
"school":{
"id":3,
"schoolName":"School 3"
},
"student":{
"id":1,
"schoolId":3,
"studentName":"O. Bas"
}
},
{
"school":{
"id":3,
"schoolName":"School 3"
},
"student":{
"id":2,
"schoolId":3,
"studentName":"C. Koc"
}
}
]
Here is the my question. How can I deserialize my json string to List<Pair<V, K> ? The real problem is that V and K are depends on the deserialized context it might vary as Student, School or another pair implementation.
public class PairDeserializer extends JsonDeserializer<Pair> {
public PairDeserializer() {
}
#Override
public Pair deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// I need to Deserialized generic type information of Pair
}
}
I think, you should create your own PropertyNamingStrategy. For example see my simple implementation:
class MapTransformNamingStrategy extends LowerCaseWithUnderscoresStrategy {
private static final long serialVersionUID = 1L;
private Map<String, String> mapping;
public MapTransformNamingStrategy(Map<String, String> mapping) {
this.mapping = mapping;
}
#Override
public String translate(String property) {
if (mapping.containsKey(property)) {
return mapping.get(property);
}
return property;
}
}
Now you can use it in this way:
Map<String, String> mapping = new HashMap<String, String>();
mapping.put("k", "student");
mapping.put("v", "school");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(new MapTransformNamingStrategy(mapping));
//etc
Example JSON output:
{ "school" : { "id" : 1,
"schoolName" : "The Best School in the world"
},
"student" : { "id" : 1,
"schoolId" : 1,
"studentName" : "Arnold Shwarz"
}
}
EDIT
Because my answer is not clear for everyone I present full example source code which serialize Java POJO objects into JSON and "vice versa".
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy;
public class JacksonProgram {
#SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
List<Pair<Student, School>> pairs = createDataForSerialization();
Map<String, String> mapping = createSchoolStudentMapping();
JsonConverter jsonConverter = new JsonConverter(mapping);
String json = jsonConverter.toJson(pairs);
System.out.println("JSON which represents list of pairs:");
System.out.println(json);
List<Pair<Student, School>> value = jsonConverter.fromJson(json, List.class);
System.out.println("----");
System.out.println("Deserialized version:");
System.out.println(value);
}
private static Map<String, String> createSchoolStudentMapping() {
Map<String, String> mapping = new HashMap<String, String>();
mapping.put("k", "student");
mapping.put("v", "school");
return mapping;
}
private static List<Pair<Student, School>> createDataForSerialization() {
List<Pair<Student, School>> pairs = new ArrayList<Pair<Student, School>>();
pairs.add(new Pair<Student, School>(new Student(1, 3, "O. Bas"), new School(3, "School 3")));
pairs.add(new Pair<Student, School>(new Student(2, 4, "C. Koc"), new School(4, "School 4")));
return pairs;
}
}
class JsonConverter {
private Map<String, String> mapping;
private ObjectMapper objectMapper;
private JsonFactory jsonFactory;
public JsonConverter(Map<String, String> mapping) {
this.mapping = mapping;
initJsonObjects();
}
private void initJsonObjects() {
objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(new MapTransformNamingStrategy(mapping));
jsonFactory = new JsonFactory();
}
public String toJson(Object object) throws Exception {
StringWriter stringWriter = new StringWriter();
JsonGenerator jsonGenerator = jsonFactory.createGenerator(stringWriter);
objectMapper.writeValue(jsonGenerator, object);
return stringWriter.toString();
}
public <T> T fromJson(String json, Class<T> expectedType) throws Exception {
JsonParser jsonParser = jsonFactory.createJsonParser(json);
return objectMapper.readValue(jsonParser, expectedType);
}
}
class MapTransformNamingStrategy extends LowerCaseWithUnderscoresStrategy {
private static final long serialVersionUID = 1L;
private Map<String, String> mapping;
public MapTransformNamingStrategy(Map<String, String> mapping) {
this.mapping = mapping;
}
#Override
public String translate(String property) {
if (mapping.containsKey(property)) {
return mapping.get(property);
}
return property;
}
}
class School {
private Integer id;
private String schoolName;
public School() {
}
public School(Integer id, String schoolName) {
this.id = id;
this.schoolName = schoolName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
#Override
public String toString() {
return "School [id=" + id + ", schoolName=" + schoolName + "]";
}
}
class Student {
private Integer id;
private Integer schoolId;
private String studentName;
public Student() {
}
public Student(Integer id, Integer schoolId, String studentName) {
this.id = id;
this.schoolId = schoolId;
this.studentName = studentName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getSchoolId() {
return schoolId;
}
public void setSchoolId(Integer schoolId) {
this.schoolId = schoolId;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
#Override
public String toString() {
return "Student [id=" + id + ", schoolId=" + schoolId + ", studentName=" + studentName
+ "]";
}
}
class Pair<V, K> {
private V v;
private K k;
public Pair() {
}
public Pair(V v, K k) {
this.v = v;
this.k = k;
}
public V getV() {
return v;
}
public void setV(V v) {
this.v = v;
}
public K getK() {
return k;
}
public void setK(K k) {
this.k = k;
}
#Override
public String toString() {
return "Pair [v=" + v + ", k=" + k + "]";
}
}
The full output log:
JSON which represents list of pairs:
[{"school":{"id":1,"schoolId":3,"studentName":"O. Bas"},"student":{"id":3,"schoolName":"School 3"}},{"school":{"id":2,"schoolId":4,"studentName":"C. Koc"},"student":{"id":4,"schoolName":"School 4"}}]
----
Deserialized version:
[{school={id=1, schoolId=3, studentName=O. Bas}, student={id=3, schoolName=School 3}}, {school={id=2, schoolId=4, studentName=C. Koc}, student={id=4, schoolName=School 4}}]
Because the output JSON is not formatted I present it in more understandable version:
[
{
"school":{
"id":1,
"schoolId":3,
"studentName":"O. Bas"
},
"student":{
"id":3,
"schoolName":"School 3"
}
},
{
"school":{
"id":2,
"schoolId":4,
"studentName":"C. Koc"
},
"student":{
"id":4,
"schoolName":"School 4"
}
}
]
As you can see, we create new JsonConverter object with definition of mapping between Pair property names and which names we want to see in JSON string representation. Now if you have for example Pair<School, Room> you can create mapping Map in this way:
private static Map<String, String> createSchoolRoomMapping() {
Map<String, String> mapping = new HashMap<String, String>();
mapping.put("k", "school");
mapping.put("v", "room");
return mapping;
}
I was going for an answer with some annotation (JsonTypeInfo and JsonUnwrapped), but those two don't work well together apparently (see this issue). That would of handled both the serialization and deserialization part of your problem, without relying on custom de/serializer. Instead, you'll need a custom deserializer, which does something along those line:
class PairDeserializer extends JsonDeserializer<Pair>{
static Map<String, Class> MAPPINGS = new HashMap<String, Class>();
#Override
public Pair deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Object key = deserializeField(jp);
Object value = deserializeField(jp);
Pair pair = new Pair();
pair.k = key;
pair.v = value;
jp.nextToken();
return pair;
}
private Object deserializeField(JsonParser jp) throws IOException, JsonParseException, JsonProcessingException {
jp.nextValue();
String className = jp.getCurrentName();
return jp.readValueAs(MAPPINGS.get(className));
}
}
Then you only need to register the mappings you need

Categories

Resources