Adding content property to serialization - spring

Whenever I use a custom serializer in spring data rest, it adds a "content" property that wrapps the object returned, like:
{
"content":{
object properties...
},
_links: {
}
}
EDIT: Add configuration class
#Configuration
public class JacksonCustomizations {
#Bean
public Module rateModule() {
return new RateModule();
}
#SuppressWarnings("serial")
static class RateModule extends SimpleModule {
public RateModule() {
setMixInAnnotation(Package.class, RateModule.PackageMixin.class);
setMixInAnnotation(Section.class, RateModule.SectionMixin.class);
setMixInAnnotation(MainPart.class, RateModule.MainPartMixin.class);
setMixInAnnotation(SubPart.class, RateModule.SubPartMixin.class);
addSerializer(MaintenanceTask.class, new MaintenanceTaskSerializer());
addDeserializer(Package.class, new PackageDeserializer());
addDeserializer(Section.class, new SectionDeserializer());
addDeserializer(MainPart.class, new MainPartDeserializer());
addDeserializer(MaintenanceTask.class, new MaintenanceTaskDeserializer());
}
#JsonAutoDetect(fieldVisibility=Visibility.NONE, getterVisibility=Visibility.NONE, isGetterVisibility=Visibility.NONE)
static abstract class PackageMixin {
#JsonProperty("name") public abstract String getName();
#JsonProperty("sections") public abstract List<Section> getSections();
}
#JsonAutoDetect(fieldVisibility=Visibility.NONE, getterVisibility=Visibility.NONE, isGetterVisibility=Visibility.NONE)
static abstract class SectionMixin {
#JsonProperty("id") public abstract Long getId();
#JsonProperty("name") public abstract String getName();
}
#JsonAutoDetect(fieldVisibility=Visibility.NONE, getterVisibility=Visibility.NONE, isGetterVisibility=Visibility.NONE)
static abstract class MainPartMixin {
#JsonProperty("name") public abstract String getName();
#JsonProperty("subparts") public abstract List<SubPart> getSubParts();
}
#JsonAutoDetect(fieldVisibility=Visibility.NONE, getterVisibility=Visibility.NONE, isGetterVisibility=Visibility.NONE)
static abstract class SubPartMixin {
#JsonProperty("id") public abstract Long getId();
#JsonProperty("name") public abstract String getName();
}
static class MaintenanceTaskSerializer extends JsonSerializer<MaintenanceTask> {
#Override
public void serialize(final MaintenanceTask value, final JsonGenerator gen, final SerializerProvider serializers)
throws IOException, JsonProcessingException {
gen.writeStartObject();
gen.writeNumberField("id", value.getId());
gen.writeStringField("maintenanceRequirementId", value.getMaintenanceRequirementId());
gen.writeStringField("type", value.getType().toString());
gen.writeStringField("title", value.getTitle());
gen.writeStringField("description", value.getDescription());
gen.writeStringField("note", value.getNote());
gen.writeStringField("effectivity", value.getEffectivity());
gen.writeNumberField("procedureReference", value.getReferenceTask().getId());
gen.writeNumberField("aircraftModel", value.getAircraftModel().getId());
gen.writeNumberField("packageId", value.getPack().getId());
gen.writeNumberField("sectionId", value.getSection().getId());
gen.writeStringField("taskType", value.getTaskType().toString());
gen.writeEndObject();
}
}
}
}
But when I use spring data rest serialization without custom serializers, the property is not inserted.
How can I prevent this property from showing?

This is an known issue.
It has been reported here: https://jira.spring.io/browse/DATAREST-504
The issue points to this stack overflow question: Different JSON output when using custom json serializer in Spring Data Rest

Related

Can't mapping the abstract class using model mapper

