Spring #Requestbody not mapping to inner class - spring

I am doing Spring Rest Api project with Spring 4.x
This Works:
Controller.java
#PostMapping("newTransaction")
TransactionRequestModel insertNewTransaction(#RequestBody TransactionRequestModel model){
//do something
}
TransactionRequestModel.java
public class TransactionRequestModel {
private int id;
private List<KeyValue> keyValueList;
public TransactionRequestModel(){}
//default constructor
//getter-setter
}
KeyValue.java
public class KeyValue {
String key;
String value;
//default constructor
//setter-getter
}
Request Body Json
{
"id": 1
"keyValueList": [
{
"key": "dummy",
"value": "dummy"
}
]
}
Spring message converter using jackson is working fine.
This Won't:
When i change TransactionRequestModel.java to following (and delete KeyValue.java)
public class TransactionRequestModel {
public class KeyValue {
String key;
String value;
//default constructor
//setter-getter
}
private int id;
private List<KeyValue> keyValueList;
public TransactionRequestModel(){}
//default constructor
//getter-setter
}
means, making KeyValue an inner class, got following error.
org.springframework.http.converter.HttpMessageNotReadableException:
Could not read document: No suitable constructor found for type
[simple type, class
com.example.model.TransactionRequestModel$KeyValue]: can not
instantiate from JSON object (missing default constructor or creator,
or perhaps need to add/enable type information?)
Why?
All the related post in SO mentions the first scenario. I would like to know why this wont work. Please help.

You have to make your inner class static.
public class TransactionRequestModel {
public static class KeyValue {
String key;
String value;
//default constructor
//setter-getter
}
private int id;
private List<KeyValue> keyValueList;
public TransactionRequestModel(){}
//default constructor
//getter-setter
}

Related

Spring data elasticsearch embedded field mapping

I'm struggling with mappings some field. I looked for an answer but couldn't find anything solving my case. Let's cut to the chase.
I have my document class
#Doucment
public class DocumentClass {
#Field(type = FieldType.Nested)
private EmployeeId employeeId;
}
An EmployeeId is wrapper for my uuid identifier. This object has nothing but just getters and setters and jackson annotations. The thing is that object extends some base class so such objects like EmployeeId can inherit this object. This super class has field id and this causes the problem. When I post some data to elasticsearch then it looks like this:
{
"employeeId": {
"id": "someUUID"
}
}
But I want to map this to be like:
{
"employeeId": "someUUID"
}
I wonder if there is a way to flatten this object.
If I get it right, you want to convert your EmployeeId class to a String and back. You have 2 possibilities to do that:
Using a property converter
If you only want to convert an EmployeeId in this entity and might keep it as it is in another, you should use a property converter that is only registered for this property:
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
public class EmployeeIdConverter implements PropertyValueConverter {
#Override
public Object write(Object value) {
return value instanceof EmployeeId employeeId ? employeeId.getId() : value.toString();
}
#Override
public Object read(Object value) {
return new EmployeeId(value.toString());
}
}
This converter must be registered on the property, notice that the field type is set to Keyword as it probably should not be analysed:
import org.springframework.data.elasticsearch.annotations.ValueConverter;
#Document
public class DocumentClass {
#Field(type = FieldType.Keyword)
#ValueConverter(EmployeeIdConverter.class)
private EmployeeId employeeId;
}
Using a global converter
If you are using this EmployeeId at several places you might want register globally 2 converters for the two conversion directions:
#WritingConverter
public class EmployeeIdToString implements Converter<EmployeeId, String>{
#Nullable
#Override
public String convert(EmployeeId employeeId) {
return employeeId.getId();
}
}
#ReadingConverter
public class StringToEmployeeId implements Converter<String, EmployeeId>{
#Nullable
#Override
public EmployeeId convert(String id) {
return new EmployeeId(id);
}
}
To register these, you need to provide a custom client configuration (see the documentation):
#Configuration
public class MyClientConfig extends ElasticsearchConfiguration {
#Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder()
.connectedTo("localhost:9200")
.build();
}
#Override
public ElasticsearchCustomConversions elasticsearchCustomConversions() {
Collection<Converter<?, ?>> converters = new ArrayList<>();
converters.add(new EmployeeIdToString());
converters.add(new StringToEmployeeId());
return new ElasticsearchCustomConversions(converters);
}
}
In this case, only the field type needs to be adjusted
#Document
public class DocumentClass {
#Field(type = FieldType.Keyword)
private EmployeeId employeeId;
}
I am guessing you have an Id field which is a string.
You need to put a #JsonValue annotation on that field to make jackson serialize it the way you want.
Field annotated by JsonValue will be used to serialize your pojo into json.
If your Id field is private, then add the annotation on the getter of that field.
https://fasterxml.github.io/jackson-annotations/javadoc/2.8/com/fasterxml/jackson/annotation/JsonValue.html

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 IoC: identifier per request

