how to deal with interfaces at jackson? - java

I am dealing with this problem. I have this class:
public class SemaphoreResponse {
ISemaphore semaphore;
StatusHolder statusHolder;
public SemaphoreResponse() {
super();
}
// Getters and setters
}
I want to convert my json string to that class, and it throws me this exception
org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.despegar.henry.automation.services.semaphoreservice.response.ISemaphore, problem: abstract types can only be instantiated with additional type information
at [Source: java.io.StringReader#17a5f5a5; line: 1, column: 2] (through reference chain: com.despegar.henry.automation.services.semaphoreservice.response.SemaphoreResponse["semaphore"])
at org.codehaus.jackson.map.JsonMappingException.from(JsonMappingException.java:163)
at org.codehaus.jackson.map.deser.StdDeserializationContext.instantiationException(StdDeserializationContext.java:233)
at org.codehaus.jackson.map.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:60)
at org.codehaus.jackson.map.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:299)
at org.codehaus.jackson.map.deser.SettableBeanProperty$MethodProperty.deserializeAndSet(SettableBeanProperty.java:414)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:697)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
So, i understand that this is happening because the "semaphore" attribute, which is an interface, so into that interface i know i have to add #JsonDeseralize like this
#JsonDeserialize(as = [class-name].class)
public interface ISemaphore {
public abstract String getId();
public abstract void setId(String id);
public abstract String getOwnerUserId();
}
But this is my problem. The attribute semaphore from SemaphoreResponse doesn't use always same class. ergo, i have two different classes called "MainSemaphore" and "ExecutionSemaphore", which both implements the interface ISemaphore. So, at the time for deserealization i want to pass the class that i want the interface adapts for as a parameter.
It would be something like
#JsonDeserialize(as = MainSemaphore.class) or #JsonDeserialize(as = ExecutionSemaphore.class) depending the case
How could i do that? I would appreciate your help

Besides full polymorphic handling, described by excellent article by ProgrammerBruce, there is also a simpler way to just support simple interface/impl, one-to-one, case: register mapping via module:
SimpleModule m = new SimpleModule(...);
m.addAbstractTypeMapping(ISemaphore.class, SemaphoreImpl.class);
mapper.registerModule(m);
and this would instruct Jackson to always deserialize things declare as ISemaphore using concrete class SemaphoreImpl.

Related

How to implements polymorphic serialization with out annotation and mixin

In jackson, we can uses the annotations
#JsonTypeInfo
#JsonSubTypes
#JsonSubTypes.Type
to implement polymorphic serialization.
We can choose to
Use these annotations on data model directly, this is the simplest way.
Use these annotations on mixin. Here is a link about it Polymorphic deserialization in Jackson without annotations.
Both of these two solutions have a problem: All the sub classes must be known when writing code.
In GraphQL
The discriminator field is fixed: "__typename"
The sub type names are fixed too: Simple name of java classes
All the requirements are fixed, that means it unnecessary to configure sub types one by one, it's possible to create a jackson module to handle them automatically.
// An empty interface
// Developers need not to configure polymorphic metadata for any class of its subtypes
public interface GraphQLObject {}
public class BookStore implements GraphQLObject {
public List<Book> getBooks() {...}
...other gettes/setters...
}
public abstract class Book implements GraphQLObject {
... some properties ...
}
public class ElectronicBook extends Book {
... some properties ...
}
public class PaperBook extends Book {
... some properties ...
}
The usage code looks like this
BookStore store = ...;
ObjectMapper mapper = new ObjectMapper();
mapper.addModule(new GraphQLModule());
System.out.println(mapper.writeValueAsString(store));
Here, we need to create "GraphQLModule", it can handle all the sub types implement the empty interface "GraphQLObject", and tell jackson how to use the simple class name of each subtype to be the value of discriminator field "__typename"
The result should looks like:
{
name: "store",
books: [
{ __typename: "ElectronicBook", name: "book-1" },
{ __typename: "PaperBook", name: "book-2" }
]
}
Is it possible to implement the "GraphQLModule"?
Note:
Like the default polymorphic behavior of jackson, discriminator field only need to be added when the object runtime type is different with the generic type argument of list which is known when compile.
I found the reason.
I try to defined customer serializer, but I found "serializeWithType" is never called.
In my project, data type is interface. I use ASM to generate its bytecode. I only generated the simplest bytecode and ignored the signature for generic.
So, in the inteface, it's List<Book>
But, in my bytecode implementation, it's List
It is possible to implement the "GraphQLModule" module extending the SimpleModule class:
public class GraphQLModule extends SimpleModule {
public GraphQLModule() {
this.addSerializer(new GraphQLSerializer());
}
}
I added inside the module a new serializer that extends the StdSerializer class:
public class GraphQLSerializer extends StdSerializer<GraphQLObject> {
public GraphQLSerializer() {
super(GraphQLObject.class);
}
#Override
public void serialize(GraphQLObject obj, JsonGenerator jg, SerializerProvider sp) throws IOException {
jg.writeStartObject();
jg.writeStringField("__typename", obj.getClass().getSimpleName());
jg.writeEndObject();
}
}
The GraphQLSerializer serializer simply takes your object implementing your GraphQLObject interface and serialize it including in the json just the classname string of the object as a __typename.
So you can add register this module to your objectMapper and use it like in this example :
public interface GraphQLObject {}
public abstract class Book implements GraphQLObject {}
public class ElectronicBook extends Book {}
public class PaperBook extends Book {}
ObjectMapper objectMapper = new ObjectMapper();
mapper.registerModule(new GraphQLModule());
List<Book> books = List.of(new ElectronicBook(), new PaperBook());
//it will print [{"__typename":"ElectronicBook"},{"__typename":"PaperBook"}]
System.out.println(mapper.writeValueAsString(books));