I have an issue while mapping the DTO to Entity the CVV and expiration date not working but the number of the card is working fine, and mapping the did I miss any configuration ??
I have class A inherit from PaymentRequestDTO and class B inherit PaymentRequest
and mapping using this.modelmapper().map(classA, classB);
Parent Class for A
public abstract class PaymentRequestDTO {
....
CardRequestDTO card;
...
}
CardDTO
public class CardRequestDTO {
#JsonProperty("name")
public String name;
#JsonProperty("number")
public String number;
#JsonProperty("expiryMonth")
public String expiryMonth;
#JsonProperty("expiryYear")
public String expiryYear;
#JsonProperty("cvv")
public String cvv;
}
Parent Class for B
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
public abstract class PaymentRequest{
#JsonProperty("card_number")
public String cardNumber;
#JsonProperty("expiry_date")
public String expiryDate;
#JsonProperty("card_security_code")
public String cardSecurityCode;
}
PropertyMap class
public class PaymentRequestConverter extends PropertyMap<PaymentRequestDTO, PaymentRequest> {
#Override
protected void configure() {
map().setCardNumber(source.getCard().getNumber());
map().setCardSecurityCode(source.getCard().getCvv());
map().setExpiryDate(FortHelper.expiryDate(source.getCard().getExpiryYear(), source.getCard().getExpiryMonth()));
}
}
Here is my model mapper
#Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.createTypeMap(PaymentRequestDTO.class, PaymentRequest.class).addMappings(new PaymentRequestConverter());
return modelMapper;
}
Model mapper version 3.1.0

JSON field Desrializing to lowercase in Spring Boot

I have a Spring Boot Controller -
#RestController
public class UserController {
#PostMapping
#ResponseStatus(CREATED)
public UserResponse register( #Valid #RequestBody UserRequest userRequest) {
//return ....
}
}
Below is UserRequest.java
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class UserRequest {
private String email;
//other property
}
I am sending below json in request body -
{
"email" : "TEST#Example.com",
//some other fields.
}
Sometime client send email in uppercase or in camel case so in userRquest I want to change value of email field to lowercase like test#example.com while de serializing to UserRequest Object.
Is there any easy way to do this. Can I introduce my own annotation like #ToLowerCase how I can create my own annotation and use that at field level in UserRequest.
There is no easy way just by introducing a new annotation #ToLowerCase,
because then you would also need to implement some annotation processor
for doing the real conversion work.
But you can achieve your goal in a slightly different way.
In your UserRequest class annotate the email property
with #JsonDeserialize and specify a converter there.
#JsonDeserialize(converter = ToLowerCaseConverter.class)
private String email;
You need to implement the converter class by yourself,
but it is easy by extending it from StdConverter.
public class ToLowerCaseConverter extends StdConverter<String, String> {
#Override
public String convert(String value) {
return value.toLowerCase();
}
}
Jackson will use the setter methods in your class.
Perform the conversion to lower case in the setter.
For example
public void setEmail(String newValue)
{
email = StringUtils.lowerCase(newValue);
}
StringUtils is an apache commons class.
You can make a general StringDeserializer and register it in ObjectMapper as shown below:-
StringDeserializer class
public final class StringDeserializer extends StdDeserializer<String> {
public StringDeserializer() {
super((Class<String>) null);
}
#Override
public String deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonToken token = parser.getCurrentToken();
if (token == JsonToken.VALUE_STRING) {
String text = parser.getText();
return text == null ? null : text.toLowerCase().trim();
}
return null;
}
}
JacksonConfiguration class
#Configuration
public class JacksonConfiguration {
#Autowired
void mapper(ObjectMapper mapper) {
mapper.registerModule(initModule());
}
private Module initModule() {
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new StringDeserializer());
return module;
}
}
The above code makes jackson deserialize all strings to lowercase and trimmed.

How to properly implement a Spring Converter?

