Consider an interface with several implementations
Public interface A {}
#DynamoDBDocument
Public class C1 implements A {
variables…
}
#DynamoDBDocument
Public class C2 implements A {
variables…
}
#DynamoDBTable
Public class TopLevelClass {
A obj; //It can be any implementation C1, C2 ….
//Getter and setter of A
}
Saving an object of type C1 in the dynamoDB works fine, but we are unable to deserialize it back. Jackson does it by adding a #class attribute to the json to help it figure out which implementation to instantiate, however DynamoDBMapper has no idea and ends up trying to instantiate an object of A which obviously fails as it’s just an interface.
Is there a way we can achieve this? We don’t want to use marshallers as it will just deserialize the whole object as string and defeats the purpose of DynamoDBDocument type.
Related
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));
I have a base class like (this is an artificial example):
#Document(collection = "cars")
public class BaseCar implements Serializable
{
private int number;
private String color;
...
}
Then I have a derived class like:
#Document(collection = "cars")
public class FastCar extends BaseCar implements Serializable
{
private int numberOfWonRaces;
...
}
For both I have a MongoRepository class:
public interface BaseCarRepository extends MongoRepository<BaseCar, String> {
{ ... }
and
public interface FastCarRepository extends MongoRepository<FastCar, String> {
{ ... }
If I now save a FastCar in MongoDB the I get additionally a _class field added which indicates from where the data is coming from. In this example it shows FastCar.
In my project I have a REST API interface to get cars. I use the findBy function to get a car by its color. For example:
BaseCar baseCar = baseCarRep.findByColor(color);
Even if I use an object of BaseCar, Springboot is detecting that it is a FastCar and is returning an FastCar object with all the information.
Question:
Is there a way to force Springboot to return only a BaseCar? I do not want to send all the information to the REST API interface.
What I have done so far:
If I remove the the _class field in the MongoDB, Springboot cannot automatically detect the class anymore and is returning the BaseCar. But I do not want to lose this functionality by forcing Springboot to remove the _class (Spring data MongoDb: MappingMongoConverter remove _class)
It seems that there is also a way with projections to filter the fields which should be returned. This is to me not an elegant way as I have to write down all the fields again and I have to update it as soon as I am updating the BaseCar class.
Thank you for any help.
Philipp
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.
I've got around 5 objects that I want to do similar things with.
I figured out that not to polute the code I will put a logic for those objects in one place.
public class MetaObjectController<T extends MetaObject> {
#Autowired
private final MetaObjectRepository<T> repository;
// generic logic
Here's how repository looks:
public interface MetaObjectRepository<T extends MetaObject> extends GraphRepository<T> {
T findByName(String name);
}
Now, I create concrete class which uses delegation:
public class ExperimentalController {
#Autowired
private final MetaObjectController<MetaCategory> metaController;
#RequestMapping(method = RequestMethod.POST)
public void add(#RequestBody MetaCategory toAdd) {
metaController.add(toAdd);
}
Now, when I look at the generated queries I see, that although instantiated correctly, repository puts MetaObject as an entity name instead of runtime type.
Is there a way to force the repository to use runtime type?
Please don't advise to put a #Query annnotation. That's not what I am looking for.
This is most probably due to type erasure: at runtime there is only the type constraint available which is MetaObject. If you want to use (via spring-data) the actually relevant subclass you will have to create explicit interfaces of the MetaObjectRepository like this:
public class Transmogrifier extends MetaObject
public interface MetaTransmogrifierRepository
extends MetaObjectRepository<Transmogrifier> {}
Hi I am persisting a class with a collection(List) of interface.
I see this on link
http://www.datanucleus.org/products/accessplatform_2_1/jdo/orm/embedded.html#Collection
and it says "Embedded elements cannot have inheritance (this may be allowed in the future)"
So, how to persist such objects?
I came accross the same issue a few hours ago, hope it helps others starting with jdo/datanucleus.
As stated in the current docs, the only way to persist a collection of interfaces is through an unidirectional join table. It's not possible to directly embed the objects implementing the interface.
#PersistenceCapable
public class SomeClass {
#Join
#Extension(vendorName="datanucleus", key="implementation-classes", value="ImplementingClass")
private List<SomeInterface> myList;
// this list would be embedded
private List<SomeOtherClass> myOtherList;
// ...
}
#PersistenceCapable
public interface SomeInterface {
// ...
}
#PersistenceCapable
public class ImplementingClass implements SomeInterface {
// ...
}
#PersistenceCapable(embeddedOnly="true")
public class SomeOtherClass {
// ...
}