I've created this bean in order to get a Supplier<String>:
#Bean
public Supplier<String> auditIdSupplier() {
return () -> String.join(
"-",
"KEY",
UUID.randomUUID().toString()
);
}
As you can see, it's intented to only generate an straightforward identifier string.
Each time, it's called, a new identifier is supplied.
I'd like to change this behavior, in order to get the same generated identifier inside request scope. I mean, first time a request is reached, a new indentifier is generated. From then on, next calls no this Supplier has to return the first generated indentifier inside request scope.
Any ideas?
As it was written in commentary, maybe something like below will work:
#Bean
#RequestScope
public Supplier<String> auditIdSupplier() {
String val = String.join("-","KEY",UUID.randomUUID().toString());
return () -> val;
}
This is my version:
#Component
#Scope(WebApplicationContext.SCOPE_REQUEST)
public class AuditIdPerRequest {
private String key;
#PostConstruct
public void calculateKey() {
this.key = String.join(
"-",
"KEY",
UUID.randomUUID().toString()
);
}
public String getAuditId() {
return this.key;
}
}
You need to configure a request scoped bean
#Configuration
public class MyConfig {
#Bean
#RequestScope
public String myRequestScopedIdentifyer(NativeWebRequest httpRequest) {
// You don't need request as parameter here, but you can inject it this way if you need request context
return String.join(
"-",
"KEY",
UUID.randomUUID().toString());
}
And then inject it where appropriate with either field injection
#Component
public class MyClass {
#Autowired
#Qualifier("myRequestScopedIdentifyer")
private String identifier
or object factory
#Component
public class MyClass {
public MyClass(#Qualifier("myRequestScopedIdentifyer") ObjectFactory<String> identifyerProvider) {
this.identifyerProvider= identifyerProvider;
}
private final ObjectFactory<String> identifyerProvider;
public void someMethod() {
String requestScopedId = identifyerProvider.getObject();
}

#value not able to read from application.properties in springboot

I am trying to read value from properties file using #value as follows.
#Value("${abc}")
private String abc;
public List<Record> fetchRecords(String label, String predicate) {
System.out.println(abc);
}
but value of abc is coming as null. Whereas when I try to print the same using #PostConstruct, I am getting the expected value.
#PostConstruct
public void postconstruct() {
System.out.println(abc);
}
Any lead why I am not able to get the value in fetchRecords() method?
For reference, here goes the code
#Component
public class AuditRecord {
private String subject;
private String predicate;
private String oldObject;
private String newObject;
private String readOnlyAuthInfo;
#Value("${registry.system.base}")
private String registrySystemContext;
public void record(DatabaseProvider provider) throws AuditFailedException {
System.out.println("---registrySystemContext value showing null here---"+registrySystemContext);
...
}
#PostConstruct
public void postconstruct() {
System.out.println("---registrySystemContext value showing here as expected---"+registrySystemContext);
}
}
The way I am calling is as follows:
#Component
public class RegistryDaoImpl implements RegistryDao {
...
private void addOrUpdateVertexAndEdge(Vertex v, Vertex dbVertex, GraphTraversalSource dbGraph, String methodOrigin){
...
AuditRecord record = new AuditRecord();
record
.subject(dbVertex.label())
.predicate(e.label())
.oldObject(null)
.newObject(existingV.label())
.record(databaseProvider);
}
}
P.S. registry.system.base is in application.yml.
You need to autowire AuditRecord and not use new directly. Only that way you will have your class in Spring's context.
We don't know your exact usage of the class but you might be interested in Spring's FactoryBean.

How to use Java 8 Optional with Moxy and Jersey

Is it possible to use Jersey with Moxy to/from Json and Java 8 Optionals?
How to configure it?
You can declare following class:
public class OptionalAdapter<T> extends XmlAdapter<T, Optional<T>> {
#Override
public Optional<T> unmarshal(T value) throws Exception {
return Optional.ofNullable(value);
}
#Override
public T marshal(Optional<T> value) throws Exception {
return value.orElse(null);
}
}
And use like this:
#XmlRootElement
public class SampleRequest {
#XmlElement(type = Integer.class)
#XmlJavaTypeAdapter(value = OptionalAdapter.class)
private Optional<Integer> id;
#XmlElement(type = String.class)
#XmlJavaTypeAdapter(value = OptionalAdapter.class)
private Optional<String> text;
/* ... */
}
Or declare in package-info.java and remove #XmlJavaTypeAdapter from POJOs:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(type = Optional.class, value = OptionalAdapter.class)
})
But here are some drawbacks:
Adapter above can only work with simple types like Integer, String, etc. that can be parsed by MOXY by default.
You have to specify #XmlElement(type = Integer.class) explicitly to tell the parser type are working with, otherwise null values would be passed to adapter's unmarshal method.
You miss the opportunity of using adapters for custom types, e.g. custom adapter for java.util.Date class based on some date format string. To overcome this you'll need to create adapter something like class OptionalDateAdapter<String> extends XmlAdapter<String, Optional<Date>>.
Also using Optional on field is not recommended, see this discussion for details.
Taking into account all the above, I would suggest just using Optional as return type for your POJOs:
#XmlRootElement
public class SampleRequest {
#XmlElement
private Integer id;
public Optional<Integer> getId() {
return Optional.ofNullable(id);
}
public void setId(Integer id) {
this.id = id;
}
}

Resources