interface Foo
public String key()
class Bar implements Foo
public int id;
public String name;
public Bar2 bar2; <--- bar2.key() should be used as json value
String key() { return name }
class Bar2 implements Foo
public int id;
public int name;
public Bar bar; <--- bar.key() should be used as json value
String key() { return name }
Whenever any object of type Foo is referenced in serialization, it's value should be object.key().
For deserialization, the value to should be used to lookup the actual object (Bar, Bar2, etc)
How can this be done with Jackson?
you need a getter method for the common property. Change Foo into abstract class and define the property and getter method there.
public abstract class Foo implements Serializable{
public String name;
public Foo bar;
public Foo() {
}
public String getBar(){
return bar.name;
}
public void setBar(Foo bar) {
this.bar = bar;
}
}
class Bar extends Foo{
public int id;
public Bar() {
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
Bar bar = new Bar();
Bar2 bar2 = new Bar2();
bar.id = 1; bar.name = "bar1";bar.setBar(bar2);
bar2.id = 2; bar2.name = "bar2"; bar2.setBar(bar);
System.out.println(mapper.writeValueAsString(bar));
}
}
public class Bar2 extends Foo {
public int id;
public Bar2() {
}
}
Related
I have problems with persisting the following construct with JPA.
#MappedSuperclass
public abstract class SuperFoo {
#Embedded
private Bar bar;
public SuperFoo() {}
public SuperFoo(...) {
...
this.bar = new Bar("barKey");
}
...
public void setBar(Bar bar) {
this.bar = bar;
}
public Bar getBar() {
return bar;
}
}
#Embeddable
public class Bar {
#ElementCollection
Map<String, Long> durations;
#ElementCollection
Map<String, Integer> integers;
public Bar() {}
public Bar(String barKey) {
his.durations = new HashMap<String, Long>();
this.integers = new HashMap<String, Integer>();
durations.put(key, new Long(15));
integers.put(key, new Integer(10));
}
... setters and getters.....
}
#Entity
public class Foo extends SuperFoo {
#Id
private String id;
public Foo() {}
public Foo(String id, *superArguments*) {
super(*superArguments*);
this.id = id;
}
public String getId() { return id; }
public void setId(String id) {
this.id = id;
}
}
#Entity
public class FooContainer {
#Id
String id;
#OneToMany(cascade = CascadeType.ALL)
List<Foo> fooList;
public FooContainer() {}
public FooContainer(String id) {
this.id = id;
this.fooList = new ArrayList<>();
}
public void addFoo(Foo foo) {
this.fooList.add(foo);
}
public void setFooList(List<Foo> fooList) {this.fooList = fooList;}
public List<Foo> getFooList() { return fooList; }
}
Method where persisting takes place:
public void method() {
FooContainer fooContainer = fooContainerRepository.getOne(...);
fooContainer.addFoo(new Foo(...));
fooContainerRepository.save(fooContainer);
}
JPA creates the following tables (looks fine):
foo
foo_durations
foo_integers
Now, when I save an instance of Foo, everything gets persisted (fields in Foo). BUT I get no entries in foo_durations and foo_integers. Furthermore, I get no exceptions.
I did a little research myself and found the following:
"An embeddable class that is contained within an element collection must not contain an element collection."
I think, here, that is not the case because my embeddable class "Bar" is not contained within an element collection. So does anybody know, what I might have done wrong?
Thanks in advance!
Update:
I forgot to mention that the Bar Object inside SuperFoo is not set via constructor but calculated inside the constructor.
POJO1
#Builder
Class POJO1
{
private String astring;
private String bstring;
}
POJO2
#Builder
Class POJO2
{
private String astring;
private String bstring;
}
builderMethod
private POJO1 buildPOJO()
{
return POJO1.builder().withaString().withbString().build();
}
What i want
POJO1 pojo = buildPOJO();
POJO2 pojo = buildPOJO(); // Same method to build both pojo's
I have these two pojo's in my code. And I have to build them at several places. Both the pojo's will always contain same fields, and the number of fields is substantial too. I have to build a buildPOJO method by which i can make the objects of both POJO. I don't have a clear-cut idea but as the fields are always similar. Can casting or any other way will be helpful in achieving this?
With a proxy wrapper you can use a common interface without having to implement it in your classes. Extract the common setters to an interface and use it in your builder to set the common fields.
The proxy code is minimal:
public class Wrapper<T> implements InvocationHandler {
private final T delegate;
public Wrapper(T delegate) {
this.delegate = delegate;
}
public static <T, I> I proxy(T delegate, Class<I> facade) {
return (I) Proxy.newProxyInstance(
facade.getClassLoader(),
new Class[] { facade },
new Wrapper(delegate)
);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return delegate.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes()).invoke(delegate, args);
}
}
And simple enough to use in the builder:
class PojoBuilder {
private String foo;
private int bar;
private String a, b;
public PojoBuilder withFoo(String foo) {
this.foo = foo;
return this;
}
public PojoBuilder withBar(int bar) {
this.bar = bar;
return this;
}
public PojoBuilder withA(String a) {
this.a = a;
return this;
}
public PojoBuilder withB(String b) {
this.b = b;
return this;
}
public PojoA buildA() {
PojoA a = new PojoA();
buildCommon(Wrapper.proxy(a, Pojo.class));
a.setA(this.a);
return a;
}
public PojoB buildB() {
PojoB b = new PojoB();
buildCommon(Wrapper.proxy(b, Pojo.class));
b.setB(this.b);
return b;
}
private void buildCommon(Pojo common) {
common.setFoo(foo);
common.setBar(bar);
}
}
// Common setters for all pojos
interface Pojo {
void setFoo(String foo);
void setBar(int bar);
}
// One of the pojos.
// Note that this doesn't actually implement Pojo
class PojoA {
private String foo;
private int bar;
private String a;
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
public int getBar() {
return bar;
}
public void setBar(int bar) {
this.bar = bar;
}
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
}
class PojoB {
private String foo;
private int bar;
private String b;
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
public int getBar() {
return bar;
}
public void setBar(int bar) {
this.bar = bar;
}
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
}
I use different NoSQL databases and depending on the database I need to name the "id" different. So for example in OrientDB the id is named "#rid"
#JsonProperty("#rid")
private String id;
And for MongoDB the id is named "_id"
#JsonProperty("#_id")
private String id;
I do not know what is wrong with the modern DB developers not just naming the id field "id" ^^. But now I have a problem. How can I dynamically serialize/deserialize the id field in some case as "#rid" and in another case as "_id"?
EDIT:
Based on rmullers suggestion I have tried to use mixins. So I have for example:
public interface IdMixins {
}
public interface MongoIdMixIn extends IdMixins {
#JsonProperty("_id") String getId();
#JsonProperty("_id") void setId(String id);
}
public interface OrientIdMixIn extends IdMixins{
#JsonProperty("#rid") String getId();
#JsonProperty("#rid") void setId(String id);
}
Where IdMixins is a completly empty interface just used to get more controll which interfaces can be passet to the mapper.
Then there is a class:
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="#javaClass")
public abstract class AbstractBean implements Serializable {
private static final long serialVersionUID = -1286900676713424199L;
// #JsonProperty("#rid")
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
But when I run this simple test, the output is still "id":
public class MixinTest {
public static void main(String[] args) throws JsonProcessingException {
Foo f = new Foo();
f.setId("123");
f.setBar("lala");
ObjectMapper mapper = new ObjectMapper();
ObjectMapper m2 = mapper.copy();
m2.addMixInAnnotations(AbstractBean.class, MongoIdMixIn.class);
System.out.println(m2.writeValueAsString(f));
ObjectMapper m3 = mapper.copy();
m3.addMixInAnnotations(AbstractBean.class, OrientIdMixIn.class);
System.out.println(m3.writeValueAsString(f));
}
public static class Foo extends AbstractBean {
private String bar;
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
}
Outputs:
{"#javaClass":"test.MixinTest$Foo","id":"123","bar":"lala","#class":"Foo"}
{"#javaClass":"test.MixinTest$Foo","id":"123","bar":"lala","#class":"Foo"}
Have you tried using http://wiki.fasterxml.com/JacksonMixInAnnotations? Then you can use an OrientDbMixin and a MongoDbMixin with different #JsonProperty configuration.
Update: Working example
public final class JacksonTest {
static final class ExampleBean {
private String id;
private String bar;
#JsonProperty("donotwanttoseethis")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
public interface MongoIdMixIn {
#JsonProperty("_id") String getId();
}
public interface OrientIdMixIn {
#JsonProperty("#rid") String getId();
}
private final static Logger LOG = LoggerFactory.getLogger();
public static void main(String[] args) throws JsonProcessingException {
ExampleBean bean = new ExampleBean();
bean.setId("1234");
bean.setBar("lala");
ObjectMapper m2 = new ObjectMapper();
m2.addMixInAnnotations(ExampleBean.class, MongoIdMixIn.class);
LOG.info(m2.writeValueAsString(bean));
ObjectMapper m3 = new ObjectMapper();
m3.addMixInAnnotations(ExampleBean.class, OrientIdMixIn.class);
LOG.info(m3.writeValueAsString(bean));
}
}
From the tutorial I had the impression that this should work (simplified example):
public class Foo {
private String bar;
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
public static class Qux {
private String foobar;
public String getFoobar() {
return foobar;
}
public void setFoobar(String foobar) {
this.foobar = foobar;
}
}
}
...
String in = "{ \"bar\": \"123\", \"qux\" : {\"foobar\": \"234\"}}";
ObjectMapper mapper = new ObjectMapper();
Foo obj = mapper.readValue(in, Foo.class);
However, I get an error
UnrecognizedPropertyException: Unrecognized field "qux" (Class Foo), not marked as ignorable
I'm running 2.2.2
You can configure ObjectMapper to ignore fields it doesn't find in your class with
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
If not configured this way, it will throw exceptions while parsing if it finds a field it does not recognize on the class type you specified.
It will work if you pull your Qux class out of Foo
public class Foo {
private String bar;
// added this
private Qux qux;
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
// added getter and setter
public Qux getQux() {
return qux;
}
public void setQux(Qux qux) {
this.qux = bar;
}
}
public static class Qux {
private String foobar;
public String getFoobar() {
return foobar;
}
public void setFoobar(String foobar) {
this.foobar = foobar;
}
}
The Foo class needs an instance property of type Qux for automatic deserialization to work. The way the Foo class is currently defined, there is no destination property to inject the qux JSON object values.
public class Foo {
private String bar;
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
// additional property
private Qux qux;
public Qux getQux() {
return qux;
}
public void setQux(Qux value) {
qux = value;
}
public static class Qux {
private String foobar;
public String getFoobar() {
return foobar;
}
public void setFoobar(String foobar) {
this.foobar = foobar;
}
}
}
Please take a look at the following example. I got the exception that XmlIDREF annotation is not allowed in class Bar. If you use the concrete Class Bar instead of IBar it works perfectly. But i have to use the interface. Is there a solution for this problem?
#XmlRootElement
public class Foo {
#XmlElement(name = "bar")
public List<Bar> bars;
public String fooProp;
}
public interface IBar {
#XmlID
String getId();
void setId(String id);
}
#XmlRootElement
public class Bar implements IBar {
#XmlIDREF
#XmlAnyElement
public IBar bar;
public String barProp;
private String id;
#Override
#XmlID
public String getId() {
return this.id;
}
#Override
public void setId(String id) {
this.id = id;
}
}
public static void main(String[] args) throws Exception {
File file = null;
file = new File("somewhere");
try(Writer w = new FileWriter(file)){
JAXBContext context = JAXBContext.newInstance("jaxbtest");
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
Foo foo = new Foo();
foo.fooProp ="FooProperty";
Bar bar1 = new Bar();
bar1.barProp = "BarProperty1";
bar1.setId("1");
Bar bar2 = new Bar();
bar2.barProp = "BarProperty2";
bar2.setId("2");
bar1.bar = bar2;
bar2.bar = bar1;
List<Bar> list = new ArrayList<Bar>();
list.add(bar1);
list.add(bar2);
foo.bars = list;
m.marshal(foo, w);
} catch (Exception e) {
e.printStackTrace();
}
}
A workaround is to use #XmlElements annotation and tell the jaxb context all the possible implementations of IBar:
#XmlIDREF
#XmlElements( #XmlElement( type = Bar.class )/*, #XmlElement( type = AnotherBar.class) */ )
public IBar bar;