JAXB: Annotations of field declared in superclass differ in subclasses - java

I will try to explain the issue with an example:
Base:
public abstract class Base {
protected Foo foo;
}
Derived1:
//#SomeXMLAnnotations
public class Derived1 extends Base {
//Here i would like to define annotations for foo
#XmlElements({
#XmlElement(name = "foo1", type = Foo1.class),
#XmlElement(name = "foo2", type = Foo2.class)
})
//#AnyAnnoations..
//protected Foo foo;
}
Derived2:
//#SomeXMLAnnotations
public class Derived2 extends Base {
//Here i would like to define annotations for foo too
//But they will differ from the ones defined in Derived1
#XmlElements({
#XmlElement(name = "foo3", type = Foo3.class),
#XmlElement(name = "foo4", type = Foo4.class)
})
//#AnyAnnoations..
//protected Foo foo;
}
The #XmlElements annotation is just an example. It should work with any other annotation too.
I know I could shadow the superclass foo field but I dont think it's a proper way to solve this issue.
So is it possible in java (with JAXB) to override/add annotation of/to a field that is declared in a superclass?

"Ugly" solution:
//#SomeXMLAnnotations
public class Derived1 extends Base {
//#AnyAnnoations..
public Foo getFoo() {
return this.foo
}
protected Foo setFoo(Foo foo) {
this.foo = foo;
}
}
Overriding/adding properties and annotating them seems to work. If getter or setter is never used somewhere it's pretty ugly though, because this has to happen in every subclass.

Related

How to serialize/deserialize (json) a class that has an attribute that's an interface

I'd like to serialize/deserialize (json) a class that contains an attribute that is an interface, but the underlying class doesn't have any attributes. The below is my most simplified case and my best attempt at what to do.
This throws an error when trying to deserialize No suitable constructor found for type [simple type, class com.example.Bar]: can not instantiate from JSON object (need to add/enable type information?) at [Source: java.io.StringReader#301ec38b; line: 1, column: 2]
public interface FooInterface {
String doThing();
}
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
#EqualsAndHashCode
public class Foo implements FooInterface {
#Override
public String doThing() {
return "foo";
}
}
#Getter
#Setter
#EqualsAndHashCode
public class Bar {
FooInterface foo;
public Bar(FooInterface foo) {
this.foo = foo;
}
}
#Test
public void fooTest() throws IOException {
Foo foo = new Foo();
Bar bar = new Bar(foo);
String serialized = new ObjectMapper().writeValueAsString(bar); // = {"foo":{}}
Bar deserialized = new ObjectMapper().readValue(serialized, Bar.class);
Assert.assertEquals(bar, deserialized);
}
Please add default constructor to class Bar and I guess your issue should be resolved.
#Getter
#Setter
#EqualsAndHashCode
public class Bar {
FooInterface foo;
public Bar() {}
public Bar(FooInterface foo) {
this.foo = foo;
}
}
Do let me know if this doesn't solve your problem, I will try to dig deeper.
As #Aditya mentioned I was missing the default constructor which was causing the error I was having, but then the new error led me to finding this question which was the crux of the problem that this question was asking about.
Looks like I misunderstood what the JsonAutoDetect annotation did. Below is the code that ended up working for me.
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Foo.class),
})
public interface FooInterface {
String doThing();
}
#EqualsAndHashCode
public class Foo implements FooInterface {
#Override
public String doThing() {
return "foo";
}
}
#Getter
#Setter
#EqualsAndHashCode
public class Bar {
FooInterface foo;
public Bar() {}
public Bar(FooInterface foo) {
this.foo = foo;
}
}
#Test
public void fooTest() throws IOException {
Foo foo = new Foo();
Bar bar = new Bar(foo);
String serialized = new ObjectMapper().writeValueAsString(bar); // {"foo":{"type":"Foo"}}
Bar deserialized = new ObjectMapper().readValue(serialized, Bar.class);
Assert.assertEquals(bar, deserialized);
}