Can I use the Lombok #Builder passing the parent class as parameter?

I want to create a new Child instance passing a Parent and other additional parameters.
For example if I have:
public class Parent {
public String param1;
public String param2;
// many parameters
public String paramN;
}
public class Child extends Parent {
public String subValue;
}
With lombok, is there a builder that lets me create a Child instance passing the Parent and the missing value as parameters?
Would be easier if I could write something like:
Parent p = Parent.builder()
.param1("a")
.param2("b")
// many parameters
.paramN("b")
.build();
Child c = Child.builder(p).subValue("c").build();
Other answers don't truly make your client code simply reuse the parent instance you already have. But this is doable. You have two options:
The hard one is to write your custom annotation that does what you want. You can even make it generic so that it works for any classes the have parent/child hierarchy. Have a look at this example. If you feel brave you can raise a feature request on Lombok's github page.
Option two would be to write your custom builder for the child. See example here. In your custom builder in the init step you would be reading a passed in Parent instance, and setup the inherited fields only.
The regular #Builder is not sufficient here, because you are dealing with a class hierarchy. However, #SuperBuilder was made exactly for such a case.
#SuperBuilder generates complex code loaded with generics. That makes this solution difficult to understand without in-depth knowledge about the code #SuperBuilder generates. You should think about whether this is worth it.
Here's the solution (with Lombok >= 1.18.16):
#SuperBuilder(toBuilder = true)
public static class Parent {
public String param1;
public String param2;
// many parameters
public String paramN;
public abstract static class ParentBuilder<C extends Parent, B extends Parent.ParentBuilder<C, B>> {
protected B $fillValuesFromParent(Parent instance) {
$fillValuesFromInstanceIntoBuilder(instance, this);
return self();
}
}
}
#SuperBuilder(toBuilder = true)
public static class Child extends Parent {
public String subValue;
public static ChildBuilder<?, ?> toBuilder(Parent p) {
return new ChildBuilderImpl().$fillValuesFromParent(p);
}
}
The new toBuilder method on Child creates a new ChildBuilderImpl (which will create a Child instance when calling build()). To fill the values from the given Parent p, it calls the new $fillValuesFromParent method from ParentBuilder. This method further delegates the call to the method $fillValuesFromInstanceIntoBuilder, which is generated by Lombok and performs the actual copying of the field values to the new builder instance.
Also note the $ prefix on the methods. This basically says: I'm an implementation detail; don't use me unless you know what you are doing, I might break on the next Lombok version without further notice.
I would suggest you use #SuperBuilder
#SuperBuilder was introduced as experimental feature in lombok v1.18.2.
The #SuperBuilder annotation produces complex builder APIs for your
classes. In contrast to #Builder, #SuperBuilder also works with fields
from superclasses. However, it only works for types. Most importantly,
it requires that all superclasses also have the #SuperBuilder
annotation.
#Getter
#SuperBuilder
public class Parent {
public String name;
public String value;
}
#Getter
#SuperBuilder
public class Child extends Parent {
public String subValue;
}
Then all you need to do is
Child.builder().name("a").value("b").subValue("c").build();

How to receive a non-entity class as param on POST method?