I have a Money class with factory methods for numeric and String values. I would like to use it as a property of my input Pojos.
I created some Converters for it, this is the String one:
#Component
public class StringMoneyConverter implements Converter<String, Money> {
#Override
public Money convert(String source) {
return Money.from(source);
}
}
My testing Pojo is very simple:
public class MoneyTestPojo {
private Money value;
//getter and setter ommited
}
I have an endpoint which expects a Pojo:
#PostMapping("/pojo")
public String savePojo(#RequestBody MoneyTestPojo pojo) {
//...
}
Finally, this is the request body:
{
value: "100"
}
I have the following error when I try this request:
JSON parse error: Cannot construct instance of
br.marcellorvalle.Money (although at least one Creator
exists): no String-argument constructor/factory method to deserialize
from String value ('100'); nested exception is
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
construct instance of br.marcellorvalle.Money (although at
least one Creator exists): no String-argument constructor/factory
method to deserialize from String value ('100')\n at [Source:
(PushbackInputStream); line: 8, column: 19] (through reference chain:
br.marcellorvalle.MoneytestPojo[\"value\"])",
If I change Money and add a constructor which receives a String this request works but I really need a factory method as I have to deliver special instances of Money on specific cases (zeros, nulls and empty strings).
Am I missing something?
Edit: As asked, here goes the Money class:
public class Money {
public static final Money ZERO = new Money(BigDecimal.ZERO);
private static final int PRECISION = 2;
private static final int EXTENDED_PRECISION = 16;
private static final RoundingMode ROUNDING = RoundingMode.HALF_EVEN;
private final BigDecimal amount;
private Money(BigDecimal amount) {
this.amount = amount;
}
public static Money from(float value) {
return Money.from(BigDecimal.valueOf(value));
}
public static Money from(double value) {
return Money.from(BigDecimal.valueOf(value));
}
public static Money from(String value) {
if (Objects.isNull(value) || "".equals(value)) {
return null;
}
return Money.from(new BigDecimal(value));
}
public static Money from(BigDecimal value) {
if (Objects.requireNonNull(value).equals(BigDecimal.ZERO)) {
return Money.ZERO;
}
return new Money(value);
}
//(...)
}
Annotating your factory method with #JsonCreator (from the com.fasterxml.jackson.annotation package) will resolve the issue:
#JsonCreator
public static Money from(String value) {
if (Objects.isNull(value) || "".equals(value)) {
return null;
}
return Money.from(new BigDecimal(value));
}
I just tested it, and it worked for me. Rest of your code looks fine except for the sample request (value should be in quotes), but I guess that's just a typo.
Update 1:
If you're unable to make changes to the Money class, I can think of another option - a custom Jackson deserializer:
public class MoneyDeserializer extends StdDeserializer<Money> {
private static final long serialVersionUID = 0L;
public MoneyDeserializer() {
this(null);
}
public MoneyDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Money deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
String value = node.textValue();
return Money.from(value);
}
}
Just register it with your ObjectMapper.
It seems that using the org.springframework.core.convert.converter.Converter only works if the Money class is a "#PathVariable" in the controller.
I finally solved it using the com.fasterxml.jackson.databind.util.StdConverter class:
I created the following Converter classes:
public class MoneyJsonConverters {
public static class FromString extends StdConverter<String, Money> {
#Override
public Money convert(String value) {
return Money.from(value);
}
}
public static class ToString extends StdConverter<Money, String> {
#Override
public String convert(Money value) {
return value.toString();
}
}
}
Then I annotated the Pojo with #JsonDeserialize #JsonSerialize accordingly:
public class MoneyTestPojo {
#JsonSerialize(converter = MoneyJsonConverters.ToString.class)
#JsonDeserialize(converter = MoneyJsonConverters.FromString.class)
private Money value;
//getter and setter ommited
}

Spring Data REST Custom Resource URI works for String but not Long

I have a model:
public class MyModel {
#Id private Long id;
private Long externalId;
// Getters, setters
}
I'd like to use externalId as my resource identifier:
#Configuration
static class RepositoryEntityLookupConfig extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration configuration) {
configuration
.withEntityLookup()
.forRepository(MyRepository.class, MyModel::getExternalId, MyRepository::findByExternalId);
}
}
If externalId is a String, this works fine. But since it's a number (Long)
public interface MyRepository extends JpaRepository<MyModel, Long> {
Optional<MyModel> findByExternalId(#Param("externalId") Long externalId);
}
when invoking: /myModels/1 I get:
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Long
at org.springframework.data.rest.core.config.EntityLookupConfiguration$RepositoriesEntityLookup.lookupEntity(EntityLookupConfiguration.java:213) ~[spring-data-rest-core-2.6.4.RELEASE.jar:na]
at org.springframework.data.rest.core.support.UnwrappingRepositoryInvokerFactory$UnwrappingRepositoryInvoker.invokeFindOne(UnwrappingRepositoryInvokerFactory.java:130) ~[spring-data-rest-core-2.6.4.RELEASE.jar:na]
at org.springframework.data.rest.webmvc.RepositoryEntityController.getItemResource(RepositoryEntityController.java:524) ~[spring-data-rest-webmvc-2.6.4.RELEASE.jar:na]
at org.springframework.data.rest.webmvc.RepositoryEntityController.getItemResource(RepositoryEntityController.java:335) ~[spring-data-rest-webmvc-2.6.4.RELEASE.jar:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_111]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_111]
...
A separate custom EntityLookupSupport<MyModel> component class works.
Am I missing something to get it working for Long using method references in my RepositoryRestConfigurerAdapter?
Try to add this to your RepositoryEntityLookupConfig class:
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
conversionService.addConverter(String.class, Long.class, Long::parseLong);
super.configureConversionService(conversionService);
}
Do you really need to set configuration by yourself ? You could try to use spring-boot auto-configuration by adding #RepositoryRestResource annotation
#RepositoryRestResource(collectionResourceRel = "myModels", path = "myModels")
public interface MyRepository extends JpaRepository<MyModel, Long> {
Optional<MyModel> findByExternalId(#Param("externalId") Long externalId);
}
Also add #Entity on your model class
#Entity
public class MyModel {
#Id
private Long id;
#Column(name = "EXTERNAL_ID")
// Column annotation is not required if you respect case-sensitive
private Long externalId;
// Getters, setters
}
Apparently, the default BackendIdConverter (see DefaultIdConverter) does nothing with ID conversion and on the other hand Spring Data Rest cannot use the repository ID type. So, you have to either convert it yourself or configure your custom ID converter bean, for example:
#Bean
public BackendIdConverter myModelBackendIdConverter() {
return new BackendIdConverter() {
#Override
public Serializable fromRequestId(final String id, final Class<?> entityType) {
return Optional.ofNullable(id).map(Long::parseLong).orElse(null);
}
#Override
public boolean supports(final Class<?> delimiter) {
return MyModel.class.isAssignableFrom(delimiter);
}
#Override
public String toRequestId(final Serializable id, final Class<?> entityType) {
return Optional.ofNullable(id).map(Object::toString).orElse(null);
}
};
}
See also:
BackendIdHandlerMethodArgumentResolver
#BackendId
The signature of the method you are trying to call seems to be:
forRepository(Class<R> type, Converter<T,ID> identifierMapping,
EntityLookupRegistrar.LookupRegistrar.Lookup<R,ID> lookup)
I don't see how MyModel::getExternalId can be doing the necessary conversion.
I would try something like the following:
#Configuration
static class RepositoryEntityLookupConfig extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration configuration) {
configuration
.withEntityLookup()
.forRepository(MyRepository.class, Long::parseLong, MyRepository::findByExternalId);
}
}