Jaxb custom element from two fields of an object

Is there a way with jaxb to create a custom element from two fields from an object?
Here's an example of what I have and what I want. I realize I could extract the fields into a seperate annotated class, but I'm curious of theres a way to do something similar to this.
#XmlRootElement()
public class Foo {
public String bar
public String baz
}
Expected output xml:
<foo>
<customElement bar="barValue">bazValue</customElement>
</foo>
Thanks!
class for foo
#XmlRootElement()
public class Foo {
private customElement CustomElement;
public CustomElement getCustomElement(){
return customElement;
}
#XmlElement
public void setCustomElement(CustomElement customElement){
this.customElement = customElement;
}
}
class for custom element
#XmlAccessorType(XmlAccessType.FIELD)
public class CustomElement {
#XmlAttribute
private String bar;
#XmlValue
private String baz
// set getters and setters
}

lombok #Builder(toBuilder = true) compilation error when used on constructor of a sub class

My code is as followd
package test.lombok;
import lombok.*;
#AllArgsConstructor(access = AccessLevel.PROTECTED)
#Getter
public class SuperClass {
private int foo;
#Getter
public static class SubClass extends SuperClass {
private int bar;
#Builder(toBuilder = true)
private SubClass(int foo, int bar) {
super(foo);
this.bar = bar;
}
}
}
As showed above, I'm trying to use #Builder(toBuilder = true) on a sub class.
When toBuilder set to false, there is no problem at all.
But when I set toBuilder = true, I got an compilation error "Error:java: foo has private access in test.lombok.SuperClass".
I'm wondering why does this happen and how to fix this.
Lombok tries to create toBuilder method in SubClass when the attribute toBuilder is set to true in annotation #Builder. The method returns the SubClassBuilder class. Here is the what the toBuilder method would look like,
public SuperClass.SubClass.SubClassBuilder toBuilder() {
return (new SuperClass.SubClass.SubClassBuilder())
.foo(this.foo).bar(this.bar);
}
As you notice, toBuilder method tries to access foo attribute directly and not by method getFoo. Since foo is private and belongs to the parent class, SuperClass, you get the following error:
Error:java: foo has private access in test.lombok.SuperClass
The problem is because of how the toBuilder method is implemented in SubClass:
public SuperClass.SubClass.SubClassBuilder toBuilder() {
return (new SuperClass.SubClass.SubClassBuilder()).foo(this.foo).bar(this.bar);
}
Instead of this.foo it should be super.foo, and the code would compile. Accessing super.foo is possible in this case because SubClass is an inner class of the SuperClass, otherwise, Java would disallow super.foo too.
If you want to see the code generated by lombok by for yourself, declare foo as public, then compile, then delombok (or decompile) and you will see code like this (then change the property to private to see where the error occurs):
import java.beans.ConstructorProperties;
public class SuperClass {
public int foo;
#ConstructorProperties({"foo"})
protected SuperClass(int foo) {
this.foo = foo;
}
public int getFoo() {
return this.foo;
}
public static class SubClass extends SuperClass {
private int bar;
private SubClass(int foo, int bar) {
super(foo);
this.bar = bar;
}
public static SuperClass.SubClass.SubClassBuilder builder() {
return new SuperClass.SubClass.SubClassBuilder();
}
public SuperClass.SubClass.SubClassBuilder toBuilder() {
return (new SuperClass.SubClass.SubClassBuilder()).foo(this.foo).bar(this.bar);
}
public int getBar() {
return this.bar;
}
public static class SubClassBuilder {
private int foo;
private int bar;
SubClassBuilder() {
}
public SuperClass.SubClass.SubClassBuilder foo(int foo) {
this.foo = foo;
return this;
}
public SuperClass.SubClass.SubClassBuilder bar(int bar) {
this.bar = bar;
return this;
}
public SuperClass.SubClass build() {
return new SuperClass.SubClass(this.foo, this.bar);
}
public String toString() {
return "SuperClass.SubClass.SubClassBuilder(foo=" + this.foo + ", bar=" + this.bar + ")";
}
}
}
}
EDIT: Thanks to #maaartinus for pointing me to super.foo, the answer is updated with that info.
AFAICT this is a Lombok bug. There are three ways, how to access foo and only one of them works:
plain foo leads to "Cannot make a static reference to the non-static field foo"
this.foo as used by Lombok leads to "The field SuperClass.foo is not visible"
super.foo works!
AFAIK everything declared in the same source file is accessible somehow, but finding the proper expression might be tricky.
Starting from Lombok 1.18.12, you can use the new, experimental feature #SuperBuilder.
It supports toBuilder :
import lombok.*;
import lombok.experimental.SuperBuilder;
#AllArgsConstructor(access = AccessLevel.PROTECTED)
#Getter
#ToString // For demonstration purposes only; only used in the main method.
#SuperBuilder(toBuilder = true)
public class SuperClass {
private int foo;
#Getter
#ToString(callSuper = true) // For demonstration purposes only; only used in the main method.
#SuperBuilder(toBuilder = true)
public static class SubClass extends SuperClass {
private int bar;
private SubClass(int foo, int bar) {
super(foo);
this.bar = bar;
}
}
public static void main(String[] argv) {
SubClass sc = SubClass.builder()
.foo(1)
.bar(2)
.build();
System.out.println(sc);
}
}
Prints:
SuperClass.SubClass(super=SuperClass(foo=1), bar=2)

