Spring Data Mongodb : Deserialize using builder - java

I'm wondring if there's a way to configure spring data to use a builder to deserialise a document. Something like JsonPOJOBuilder or JsonDeserialize(builder = ADocumentBuilder) for jackson ?
In fact I'm using lombok with #SuperBuilder annotation like :
#Data
#RequiredArgConstrcutor
#SuperBuilder
public abstract class AttachementBase {
private String a;
private final String b; //maybe final
private String c;
}
#Value
#SuperBuilder
#Document
public class Attachement extend AttachementBase{
private String d;
}
Evething's works fine with the builder and how to use it, except when calling the AttachementRepository.findXX() Spring trys to call a constructor but it cannot find the appropriate one.
Lombok generates AttachementBuilder in the Attachement class and I want to use it to deserialise this document.
I do not want this post be related to lombok, i juste illustrated the use case. The question is how to use a builder to deserialize a document in Spring-data-mongodb ?

Related

How to serialize ArrayList<ObjectId> using Jackson

Is there a way to tell Jackson to serialize an ArrayList<ObjectId> in an object?
Currently what I did is create a new Class containing the id and uses it instead like ArrayList<SomeObject>
class SomeObject {
#JsonSerialize(using = ToStringSerializer.class)
private final ObjectId id;
}
It works fine but I want to know if there's a way for Jackson to serialize an ArrayList<ObjectId> without created a new class?

How to prevent ObjectMapper de-serializing plain string to Object successfully?

I have a simple POJO:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class StatusPojo {
private String status;
}
When I de-serialize simple string "asd" (without quotes) like this:
StatusPojo pojo = new ObjectMapper().readValue("asd", StatusPojo.class)
I am getting a StatusPojo object created successfully, with status field's value as "asd", though it is not valid JSON and nowhere has the field name "status" mentioned along.
Why is it behaving like this and how to disable this behavior and have object mapper throw an exception?
Your POJO has #AllArgsConstructor (maybe because of the #Builder) that then generates something like this:
public StatusPojo(String status) {
this.status = status;
}
When ObjectMapper then de-serializes plain string it uses that constructor to create object.
If you added some other property to your pojo, like just:
private String noAsdPlease;
there would be an exception, because ObjectMapper could not find creator, there would not be that above mentioned constructor but with two String params.
At quick glace DeserializationFeature does not have such a feature that disables using one string arg constructor for plain string.
Playing with more fields, removing #Builder & #AllArgsConstructor might resolve your problem but if you cannot change those ther might not be other options.

Spring Boot and Jackson: Define which fields to serialize on external class

I'm working on a rest api project with spring boot and hibernate, and I'm wondering on json serialization of RestController using Jackson.
Here is the problem: I use external hibernate entities class defined in a library I cannot edit. This classes are very complex and define lot of field I'm not interested in when I return the object with the rest api.
Actually, I've solved the problem wrapping the original class with a wrapper class that exposes only the values I want to return from the controller.
Eg:
original class
class AccountEntity {
///...
public String getName() {
return this.name;
}
/// ... Lot of code here
}
Wrapper class:
class AccountWrapper {
AccountEntity original;
public AccountWrapper(AccountEntity original) {
this.original = original;
}
public String getName() {
return this.original.getName();
}
}
and the use the Wrapper as following
#RestController("/api/user")
public class UsersController {
#GetMapping("/")
public AccountWrapper getUser() {
AccountEntity account = //get account in some way
AccountWrapper accountWrapper = new AccountWrapper(account);
return accountWrapper;
}
}
The method works well, but it's not very clean and makes stuff more complex (e.g., when I have to return lists), because I always have to wrap the original class.
I didn't found a method to make me able to specify which fields I want to serialize without modify (and I cannot) the original class.
Any help?
Instead of using a wrapper class, create a DTO object for the rest API that will be leaner than the DB entity and a trasformer to create DTO from entity (and vice a verce)
The difference from using a wrapper here is that the DB entity is not part of the DTO, and thus does not need to be serialized on the response.
The big advantage here is that you separate the DB layer from the API layer, which makes it more flexible and easy to manage.
you can read more about this pattern here
Apparently, you can use Jackson Mixins to annotate a class with Jackson annotations.
See this answer for example.
The idea is to create an class with the annotations you want and to use objectMapper.getSerializationConfig().addMixInAnnotations() to register the MixIn with your class.
For example:
//Class you don't controll
public class User {
private String name;
private String password; //attribute we want to omit
//... getters and setters
}
public abstract class UserMixIn {
#JsonIgnore String getPassword();
}
objectMapper.addMixInAnnotations(User.class, UserMixIn.class);
Hope it helps,

How to use Jackson to deserialize external Lombok builder class

