XML JAXB marshall not saving to xml file - java

I have a javafx program that brings up a filechooser to allow a user to pick and image an display it to a grid view with an inserted caption that pops up after an image was pciked. I save both the filepath and the caption to different arraylists [for now] and my goal is to save both to xml file so that I can unmarshall it when the application is re opened so the images would still be there. Right now I just want to be able to save the two strings to an xml file and then figure out the rest later. I am currently able to run my code with no errors until I reach my stop method where I try to save every image and caption the user has added to the array lists.
My JAXB Annotation:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class ImageCap {
private String filePath;
private String caption;
public ImageCap() {
}
public ImageCap(String filePath, String caption) {
this.filePath = filePath;
this.caption = caption;
}
#Override
public String toString() {
return "ImageCap{" + "filePath=" + filePath + ", caption=" + caption + '}';
}
public String getFilePath() {
return filePath;
}
#XmlElement
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public String getCaptions() {
return caption;
}
#XmlElement
public void setCaption(String caption) {
this.caption = caption;
}
}
And my main to test:
public static void main(String[] args) {launch(args);}
public void start(Stage primaryStage) throws JAXBException{
final JFXPanel bananarama = new JFXPanel();
//import the library (read))
// create the (initial) display
display.makeBrowseButton(primaryStage);
display.createDisplay(primaryStage);
// show user
primaryStage.show();
}#Override
public void stop() throws JAXBException{
File file = new File("file.xml");
JAXBContext jaxbContext = JAXBContext.newInstance(ImageCap.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
//this.context = JAXBContext.newInstance(ImageCap.class);
//Marshaller marshaller = context.createMarshaller();
for(int i = 0; i < display.filePaths.size(); i++)
{
ImageCap imageCap = new ImageCap();
imageCap.setFilePath(display.filePaths.get(i));
imageCap.setCaption(display.captions.get(i).toString());
System.out.println(display.filePaths.get(i).toString());
try {
// output pretty printed
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(imageCap, file);
} catch (JAXBException e) {
e.printStackTrace();
}
}
}
Here are my errors after the stop command:
this problem is related to the following location:
at public void ImageCap.setCaption(java.lang.String)
at ImageCap
this problem is related to the following location:
at private java.lang.String ImageCap.caption
at ImageCap
Class has two properties of the same name "filePath"
this problem is related to the following location:
at public java.lang.String ImageCap.getFilePath()
at ImageCap
this problem is related to the following location:
at private java.lang.String ImageCap.filePath
at ImageCap
but it specically cuts off at line 81 which is:
JAXBContext jaxbContext = JAXBContext.newInstance(ImageCap.class);
any ideas why?

If you have getter and setters for a field of the same name, then you need to use XmlAccessType.PROPERTY rather than XmlAccessType.FIELD:
#XmlRootElement
#XmlAccessorType(XmlAccessType.PROPERTY)
public static class ImageCap {
private String filePath;
private String caption;
...
Also, another problem that you will encounter is that you misspelled getCaptions() as plural, when it should be getCaption(). JAXB will complain about it as well.
Finally, by marshaling your file inside the loop, you are rewriting over and over the same file with the currently processed imageCap. If what you want is to marshall all imageCaps you need to put them on a List and marshall the List instead. In order to do that you'd need a new JAXB model class like:
#XmlRootElement(name = "myImageCapList")
class ImageCapList {
#XmlElement
List<ImageCap> imageCap;
public ImageCapList() {}
public ImageCapList(List<ImageCap> imageCaps) {
this.imageCap = imageCaps;
}
}
and you'd need to create an instance of this object wrapping your list of ImageCap objects (List<ImageCap>) and use it as the target to invoke the jaxbMarshaller.marshal method as shown in the following method:
public void imageCapsMarshal(List<ImageCap> imageCaps, File outFile) {
try {
jaxbMarshaller.marshal(new ImageCapList(imageCaps), outFile);
} catch (JAXBException e) {
// HANDLE EXCEPTIONS
}
}
also, you'll need to instantiate your JAXBContext appropriately:
jaxbContext = JAXBContext.newInstance(ImageCapList.class);
The following is a complete working demo of this for you to play with:
import java.io.File;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
public class JAXBMarshall {
private JAXBContext jaxbContext;
private Marshaller jaxbMarshaller;
public JAXBMarshall() throws JAXBException {
jaxbContext = JAXBContext.newInstance(ImageCapList.class);
jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
}
public void imageCapsMarshal(List<ImageCap> imageCaps, File outFile) {
try {
jaxbMarshaller.marshal(new ImageCapList(imageCaps), outFile);
} catch (JAXBException e) {
// HANDLE EXCEPTIONS
}
}
public static void main(String[] args) throws JAXBException {
JAXBMarshall jaxbMarshaller = new JAXBMarshall();
File file = new File("file.xml");
List<ImageCap> imageCaps = IntStream.range(0, 10)
.mapToObj(idx -> new ImageCap("my/file/path/" + idx, idx + ". The Caption!"))
.collect(Collectors.toList());
jaxbMarshaller.imageCapsMarshal(imageCaps, file);
}
#XmlRootElement(name = "myImageCapList")
static class ImageCapList {
#XmlElement
List<ImageCap> imageCap;
public ImageCapList() {}
public ImageCapList(List<ImageCap> imageCaps) {
this.imageCap = imageCaps;
}
}
#XmlRootElement
static class ImageCap {
#XmlElement
String filePath;
#XmlElement
String caption;
public ImageCap() {}
public ImageCap(String filePath, String caption) {
this.filePath = filePath;
this.caption = caption;
}
#Override
public String toString() {
return "ImageCap{" + "filePath=" + filePath + ", caption=" + caption + '}';
}
}
}
Complete code on GitHub
Hope this helps.

Related

OpenCsv writes wrong column names with BeanToCsv + HeaderColumnNameTranslateMappingStrategy

I'm using opencsv 3.6 in order to create a csv file starting from a java bean.
First of all, I tried this code:
import com.opencsv.CSVReader;
import com.opencsv.CSVWriter;
import com.opencsv.bean.BeanToCsv;
import com.opencsv.bean.HeaderColumnNameTranslateMappingStrategy;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class CustomBean {
private String name;
private String surname;
public CustomBean(String n, String s) {
this.name = n;
this.surname = s;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setSurname(String surname) {
this.surname = surname;
}
public String getSurname() {
return surname;
}
public static void main(String[] args) {
Map<String,String> mapping = new HashMap<String,String>();
mapping.put("COLUMN1","name");
mapping.put("COLUMN2","surname");
HeaderColumnNameTranslateMappingStrategy<CustomBean> strategy = new HeaderColumnNameTranslateMappingStrategy<CustomBean>();
strategy.setType(CustomBean.class);
strategy.setColumnMapping(mapping);
ArrayList<CustomBean> customUsers = new ArrayList<CustomBean>();
customUsers.add(new CustomBean("Kobe","Bryant"));
BeanToCsv<CustomBean> bean = new BeanToCsv<CustomBean>();
try {
CSVWriter writer = new CSVWriter(new FileWriter("testOut.csv"));
bean.write(strategy, writer, customUsers);
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
But I had the following error:
Exception in thread "main" java.lang.RuntimeException: Error writing CSV !
at com.opencsv.bean.BeanToCsv.write(BeanToCsv.java:74)
at test.CustomBean.main(CustomBean.java:63)
Caused by: java.lang.NullPointerException
at com.opencsv.bean.HeaderColumnNameTranslateMappingStrategy.getColumnName(HeaderColumnNameTranslateMappingStrategy.java:45)
at com.opencsv.bean.HeaderColumnNameMappingStrategy.findDescriptor(HeaderColumnNameMappingStrategy.java:112)
at com.opencsv.bean.BeanToCsv.processHeader(BeanToCsv.java:103)
at com.opencsv.bean.BeanToCsv.write(BeanToCsv.java:69)
... 1 more
This happens because in opencsv source code in getColumnName method in the HeaderColumnNameTranslateMappingStrategy class there is the following line:
return col < header.length ? columnMapping.get(header[col].toUpperCase()) : null;
Therefore, header is null. This is true, in fact this class is a subclass of HeaderColumnNameMappingStrategy class that contains the header variable (String[] type) that is never initialized.
The only useful method I found in this class is captureHeader, but unfortunately it takes a CSVReader as input.
For this reason I created an empty csv file:
COLUMN1,COLUMN2
and I added the following lines at the beginning of the try/catch block:
CSVReader reader = new CSVReader(new FileReader("testIn.csv"));
strategy.captureHeader(reader);
In this way (that I really don't like because I have to create a csv file) I have no exception, but in the result csv file the name of the columns does not follow the mapping strategy:
"name","surname"
"Kobe","Bryant"
Two questions:
How can I have the expected result, i.e. the right column names in the csv file?
There is a way to not use the CSVReader class?
Looking at the source code of BeanToCsv, the processHeader(...) method does nothing with the supplied headers. Your only option is to create a custom strategy ( to avoid CSVReader ) and a custom BeanToCsv as below
public class CustomBean {
...
public static void main(String[] args) {
...
HeaderColumnNameTranslateMappingStrategy strategy = new CustomStrategy<CustomBean>();
strategy.setType(CustomBean.class);
strategy.setColumnMapping(mapping);
...
BeanToCsv bean = new CustomBeanToCsv<CustomBean>();
...
}
static class CustomStrategy<T> extends HeaderColumnNameTranslateMappingStrategy {
#Override
public void setColumnMapping(Map columnMapping) {
super.setColumnMapping(columnMapping);
header = new String[columnMapping.size()];
int i = 0;
for (Map.Entry entry : columnMapping.entrySet()) {
header[i] = entry.getKey().toUpperCase();
i++;
}
}
public String[] getHeader() {
return header;
}
}
static class CustomBeanToCsv<T> extends BeanToCsv {
#Override
protected String[] processHeader(MappingStrategy mapper) throws IntrospectionException {
if (mapper instanceof CustomStrategy) {
return ((CustomStrategy) mapper).getHeader();
} else {
return super.processHeader(mapper);
}
}
}
}
I have been using openCSV for five years now and I am still learning stuff about it. The issue is that the HeaderColumnNameMappingStrategy and HeaderColumnNameTranslateMappingStrategy were made for the CsvToBean. It wants a file to read to get the header out to see where the reader should read from.
For the BeanToCsv class use the ColumnPositionMappingStrategy class. You give it the class you are mapping and a list of the columns you want to map and it does the rest for you.
Here is a little test method I wrote that worked.
public void createUsingBeanToCsv(int numRecords, FileWriter fos) {
List<SmallRecord> smallRecords = new ArrayList<>(numRecords);
for (int i = 0; i < numRecords; i++) {
smallRecords.add(SmallRecordGenerator.createRecord(i));
}
BeanToCsv<SmallRecord> beanToCsv = new BeanToCsv<>();
ColumnPositionMappingStrategy<SmallRecord> strategy = new ColumnPositionMappingStrategy<>();
strategy.setType(SmallRecord.class);
String[] columns = new String[]{"bigDecimal_1", "name_1", "intNumber_1"};
strategy.setColumnMapping(columns);
beanToCsv.write(strategy, fos, smallRecords);
}

How to generate unique element names or auto-increment element names with JAXB?

Is there a way to auto-increment the element names of a Collection when marshalling to an XML using JAXB? I generate an XML using this code:
public void saveTestSettings(final String filename, final DefaultTestSettings t) throws Exception
{
try
{
final Path path = FileSystems.getDefault().getPath((paths.getTestSettingsPath() + filename));
if (Files.notExists(path.getParent()))
throw new Exception(path.getParent().toString() + " does not exist!!");
final OutputStream out = new BufferedOutputStream(Files.newOutputStream(path));
final JAXBContext jaxbContext = JAXBContext.newInstance(DefaultTestSettings.class);
final Marshaller m = jaxbContext.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
m.marshal(t, out);
out.flush();
out.close();
}
catch (JAXBException | IOException e)
{
e.printStackTrace();
throw new Exception(e.getMessage());
}
}
The resulting XML looks like this:
<defaultTestSettings>
<groups>
<instruments>
<name>A200</name>
</instruments>
<name>A0</name>
</groups>
<groups>
<instruments>
<name>A300</name>
</instruments>
<instruments>
<name>A400</name>
</instruments>
<name>A1</name>
</groups>
</defaultTestSettings>
I would like the groups and instruments to be automatically incremented, so that the result looks like this:
<defaultTestSettings>
<groups1>
<instruments1>
<name>A200</name>
</instruments1>
<name>A0</name>
</groups1>
<groups2>
<instruments1>
<name>A300</name>
</instruments1>
<instruments2>
<name>A400</name>
</instruments2>
<name>A1</name>
</groups2>
</defaultTestSettings>
I am not too familiar with using XML schema, would that work?
Below are a couple of way's this use case could be handled:
Option #1 - Using the Standard JAXB APIs
My answer below expands on the excellent answer given by Ilya. I have extended it to use an XmlAdapter to remove the logic from the getElements() method.
Java Model
DefaultSettings
import java.util.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
public class DefaultSettings {
private List<Groups> groups = new ArrayList<Groups>();
#XmlAnyElement
#XmlJavaTypeAdapter(GroupsAdapter.class)
public List<Groups> getGroups() {
return groups;
}
}
Groups
import java.util.*;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
public class Groups {
private List<Instruments> instruments = new ArrayList<Instruments>();
#XmlAnyElement
#XmlJavaTypeAdapter(InstrumentsAdapter.class)
public List<Instruments> getInstruments() {
return instruments;
}
}
Instruments
public class Instruments {
}
XmlAdapter
GroupsAdapter
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.namespace.QName;
public class GroupsAdapter extends XmlAdapter<JAXBElement<Groups>, Groups> {
private int counter = 1;
private InstrumentsAdapter instrumentsAdapter = new InstrumentsAdapter();
public InstrumentsAdapter getInstrumentsAdapter() {
return instrumentsAdapter;
}
#Override
public Groups unmarshal(JAXBElement<Groups> v) throws Exception {
return v.getValue();
}
#Override
public JAXBElement<Groups> marshal(Groups v) throws Exception {
instrumentsAdapter.resetCounter();
return new JAXBElement<Groups>(new QName("groups" + counter++), Groups.class, v);
}
}
InstrumentsAdapter
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.namespace.QName;
public class InstrumentsAdapter extends XmlAdapter<JAXBElement<Instruments>, Instruments> {
private int counter = 1;
#Override
public Instruments unmarshal(JAXBElement<Instruments> v) throws Exception {
return v.getValue();
}
#Override
public JAXBElement<Instruments> marshal(Instruments v) throws Exception {
return new JAXBElement<Instruments>(new QName("instruments" + counter++), Instruments.class, v);
}
public void resetCounter() {
counter = 1;
}
}
Demo Code
Demo
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(DefaultSettings.class, Groups.class, Instruments.class);
Groups groups1 = new Groups();
groups1.getInstruments().add(new Instruments());
groups1.getInstruments().add(new Instruments());
Groups groups2 = new Groups();
groups2.getInstruments().add(new Instruments());
groups2.getInstruments().add(new Instruments());
DefaultSettings ds = new DefaultSettings();
ds.getGroups().add(groups1);
ds.getGroups().add(groups2);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
GroupsAdapter groupsAdapter = new GroupsAdapter();
marshaller.setAdapter(groupsAdapter);
marshaller.setAdapter(groupsAdapter.getInstrumentsAdapter());
marshaller.marshal(ds, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<defaultSettings>
<groups1>
<instruments1/>
<instruments2/>
</groups1>
<groups2>
<instruments1/>
<instruments2/>
</groups2>
</defaultSettings>
Option #2 - Using EclipseLink JAXB (MOXy)'s #XmlVariableNode Extension
Below is a link to how this use case can be mapped using hte #XmlVariableNode extension that we added in EclipseLink JAXB (MOXy):
http://blog.bdoughan.com/2013/06/mapping-bad-xml-enumerated-collection.html
You can do it with #XmlAnyElement annotation.
Simple example groups tag.
#XmlRootElement(name = "defaultTestSettings")
#XmlSeeAlso(Group.class)
public class DefaultTestSettings
{
private static int counter = 1;
private static final String PROP = "group";
List<Group> groups = new ArrayList<Group>();
#XmlAnyElement
public List<JAXBElement<Group>> getElements()
{
final List<JAXBElement<Group>> retVal = new ArrayList<>();
for (final Group g : groups)
{
retVal.add(new JAXBElement(new QName(PROP + counter++), Group.class, g));
}
return retVal;
}
}

JSON to POJO conversion error

I am trying to pass a JSON request to my server where the controller encounters an error while converting the JSON to POJO.
JSON Request
{
"request":[
{"name":"mac"},
{"name":"rosy"}
]
}
My controller function
#RequestMapping(value = "/namelist",
method = RequestMethod.POST,
consumes = { "application/json" },
produces = {"application/json"})
public ... postNameList(#RequestBody NameList names) {}
Public Class NameList extends ArrayList<Name> {}
Public Class Name { private name; ...}
Error
message: "Could not read JSON: Can not deserialize instance of
com.abc.xyz.mypackage.NameList out of START_OBJECT token at [Source:
org.eclipse.jetty.server.HttpConnection$Input#79aac24b{HttpChannelOverHttp#1d109942{r=1,a=DISPATCHED,uri=/namelist},HttpConnection#2cbdcaf6{FILLING},g=HttpGenerator{s=START},p=HttpParser{s=END,137
of 137}}; line: 1, column: 1]
I am not sure what's wrong with the code. I am fairly new to Spring so any help is appreciated.
Your POJO classes should look like this:
class Request {
private List<Name> request;
// getters, setters, toString, ...
}
class Name {
private String name;
// getters, setters, toString, ...
}
Usage:
#RequestMapping(value = "/namelist",
method = RequestMethod.POST,
consumes = { "application/json" },
produces = {"application/json"})
public ... postNameList(#RequestBody Request request) { ... }
I faced similar situation and then created utility to convert JSON objects into Java Objects. Hope this helps.
Here sample.json is the file you want to a Java Object
import com.sun.codemodel.JCodeModel;
import org.jsonschema2pojo.*;
import org.jsonschema2pojo.rules.RuleFactory;
import org.springframework.util.ResourceUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
/**
* Created by Pratik Ambani
*/
class JsonToPojo {
public static void main(String[] args) {
String packageName = "com.practise";
File inputJson = null;
try {
inputJson = ResourceUtils.getFile("classpath:sample.json");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
File outputPojoDirectory = new File("." + File.separator + "Generated Pojo");
outputPojoDirectory.mkdirs();
try {
new JsonToPojo().convert2JSON(inputJson.toURI().toURL(), outputPojoDirectory, packageName, inputJson.getName().replace(".json", ""));
} catch (IOException e) {
System.err.println("Encountered issue while converting to pojo: " + e.getMessage());
e.printStackTrace();
}
}
private void convert2JSON(URL inputJson, File outputPojoDirectory, String packageName, String className) throws IOException {
JCodeModel codeModel = new JCodeModel();
GenerationConfig config = new DefaultGenerationConfig() {
#Override
public boolean isGenerateBuilders() { // set config option by overriding method
return true;
}
#Override
public SourceType getSourceType() {
return SourceType.JSON;
}
};
SchemaMapper mapper = new SchemaMapper(new RuleFactory(config, new Jackson2Annotator(config), new SchemaStore()), new SchemaGenerator());
mapper.generate(codeModel, className, packageName, inputJson);
codeModel.build(outputPojoDirectory);
}
}

Weird error when using foreach loop in java

I am working on a little todolist-program and i'm getting a weird bug that i never had before. I have 4 classes: 1 POJO class that contains the todo-data:
public class Todo implements Comparable {
private String title;
private String task;
private boolean done;
public Todo(String title, String task) {
this.title = title;
this.task = task;
}
public String getTitle() {
return title;
}
public void setTitle(String newTitle) {
title = newTitle;
}
public String getTask() {
return task;
}
public void setTask(String newTask) {
task = newTask;
}
public boolean isDone() {
return done;
}
public void setDone(boolean isDone) {
done = isDone;
}
public int compareTo(Object obj) {
Todo todo = (Todo) obj;
return getTitle().compareTo(todo.getTitle());
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Todo {\n");
sb.append("Title: \"");
sb.append(getTitle() + "\";\n");
sb.append("Task: \"");
sb.append(getTask() + "\";\n");
sb.append("}");
return sb.toString();
}
}
Then I have a class that stores and loads my todos:
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class ListStorage {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
FileWriter writer;
BufferedReader reader;
public void storeList(List list, String filename) throws IOException {
String json = gson.toJson(list);
writer = new FileWriter(filename);
writer.write(json);
writer.close();
}
public List loadList(String filename) throws FileNotFoundException {
reader = new BufferedReader(new FileReader(filename));
List list = gson.fromJson(reader, List.class);
return list;
}
}
Then I have a 'Manager' class that is basically my controller:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Manager {
private List<Todo> todos = new ArrayList<>();
private ListStorage storage = new ListStorage();
public List getTodos() {
return todos;
}
public void setTodos(List newTodos) {
todos = newTodos;
}
public ListStorage getStorage() {
return storage;
}
public void add(String title, String task) {
todos.add(new Todo(title, task));
sort();
try {
storage.storeList(todos, "todos");
} catch(Exception e) {
e.printStackTrace();
}
}
public void remove(int index) {
todos.remove(index);
sort();
try {
storage.storeList(todos, "todos");
} catch(Exception e) {
e.printStackTrace();
}
}
private void sort() {
Collections.sort(todos);
}
}
And finally there is my main-class for testing my code (The bug seems to be here):
class CLITodo {
public static void main(String[] args) {
Manager man = new Manager();
man.add("Hello", "Bye");
man.add("Foo", "Bar");
try {
man.setTodos(man.getStorage().loadList("todos"));
} catch(Exception e) {
}
java.util.List<Todo> todos = man.getTodos();
for (Todo t : todos) {
System.out.println(t);
}
}
}
The error message I get when I leave the <Todo> in CLITodo class is:
Exception in thread "main" java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to Todo at CLITodo.main(CLITodo.java:13)
When I remove <Todo> in CLITodo I get this error:
CLITodo.java:13:19: error: incompatible types
for (Todo t : todos) {
^
required: Todo
found: Object
Why does this error occur? My Manager classes getTodos()-Method returns a List of type Todo yet the compiler tells me that it is just an Object (which it is of course but it is a collection as well, which should actually work).
This is the first time this error occured and I really can't seem to find what is causing it.
When you don't specify what nested type to use to deserialize your JSON, like you do here
List list = gson.fromJson(reader, List.class); // All it knows is that the root json is a List
Gson uses LinkedTreeMap.
What you really want is
List list = gson.fromJson(reader, new TypeToken<List<Todo>>(){}.getType());

JAXB unmarshalling multiple XML elements into single class

I have the following XML structure, which is modelling a single concept across multiple XML elements. This format is not in my control.
<Output>
<Wrapper>
<Channel>
<id>1</id>
<type>x</type>
</Channel>
<Channel>
<id>2</id>
<type>y</type>
</Channel>
<ChannelName>
<id>1</id>
<name>Channel name</name>
</ChannelName>
<ChannelName>
<id>2</id>
<name>Another channel name</name>
</ChannelName>
</Wrapper>
</Output>
I want to model this in a database that I do have control over and can have a more simple Channel table with id, type and name fields. Therefore I would like to unmarshal into a single List<Channel> on the Wrapper class.
Can this be done with #Xml... annotations automatically? I am currently using JAXB to unmarshal into separate #XmlElement(name="Channel") and #XmlElement(name="ChannelName") class lists and then post-processing the transient ChannelName/name on the Channel but I am thinking there must be an easier automated way to map these elements. Or is it a job for XSLT?
It might help to know that the XML is coming in as an HTTP file POST file and I'm using Spring 3, Java and Hibernate. I'm hoping something in EclipseLink JAXB (MOXy) might help :)
#XmlElementWrapper will do the job:
#XmlElementWrapper(name="Wrapper")
#XmlElement(name="Channel")
private List<Channel> channels;
For more advanced cases you can use the #XmlPath extension in EclipseLink JAXB (MOXy):
http://bdoughan.blogspot.com/2010/09/xpath-based-mapping-geocode-example.html
Here is what I have so far. I'm still trying to eliminate the need for the helper objects. This example requires EclipseLink JAXB (MOXy).
Model Objects
Your model objects are:
package example;
import java.util.ArrayList;
import java.util.List;
public class Wrapper {
private List<Channel> channels = new ArrayList<Channel>();
public List<Channel> getChannels() {
return channels;
}
public void setChannels(List<Channel> channels) {
this.channels = channels;
}
}
and:
package example;
import javax.xml.bind.annotation.XmlID;
public class Channel {
private String id;
private String type;
private String name;
#XmlID
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Helper Objects
My current solution involves some helper objects:
package example.adapted;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
import example.Channel;
import example.Wrapper;
#XmlRootElement(name="Output")
#XmlType(propOrder={"channels", "channelNames"})
public class AdaptedWrapper {
private Wrapper wrapper = new Wrapper();
private List<ChannelName> channelNames;
#XmlTransient
public Wrapper getWrapper() {
for(ChannelName channelName : channelNames) {
channelName.getChannel().setName(channelName.getName());
}
return wrapper;
}
#XmlElementWrapper(name="Wrapper")
#XmlElement(name="Channel")
public List<Channel> getChannels() {
return wrapper.getChannels();
}
public void setChannels(List<Channel> channels) {
wrapper.setChannels(channels);
}
#XmlElementWrapper(name="Wrapper")
#XmlElement(name="ChannelName")
public List<ChannelName> getChannelNames() {
return channelNames;
}
public void setChannelNames(List<ChannelName> channelNames) {
this.channelNames = channelNames;
}
}
and:
package example.adapted;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlIDREF;
import example.Channel;
public class ChannelName {
private String name;
private Channel channel;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#XmlIDREF
#XmlElement(name="id")
public Channel getChannel() {
return channel;
}
public void setChannel(Channel channel) {
this.channel = channel;
}
}
Demo Code
package example;
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import example.adapted.AdaptedWrapper;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(AdaptedWrapper.class);
File xml = new File("input.xml");
Unmarshaller unmarshaller = jc.createUnmarshaller();
AdaptedWrapper adaptedWrapper = (AdaptedWrapper) unmarshaller.unmarshal(xml);
Wrapper wrapper = adaptedWrapper.getWrapper();
for(Channel channel : wrapper.getChannels()) {
System.out.println(channel.getName());
}
}
}
You can save your coding time by automating this process in JAXB:
Create a XML schema for your XML using the link below as save it as output.xsd file:
http://www.xmlforasp.net/CodeBank/System_Xml_Schema/BuildSchema/BuildXMLSchema.aspx
Run the batch script file (name it as output.bat) below from the project root folder (.) using JDK as only JDK has xjc.exe tool (fill in the necessary details):
"C:\Program Files\Java\jdk1.6.0_24\bin\xjc.exe" -p %1 %2 -d %3
where...
syntax: output.bat %1 %2 %3
%1 = target package name
%2 = full file path name of the generated XML schema .xsd
%3 = root source folder to store generated JAXB java files
Example:
let say project folder is organized as follows:
.
\_src
Run following at command prompt from (.):
output.bat com.project.xml .\output.xsd .\src
It will create a few files:
.
\_src
\_com
\_project
\_xml
|_ObjectFactory.java
|_Output.java
Then, you can create a few useful methods below to manipulate Output objects:
private JAXBContext jaxbContext = null;
private Unmarshaller unmarshaller = null;
private Marshaller marshaller = null;
public OutputManager(String packageName) {
try {
jaxbContext = JAXBContext.newInstance(packageName);
unmarshaller = jaxbContext.createUnmarshaller();
marshaller = jaxbContext.createMarshaller();
} catch (JAXBException e) {
}
}
public Output loadXML(InputStream istrm) {
Output load = null;
try {
Object o = unmarshaller.unmarshal(istrm);
if (o != null) {
load = (Output) o;
}
} catch (JAXBException e) {
JOptionPane.showMessageDialog(null, e.getLocalizedMessage(), e.getClass().getSimpleName(), JOptionPane.ERROR_MESSAGE);
}
return load;
}
public void saveXML(Object o, java.io.File file) {
Output save = null;
try {
save = (Output) o;
if (save != null) {
marshaller.marshal(save, file);
}
} catch (JAXBException e) {
JOptionPane.showMessageDialog(null, e.getLocalizedMessage(), e.getClass().getSimpleName(), JOptionPane.ERROR_MESSAGE);
}
}
public void saveXML(Object o, FileOutputStream ostrm) {
Output save = null;
try {
save = (Output) o;
if (save != null) {
marshaller.marshal(save, ostrm);
}
} catch (JAXBException e) {
JOptionPane.showMessageDialog(null, e.getLocalizedMessage(), e.getClass().getSimpleName(), JOptionPane.ERROR_MESSAGE);
}
}

Categories