Jackson serializes strange output

I am using Jackson to convert json to an object. However, the json looks wrong. Here is what I am seeing:
"interfaces": {"interfaces": [
"HA_1",
"HA_2"
]},
There should not be two interfaces. I want to see:
"interfaces": [
"HA_1",
"HA_2"
]},
I am not sure how this is happening. I can show you my conversion classes:
#XmlAccessorType(XmlAccessType.NONE)
public class InterfacesRep implements Serializable {
private static final long serialVersionUID = -1503363608473342020L;
#XmlElement(name = "interface", type = String.class)
private Collection<String> all = new ArrayList<String>();
public InterfacesRep() {}
public InterfacesRep(Collection<String> all) {
this.all = all;
}
public Collection<String> getAll() {
return all;
}
public void setAll(List<String> all) {
this.all = all;
}
}
And the outer class:
public class OuterRep {
private static final long serialVersionUID = -1719378545790376294L;
#XmlElement(name = "interfaces", type=InterfacesRep.class)
private InterfacesRep interfaces;
public OuterRep() {
}
public InterfacesRep getInterfaces() {
return interfaces;
}
public void setInterfaces(InterfacesRep interfaces) {
this.interfaces = interfaces;
}
}
Do you know why I see "interfaces" twice?
Because you are defining it on the property at both levels.The outer class has a property name called "interfaces" and the inner class's Collection is also named "interfaces".
This simplest fix (in my mind) would be to not use a wrapper class for the Collection. Just put the collection in the outer class.
On a side note, why are you using Jackson's XML annotations to serialize JSON?

XmlRootElement annotation at abstract class - possible?

This is what I'm trying to do:
#XmlRootElement(name = "bar")
#XmlAccessorType(XmlAccessType.NONE)
public abstract class Bar {
}
public final class Foo extends Bar {
#XmlElement
public String getMsg() {
return "hello, world!";
}
}
Now I'm trying to marshall an instance of class Foo:
com.sun.istack.SAXException2: unable to marshal type "Foo" as
an element because it is missing an #XmlRootElement annotation
What is a workaround?
Can you get away without the #XmlRootElement annotation on the superclass. Instead you should put it on each of your subclasses. JAX-B will still know about the superclass fields.

Categories