I'm pretty new to xstream.
I'm working on a model class that looks like the following:
#XStreamAlias("MyRootClass")
public class MyRootClass {
// A bunch of other classes as child nodes
#XStreamAlias("MyClassList")
private List<MyClass> foo;
}
Now, is there a way for me to produce the following XML when marshalling, without modifying the class?
<MyRootClass>
<!-- a bunch of other class nodes -->
<MyClassList COUNT="3">
<MyClass>MyClass 1</MyClass>
<MyClass>MyClass 2</MyClass>
<MyClass>MyClass 3</MyClass>
</MyClassList>
</MyRootClass>
The main issue is how to add the attribute "COUNT" to the list of MyClass gracefully. It will always show the number of MyClass inside MyClassList.
I am not allowed to modify the model class. However, I can implement my own converter to achieve the above.
The question is: what's the best way to do it?
There are lots of other (complex) classes within MyRootClass, and they've all been aliased & annotated. So, creating a 'marshal' method from scratch might be overkill?
welp, I figured it out.
Not sure if this is the "best" way to do it, but it works.
the key is using converters and nested converters.
this is what I ended up doing:
public class MyRootClassConverter implements Converter {
#Override
public boolean canConvert(Class type) {
return type.equals(MyRootClassConverter.class);
}
#Override
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
MyRootClass rootClass= (MyRootClass) source;
// as necessary
writer.startNode("Other Class1");
writer.setValue("Other Class Value");
writer.endNode();
if (rootClass.getMyClassList() != null || !rootClass.getMyClassList().isEmpty()) {
writer.startNode("MyClassList");
writer.addAttribute("COUNT", String.valueOf(rootClass.getMyClassList().size()));
for (MyClass child : rootClass.getMyClassList()) {
writer.startNode("MyClass");
context.convertAnother(child); // this is where the nesting happens
writer.endNode();
}
writer.endNode();
}
}
}
Related
I have two enums I've created for my Java project, and both of them need to be serialized and deserialized using Gson. The problem is, I need to use a value on each enum Field as the serialized value.
As an example, I have these enums:
Options Enum
Language Enum
My hope is that I am able to serialize both enums using the key value provided to both. This is a really simplified example, but it still perfectly describes my situation.
I tried using custom serializer classes for both:
Options Serializer
Language Serializer
And yes, I did register both using registerTypeAdapter(type, adapter)
The strange this is, it would work for one enum, serializing to the correct value, but not the other. I suspect it's because the class that's being serialized is formatted similar to this:
public class Item {
public Language language;
public List<Options> options;
}
Where in this case, Language is serialized properly, but the Options enum is not, just returning the enum value name.
I'm not sure if there's some special way I need to handle this, but it's getting frustrating.
EDIT: I know about using the #SerializedName() annotation, but both of the enums I'm using have hundreds of entries and the keys that are part of the enum are used elsewhere throughout the program as well. Using #SerializedName(), at least in my case, I don't think would be feasible.
Write an adapter for Item. I would add more but laptop on 1%.
static class ItemAdapter extends TypeAdapter<Item> {
#Override
public void write(JsonWriter out, Item value) throws IOException {
out.beginObject();
out.name("language").value(value.language.key);
out.name("options");
out.beginArray();
for (Option option : value.options) {
out.value(option.key);
}
out.endArray();
out.endObject();
}
#Override
public Item read(JsonReader in) throws IOException {
in.beginObject();
in.nextName();
String languageKey = in.nextString();
in.nextName();
in.beginArray();
List<String> optionKeys = new ArrayList<>();
while (in.hasNext()) {
optionKeys.add(in.nextString());
}
in.endArray();
in.endObject();
return new Item(Language.BY_KEY.get(languageKey),
optionKeys.stream()
.map(Option.BY_KEY::get)
.collect(Collectors.toList()));
}
}
Let's suppose the following scenario:
I have a list of objects of the following type:
public class MyObject {
private String name
private SomeClass someField
private List<Fact> facts
}
The fields name and someField are just to show that the class has some regular members. You can suppose that it's known how to convert these classes to xml.
Fact is an interface where the implementations are not known to me but provided by plugins. Plugins can be required to provide arbitrary code, but I would like to make it as simple as possible.
I want to save and load these objects to xml. Note that while loading the xml, not all implementations may be present (the xml might have been written with a different set of plugins). I want to be able to still read the xml and not lose any information when saving again. In other words: I'm willing add a field such as List<Element> or List<String> to the class and when reading the xml, all parts where a plugin is present should be read into the corresponding Facts, while all parts without a plugin should be stored in an Element or String and when saving again, both lists get saved and could be read by a program having all plugins.
How best to achieve this using JAXB?
One way I can see is to use Map<Class, org.w3c.dom.Element> instead of List<Fact> which can be converted to xml by JaxB and then let any plugin provide custom code converting from and to "their" element using the org.w3c.dom API, but using that API is somewhat cumbersome, so I wonder whether there is a better way?
No idea about best, but one approach that comes close to what you describe is this:
JAXB doesn't work with interfaces; best it can do would be an abstract class. Meaning you need to use List<Object> or List<AbstractFact>. (but you can enforce some restriction in the getter, pluginresolver or afterUnmarshall()).
Your plugin provides the basic classes for the extension (SPI would be the usual approach). You collect them and (after validation) use them to create your JAXBContext. (If you want to support multiple interfaces, maybe provide them by different methods).
In the xml you need to have a type marker like this: <fact xsi:type=\"aFact\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">. If you create the xml with jaxb it will be created autmatically. (The classes need to have the #XmlRootElement annotation).
Here is a stripped down example:
interface Fact {
}
#XmlRootElement
#XmlAccessorType(XmlAccessType.NONE)
class R {
#XmlElement(name = "fact")
private List<Object> facts;
#SuppressWarnings("unchecked")
public List<Fact> getTest() {
if (facts == null) {
facts = new ArrayList<>();
}
return (List<Fact>) (Object) facts;
}
public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
// check if all facts implement same interface
for(Object object:facts) {
if (!(object instanceof Fact)) {
throw new IllegalArgumentException("Unsupported type in facts list");
}
}
}
}
#XmlAccessorType(XmlAccessType.NONE)
#XmlRootElement(name = "aFact")
class AFact implements Fact {
#XmlElement
private String a;
public AFact() {
}
public AFact(String a) {
this.a = a;
}
public String getA() {
return a;
}
#Override
public String toString() {
return "AFact [a=" + a + "]";
}
}
public class Jax {
public static void main(String[] args) throws JAXBException {
String xml = "<r><fact xsi:type=\"aFact\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><a>ba</a></fact></r>";
List<Class<?>> contextClasses = new ArrayList<>();
contextClasses.add(R.class);
contextClasses.addAll(getClassesFromPlugin());
JAXBContext context = JAXBContext.newInstance(contextClasses.toArray(new Class<?>[0]));
R entity = (R) context.createUnmarshaller().unmarshal(new StringReader(xml));
System.out.println(entity.getTest());
R r = new R();
r.getTest().add(new AFact("ab"));
context.createMarshaller().marshal(r, System.out);
}
private static List<Class<?>> getClassesFromPlugin() {
List<Class<?>> asList = Arrays.asList(AFact.class);
for(Class<?> cls:asList) {
if (!Fact.class.isAssignableFrom(cls)) {
throw new IllegalArgumentException("Unsupported class");
}
}
return asList;
}
}
I have sort of a deep class hierarchy and i want to tell JAXB to bind all the classes. I have sort of the following :
#XmlSeeAlso(B.class)
Class A {}
#XmlSeeAlso(C.class)
Class B extends A{}
#XmlSeeAlso(D.class,E.class,...)
Class C extends B{}
Class D extends C{}; Class E extends C{} ... and so on
Is there any way i can get to bind all these classes without using the #XmlSeeAlso in every super class and without mentioning all subclasses because i have many.
As mentioned in comments, Java doesn't support requested feature of getting all subclasses at runtime viaa reflection.
But it should be possible to inspect all classes in a project at it's compilation time, and generate a jaxb.index in your jar file.
An example (not complete, thus not directly working, but to demonstrate the idea) of such annotation processor can look like this:
#SupportedAnnotationTypes("*")
#SupportedSourceVersion(SourceVersion.RELEASE_8)
public class JaxbProcessor extends AbstractProcessor {
#Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
// Java 8 compiler plugin API to hook on compilation of every single class.
JavacTask.instance(env).addTaskListener(new TaskListener() {
// Prepare the writer
PrintWriter writer = new PrintWriter(env.getFiler().createResource(/* Details of output jaxb.index file */).openWriter());
Set<TypeElement> jaxbParents = new HashSet<>();
#Override public void started(TaskEvent taskEvent) {
// Nothing needs to be done here.
}
#Override public void finished(TaskEvent taskEvent) {
if(taskEvent.getKind() == ANALYZE) {
// This is where the compiler invokes our code.
// Side effect of this inspection is to collect all classes, that should be included in our jaxb.index
// into the jaxbParents set.
inspect(taskEvent.getTypeElement());
// Now simply write it to the file (output details to be provided).
// We should actually only write down difference from previous invocation. Let me fix it later.
jaxbParents.forEach(writer::println);
}
}
private void inspect(TypeElement type) {
// First inspect current class element
testForJaxbParent(type);
// Do not forget to inspect also inner classes.
type.getEnclosedElements().stream().filter(TypeElement.class::isInstance).map(TypeElement.class::cast).forEach(this::testForJaxbParent);
}
/**
* Test if the type should be added to JAXB index file.
*/
private boolean testForJaxbParent(TypeElement type) {
if(jaxbParents.contains(type)) {
// It's already in the set, so no need to bother with it.
return true;
}
if(type.getAnnotation(JaxbRoot.class) != null || testForJaxbParent((TypeElement) env.getTypeUtils().asElement(type.getSuperclass()))) {
// If our type is annotated with our special "extension" to JAXB - JaxbRoot, it means, that it is our
// root class, that needs to be added to the jaxb.index.
//
// If it is not annotated, then still test hierarchy of superclasses recursively, and if there is any
// superclass being the root, then add it including all children on the stack at the return from the
// recursion.
return jaxbParents.add(type);
}
return false;
}
});
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// No real annotation processing needed.
return false;
}
}
Once you have the annotation JaxbRoot and this processor in a jar, and ideally also the service descriptor to let Java automatically find this processor in the jar, then simply add the jar to your classpath, annotate only your root class, and you'll get generated jaxb.index with all it's subclasses.
And even if you have your project split into multiple jars, and have your root class in one, and children in another, still the processor get's invoked and generates the index file per jar. Then you'll just have to merge them all together, which can be just one utility class delivered together with the processor.
I wanted to try the factory pattern and was able to implement it, but when
generating for more than a few classes, i thought this will be ugly!! so any clarity or suggestions would be really appreciated...
My Superclass:
public abstract class Output {
public abstract void generate(Data dat); }
i got my other classes extending from Output like
public class generateXML extends Output{
.
.
.
}
My question is related to here:
public class generatorFactory(){
public Output generate(String str){
// or getting an Object as an argument like (Object obj)
if(str.equals("xml"){
return new generateXML();
}
else if.........
......
}
Is there any way we can determine the subclass type avoiding checking for each type??
You should consider replacing your if-else chain with a map.
Rather than having to write the code that checks for all the strings you want to support you just have a copule of lines to retrieve the element from the map.
You will, of course, need some more configuration code to put the items in the map, but that should be trivial.
Here it is a nice post about this topic (in PHP)
You can use newInstance() to instanciate a generator whose classname you've built from the parameter:
public Generator getGenerator (final String type)
{
final Class generatorClass = ClassLoader.getSystemClassLoader().loadClass("Generator"+type);
final Generator generator = (Generator) (generatorClass.newInstance());
return generator;
}
PS: I highly rate you to follow the rules of Java: if generateXML is a class, it should be written GenerateXML.
More over: take care by naming your classes. (1) An Object generateXML shouln'd extend Output, because it isnt' an output. (2) "GenerateXML" is a verb, i.e. an action. It is therefore not a correct word to name an object, but a method. You could name the object per example "XMLGenerator".
You can use Reflection.
Object generated = getClass().getMethod("generate" + type.toUpperCase()).invoke(this);
public Object generateXML();
public Object generateJSON();
public Object generateCSV();
You can use enum which can be passed to factory and return factory object based on enum passed. The only thing is you can not export it as API.
enum Type
{
XML {
#Override
public Object getFactory() {
// TODO Auto-generated method stub
return null;
}
};
public abstract Object getFactory();
}
If you have to expose it like API then you can do something like below.
interface IType {
public abstract Object getTypeFactory();
}
enum Type implements IType {
XML {
#Override
public Object getTypeFactory() {
// TODO Auto-generated method stub
return null;
}
};
}
And change Factory method implemetation to
public static Object getFactoryByType(String name) {
Type type = Type.valueOf(name);
return type.getTypeFactory();
}
Since you have to call new everytime I'm not sure you can bypass the branching process. Someone has to know what to give you back.
If it was for singletons you could initialize an HashMap "xml"=>generateXML singleton
After second though, you may modify your String attribute for differents Type classes MyTypeXML, MyTypeJSON, ...
and then use method with the same name but different type.
public Output generate(MyTypeXML xml) { // This will go for XML }
public Output generate(MyTypeJSON json) { // This will go for JSON }
But for factories, I'm not really against the if...else coding.
I have the following class (legacy; not annotatable) that is serialized with a custom converter:
class Test {
// some other variables
List<SomeType> someTypeList;
}
A properly working converter for SomeType is already available. However I want the list to be serialized as if it was annotated with #XStreamAlias("someTypes").
In the end I expect the following format for someTypeList:
<someTypes class="list-type">
<someType>
....
</someType>
...
</someTypes>
How do I have to implement the marshal/unmarshal method to get the desired output? Calling context.convertAnother(someTypeList) didn't yield the expected result as the surrounding <someTypes> tag was missing.
You wise to get the structure:
<someTypes class="list-type">
<someType>
....
</someType>
...
</someTypes>
Look at the following code. For your list you need to tag:
#XStreamImplicit(itemFieldName="someType")
List<someType>List;
Now, depending on what you got inside, you might need to create a custom converter. To refer to that you change a bit like this:
#XStreamImplicit(itemFieldName="someType") #XStreamConverter(YourOwnConverter.class)
List<SomeType> someTypeList;
Then create a converter class (YourOwnConverter) that would know how to un/marshal:
public boolean canConvert(Class type)
{
return type.equals(SomeType.class);
}
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context)
{
SomeType mytype = (SomeType) source;
writer.addAttribute("position", mytype.getPosition());
writer.setValue(mytype.getId());
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context)
{
SomeType mytype = new SomeType();
String position = reader.getAttribute("position");
......
return mytype ;
}
Use this as example:
http://x-stream.github.io/converter-tutorial.html
Is there a addImplicitCollection called on the xstream object during configuration somewhere which causes the someTypes tag to be skipped ?