Controller method:
#PostMapping("/nova")
#ResponseStatus(HttpStatus.CREATED)
public String adicionarTemp(#RequestBody TempCaptacao temp) {
Param Class:
public class TempCaptacao{
String dsNome;
...
List<TempResponsavel> listaResponsavel;
public TempCaptacao() {
}
SubParam Class:
public class TempResponsavel{
Long id;
String dsNome;
...
public TempResponsavel() {
}
When Angular calls this method, passing a JSON (as below) my springs returns this error:
.w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of com.spin.spincare.controller.MovAtendimentoCaptacaoController$TempCaptacao (although at least one Creator exists): can only instantiate non-static inner class by using default, no-argument constructor; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of com.spin.spincare.controller.MovAtendimentoCaptacaoController$TempCaptacao (although at least one Creator exists): can only instantiate non-static inner class by using default, no-argument constructor
at [Source: (PushbackInputStream); line: 1, column: 2]]
JSON:
{
"dsNome":"teste",
"dsGenero":"Masculino",
"dtNascimento":"2000-12-30T02:00:00.000Z",
"idConvenio":16,
"dsCep":"12321321",
"dsEndereco":"teste",
"dsEstado":"SC",
"dsCidade":"",
"dsBairro":"",
"dsComplemento":"",
"listaResponsavel":[
{
"id":1,
"dsNome":"teste",
"nrCelular":"231321312213",
"dsEmail":"",
"dsGrau":"Pai"
}
]
}
I've those empty constructor methods, but with a list inside my class, how can I pass this class as param to my POST method?
EDIT
Missing static definition on my TempCaptacao class..
Thanks!
After thinking about the error and what you have posted, I think what you are doing is this:
public class TempCaptacao {
// attributes etc
public TempCaptacao() {}
public class TempResponsavel { // inner class
// attributes etc
public class TempResponsavel() {}
}
}
Don't do that. Define the classes in your model separately, if for no other reasons than maintainability / readability / extendability.
I think JSON should be able to deserialize this if you remove the constructor from your inner class, but you're going to experience any number of issues doing something nonstandard like this.

How to deal with GSON with nested abstract classes?

i read a lot about the use of TypeAdapter and JsonSerializer/Deserializer to deal with abstract class, my problem is in case of nested abstract class.
Let say this class:
abstract class A {
String content;
}
abstract class G {
String otherContent;
}
class B extends A {
G g;
}
class C extends A {
String someThing;
}
class H extends G {
Integer num;
}
I already coded a JsonSerializer/Deserializer class for each abstract class A and G.
I know I can use the chaining on: gsonbuilder.registerTypeAdapter(A_Adapter).registerTypeAdapter(G_Adapter), but i need to use something more like the TypeAdapterFactory to identify witch adapter to use (to specify the adapter class corresponding to the abstract class i used a java annotaion/reflection).
I also seen the TypeAdapter class but it too complex to implement due to the missing of the context element present in the JsonSerializer/Deserializer.
Any idea how to do it?
To use same serializer and desrializer for every subtype of Aclass(A+B+C) you can use registerTypeHierarchyAdapter(A.class, new A_Adapter())
From documentation:
Configures Gson for custom serialization or deserialization for an
inheritance type hierarchy. This method combines the registration of a
TypeAdapter, JsonSerializer and a JsonDeserializer. If a type adapter
was previously registered for the specified type hierarchy, it is
overridden. If a type adapter is registered for a specific type in the
type hierarchy, it will be invoked instead of the one registered for
the type hierarchy.

Jackson Deserialize with Subclasses

Ok, I know there are a bunch of similar questions, but nothing seems to work.
I have the following structure set up for my entities.
public abstract class MyAbstractClass {
// bunch of properties, getters, and setters that subclasses share
public abstract String getType();
}
public class MySubclass1 extends MyAbstractClass {
// a few unique properties, getters, and setters
public String getType() {
return "Type_1"; //always the same for each instance of MySubclass1
}
}
public class MySubclass2 extends MyAbstractClass {
// a few unique properties, getters, and setters
public String getType() {
return "Type_2"; //always the same for each instance of MySubclass2
}
}
In my controller, I try to map a request to the following method.
public #RequestBody MyAbstractClass saveObject(#RequestBody MyAbstractClass mac) {
// call model to save object
}
I would like to use 1 controller method versus separate ones for the 2 entities. But using the above results in the following.
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of path.to.my.entity.MyAbstractClass, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
Makes sense.
TRY 1
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="implementingClass")
public abstract class MyAbstractClass
What I think it does - adds a metadata implementingClass property that will store the subclass class.
What the result is.
Unexpected token (END_OBJECT), expected FIELD_NAME: missing property 'implementingClass' that is to contain type id (for class path.to.my.entity.MyAbstractClass)
Tried with "class" instead of "implementingClass" for the property and got similar results.
TRY 2
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonSubTypes({
#Type(name="MySubclass1", value=MySubclass1.class),
#Type(name="MySubclass2", value=MySubclass2.class)
})
public abstract class MyAbstractClass
What I think it does - uses the defined name to do some sort of wrapping thing.
What the result is.
Could not resolve type id 'myUuid' into a subtype of [simple type, class path.to.my.entity.MyAbstractClass]
Same results even when adding #JsonTypeName("MySubclass1") and #JsonTypeName("MySubclass2") to the 2 subclasses.
Other Tries
I tried a lot. Nothing works. Won't include everything here.
I feel like there should be a simple way to do this, but I just keep on configuring things incorrectly.
I feel like the getType could maybe be leveraged, but I don't want to add an actual property for type (it's just a helper method). Also I would like to do this with annotations versus other options.
Thank you.
I figured it out but I guess I'll answer in case anyone else has this problem.
I added a type property to my subclasses instead of just a helper method (one example included below).
public class MySubclass1 extends MyAbstractClass {
#Transient
private final String type = "TYPE_1";
public String getType() {
return type;
}
}
Then I did the following for my abstract superclass.
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
#JsonSubTypes({
#Type(name="TYPE_1", value=MySubclass1.class),
#Type(name="TYPE_2", value=MySubclass2.class)
})
public abstract class MyAbstractClass
When providing the JSON, I was sure to include the type. I won't include this because it's weird knockout insanity.
It's not great. But it worked.

Categories

Resources