Spring Data Rest Repository with abstract class / inheritance

I can't get Spring Data Rest with class inheritance working.
I'd like to have a single JSON Endpoint which handles all my concrete classes.
Repo:
public interface AbstractFooRepo extends KeyValueRepository<AbstractFoo, String> {}
Abstract class:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = MyFoo.class, name = "MY_FOO")
})
public abstract class AbstractFoo {
#Id public String id;
public String type;
}
Concrete class:
public class MyFoo extends AbstractFoo { }
Now when calling POST /abstractFoos with {"type":"MY_FOO"}, it tells me: java.lang.IllegalArgumentException: PersistentEntity must not be null!.
This seems to happen, because Spring doesn't know about MyFoo.
Is there some way to tell Spring Data REST about MyFoo without creating a Repository and a REST Endpoint for it?
(I'm using Spring Boot 1.5.1 and Spring Data REST 2.6.0)
EDIT:
Application.java:
#SpringBootApplication
#EnableMapRepositories
public class Application {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
I'm using Spring Boot 1.5.1 and Spring Data Release Ingalls.
KeyValueRepository doesn't work with inheritance. It uses the class name of every saved object to find the corresponding key-value-store. E.g. save(new Foo()) will place the saved object within the Foo collection. And abstractFoosRepo.findAll() will look within the AbstractFoo collection and won't find any Foo object.
Here's the working code using MongoRepository:
Application.java
Default Spring Boot Application Starter.
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
AbstractFoo.java
I've tested include = JsonTypeInfo.As.EXISTING_PROPERTY and include = JsonTypeInfo.As.PROPERTY. Both seem to work fine!
It's even possible to register the Jackson SubTypes with a custom JacksonModule.
IMPORTANT: #RestResource(path="abstractFoos") is highly recommended. Else the _links.self links will point to /foos and /bars instead of /abstractFoos.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Foo.class, name = "MY_FOO"),
#JsonSubTypes.Type(value = Bar.class, name = "MY_Bar")
})
#Document(collection="foo_collection")
#RestResource(path="abstractFoos")
public abstract class AbstractFoo {
#Id public String id;
public abstract String getType();
}
AbstractFooRepo.java
Nothing special here
public interface AbstractFooRepo extends MongoRepository<AbstractFoo, String> { }
Foo.java & Bar.java
#Persistent
public class Foo extends AbstractFoo {
#Override
public String getType() {
return "MY_FOO";
}
}
#Persistent
public class Bar extends AbstractFoo {
#Override
public String getType() {
return "MY_BAR";
}
}
FooRelProvider.java
Without this part, the output of the objects would be separated in two arrays under _embedded.foos and _embedded.bars.
The supports method ensures that for all classes which extend AbstractFoo, the objects will be placed within _embedded.abstractFoos.
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class FooRelProvider extends EvoInflectorRelProvider {
#Override
public String getCollectionResourceRelFor(final Class<?> type) {
return super.getCollectionResourceRelFor(AbstractFoo.class);
}
#Override
public String getItemResourceRelFor(final Class<?> type) {
return super.getItemResourceRelFor(AbstractFoo.class);
}
#Override
public boolean supports(final Class<?> delimiter) {
return AbstractFoo.class.isAssignableFrom(delimiter);
}
}
EDIT
Added #Persistent to Foo.java and Bar.java. (Adding it to AbstractFoo.java doesn't work). Without this annotation I got NullPointerExceptions when trying to use JSR 303 Validation Annotations within inherited classes.
Example code to reproduce the error:
public class A {
#Id public String id;
#Valid public B b;
// #JsonTypeInfo + #JsonSubTypes
public static abstract class B {
#NotNull public String s;
}
// #Persistent <- Needed!
public static class B1 extends B { }
}
Please see the discussion in this resolved jira task for details of what is currently supported in spring-data-rest regarding JsonTypeInfo. And this jira task on what is still missing.
To summarize - only #JsonTypeInfo with include=JsonTypeInfo.As.EXISTING_PROPERTY is working for serialization and deserialization currently.
Also, you need spring-data-rest 2.5.3 (Hopper SR3) or later to get this limited support.
Please see my sample application - https://github.com/mduesterhoeft/spring-data-rest-entity-inheritance/tree/fixed-hopper-sr3-snapshot
With include=JsonTypeInfo.As.EXISTING_PROPERTY the type information is extracted from a regular property. An example helps getting the point of this way of adding type information:
The abstract class:
#Entity #Inheritance(strategy= SINGLE_TABLE)
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME,
include=JsonTypeInfo.As.EXISTING_PROPERTY,
property="type")
#JsonSubTypes({
#Type(name="DECIMAL", value=DecimalValue.class),
#Type(name="STRING", value=StringValue.class)})
public abstract class Value {
#Id #GeneratedValue(strategy = IDENTITY)
#Getter
private Long id;
public abstract String getType();
}
And the subclass:
#Entity #DiscriminatorValue("D")
#Getter #Setter
public class DecimalValue extends Value {
#Column(name = "DECIMAL_VALUE")
private BigDecimal value;
public String getType() {
return "DECIMAL";
}
}

Resources