I have a 3rd party Lombok builder POJO, one that I cannot modify, that I want to serialize using jackson. Notably it does not have a NoArgsConstructor.
#Data
#Builder
public class ExternalClass {
private String name;
private String data;
// etc.
}
On the surface this would appear to be simple, but it is incredibly frustrating in practice as each possible option seems to be counteracted by a different complication. In essence, I'm having trouble getting an external Lombok builder to work with a jackson mixin.
Lombok produces fluent setters of the style .name(String name) while Jackson's built-in builder deserializer expects .withName(String name). Lombok documentation, and recipes elsewhere such as here suggest using #JsonDeserialize(builder=ExternalClass.ExternalClassBuilder.class) in conjunction with #JsonPOJOBuilder(withPrefix="") on a predeclared inner stub builder. But this is not possible because the Lombok class is in an external library.
Applying these annotations to a mixin has no effect.
#JsonDeserialize(ExternalClass.ExternalClassBuilder.class)
public abstract class ExternalClassMixin {
#JsonPOJOBuilder(withPrefix="")
public static ExternalClassBuilder {
}
}
The only approach I've found that works is to leverage the package-access AllArgsConstructor created by #Builder and populate the mixin with the following constructor
public abstract class ExternalClassMixin {
#JsonCreator public ExternalClassMixin(
#JsonProperty("name") String name,
#JsonProperty("data") String data,
// etc.
) {}
}
This is obviously not desirable as it requires iterating and hard-coding every class property explicitly, making the mixin fragile to any change in the external POJO.
My question is - is there a robust, maintainable way to serialize this external builder class using Jackson without modifying it, using either a mixin or maybe a full blown deserializer?
Update
I implemented the excellent answer by #jan-rieke, including the suggestion to use reflection to seek out the inner builder class.
...
public Class<?> findPOJOBuilder(AnnotatedClass ac) {
Class<?> innerBuilder;
try {
innerBuilder = Class.forName(ac.getName()+"$"+ac.getRawType().getSimpleName()+"Builder");
log.info("Builder found: {}", ac.getName());
return innerBuilder;
} catch( ClassNotFoundException e ) {
return super.findPOJOBuilder(ac);
}
}
You can customize your ObjectMapper as follows:
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
#Override
public Class<?> findPOJOBuilder(AnnotatedClass ac) {
if (ExternalClass.class.equals(ac.getRawType())) {
return ExternalClass.ExternalClassBuilder.class;
}
return super.findPOJOBuilder(ac);
}
#Override
public Value findPOJOBuilderConfig(AnnotatedClass ac) {
if (ac.hasAnnotation(JsonPOJOBuilder.class)) {
return super.findPOJOBuilderConfig(ac);
}
return new JsonPOJOBuilder.Value("build", "");
}
});
This will
explicitly configure that deserialization for ExternalClass uses its builder, and
set the default prefix for builder setter methods to "" (except when the #JsonPOJOBuilder annotation is present).
If you do not want to list all external classes explicitly in findPOJOBuilder(), you can of course programmatically look into the class to check whether it has a inner class that looks like a builder.
This can be accomplished by creating two mixins: one for ExternalClass (specifying the builder to use) and one for ExternalClass.ExternalClassBuilder (specifying the lack of a prefix in the builder methods).
#JsonDeserialize(builder = ExternalClass.ExternalClassBuilder.class)
public interface ExternalClassMixin {
}
#JsonPOJOBuilder(withPrefix="")
public interface ExternalClassBuilderMixin {
}
This serializes and deserializes the JSON in the desired manner:
String json = "{\"name\": \"The Name\", \"data\": \"The Data\"}";
ObjectMapper mapper = new ObjectMapper()
.addMixIn(ExternalClass.class, ExternalClassMixin.class)
.addMixIn(ExternalClass.ExternalClassBuilder.class, ExternalClassBuilderMixin.class);
System.out.println(mapper.readValue(json, ExternalClass.class));
System.out.println(mapper.writeValueAsString(mapper.readValue(json, ExternalClass.class)));
Output:
ExternalClass(name=The Name, data=The Data)
{"name":"The Name","data":"The Data"}

Using Couchbase SDK in Java

I am trying to map the result of a couchbase query to a java reference type, so far I have found no way to do this.
How can I capture the following as a java reference type:
N1qlQueryResult result = couchbaseBucket.query(
N1qlQuery.simple("SELECT * FROM customers LIMIT 1"));
JsonObject cust = result.allRows().get(0).value();
How can I cast this 'cust' to a java object? What would be the best way of doing this, doesnt the couchbase SDK provide some solution to this?
There was a blog post published yesterday that shows you how to do this with couchbase spring-boot and spring data.
I'm not a Java expert at all, but it looks like you start by creating an entity class like this:
#Document
#Data
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
public class Building {
#NotNull
#Id
private String id;
#NotNull
#Field
private String name;
#NotNull
#Field
private String companyId;
// ... etc ...
}
Then, create a repository class.
#N1qlPrimaryIndexed
#ViewIndexed(designDoc = "building")
public interface BuildingRepository extends CouchbasePagingAndSortingRepository<Building, String> {
List<Building> findByCompanyId(String companyId);
// ... etc ...
}
Finally, you can use #Autowired in a service class or wherever to instantiate a BuildingRepository and start calling the methods on it. The full documentation for Spring Data Couchbase is available on docs.spring.io

Categories

Resources