I am trying to save the following Java object as a document in MongoDB, here are my classes:
public class GenericIpConfig {
String connection_type;
String port;
String port_ingenico;
String ip;
String ip_ingenico;
String id_ingenico;
boolean active_ingenico;
long lastPushTime;
}
#Data
#Document(collection = "generic_device_config")
public class GenericDeviceConfig {
#Id
String _id;
String storeCode;
String companyCode;
long updated;
boolean enabled;
ArrayList<SerialPort> serialPorts;
String companyId;
GenericIpConfig ipConfig;
}
And this is the request I am sending with POSTMAN:
{
"updated":0,
"enabled":true,
"serialPorts":[],
"companyId":"600",
"companyCode":"0",
"storeCode":"0",
"ipConfig": {
"connection_type":"ETHERNET",
"port": "23",
"port_ingenico": "",
"ip": "192.168.10.55",
"ip_ingenico": "",
"id_ingenico": "",
"active_ingenico": false,
"lastPushTime": 0
}
}
For some reason, I keep getting the following error:
JSON parse error: Unrecognized field \"connection_type\" (class it.igesa.monitoringsystem.model.mongo.device.config.GenericIpConfig), not marked as ignorable; nested exception is com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field \"connection_type\" (class it.igesa.monitoringsystem.model.mongo.device.config.GenericIpConfig), not marked as ignorable (0 known properties: ]
I feel like my code is missing something, since this is the first time I am working with SpringBoot/MongoDB;
It looks as if Jackson can't recognise setter methods for fields, because they don't follow java naming conventions. Try fixing the unrecognised fields with this annotation - #JsonProperty("property_name_here"), by putting it on the setters.
You are also mixing them, some of your fields are named something_something, while others are somethingSomething, which is the standard java way. It's a good idea to pick one and stick to it for the project.
You should also read up on serialization with jackson documentation, annotation examples.
Finally I found the solution. Defining setters and getters annotations for the nested class solved the problem.
I had an extra getter method in a class that I was reusing.
It worked fine when I removed the extra getter.
I hope you find this useful too.
Thanks a lot.
Related
I need to compare one PatientDTO dto object with other one PatientModel model object.
Both classes are quite similar:
class PatientDTO {
private String name;
private List<AddressDTO> address;
// Constructors, getters and setters
}
class PatientModel {
private String id;
private String nameElement;
private List<AddressModel> addressElement;
// Constructors, getters and setters
}
class AddressDTO {
private String city;
private String country;
private List<String> linesElement;
// Constructors, getters and setters
}
class AddressModel {
private String city;
private String countryElement;
private List<String> linesElement;
// Constructors, getters and setters
}
Main differences are:
Some fields are not present on DTOs: PatientDTO.id doesn't exist.
Some field names contains suffixes on Model classes: PatientDTO.name <> PatientModel.nameElement.
Other issue I like to solve, is that:
Address related assertion should be shared. I mean, Address-like classes are present on other classes, for exemple, Organization, Practitioner...
I'd like to build an assertion like this:
PatientDTO patientDTO;
PatientModel patientModel;
assertThat(patientDTO).isEqual(patientModel);
Shortly:
Should I build a custom assertion?
Should I have an assertion for Address an other one for Patient containing previous Address assertion? How could I get this?
What aboud Address assertion for Patient, Organization
What I want to avoid is code like this:
assertThat(patientDTO).anySatisfy(p->{
assertThat(p.getName()).withFailMessage("expected name: "+ p.getAddress().getCity()).isEqualTo(patientModel.getNameElement());
assertThat(p.getAddress().getCity()).withFailMessage("expected city: "+ p.getAddress().getCity()).isEqualTo(patientModel.getCityElement());
assertThat(p.getAddress().getCountry()).withFailMessage("expected country: "+ p.getAddress().getCountry()).isEqualTo(patientModel.getCountryElement());
...
}
);
I want to avoid above code since Patient classes are really large. Here I've shorted them for clarity purpouses.
Any ideas?
The field-by-field recursive comparison could help for this purpose:
PatientDTO patientDTO = new PatientDTO(...);
PatientModel patientModel = new PatientModel(...);
assertThat(patientDTO).usingRecursiveComparison()
.isEqualTo(patientModel);
Some fields are not present on DTOs: PatientDTO.id doesn't exist.
There are a few methods that can be used to tune the comparison and ignore fields:
Directly with ignoringFields(String… fieldsToIgnore)
By regexes with ignoringFieldsMatchingRegexes(String… regexes)
By types with ignoringFieldsOfTypes(Class… typesToIgnore)
Some field names contains suffixes on Model classes: PatientDTO.name <> PatientModel.nameElement.
This is currently not supported and was also asked in https://stackoverflow.com/a/70381488/9714611. We plan to raise a feature request about it and I will update the answer once the issue link is ready.
Address related assertion should be shared. I mean, Address-like classes are present on other classes, for exemple, Organization, Practitioner...
If the target is always isEqualTo, probably a custom assertion implementation is not needed as long as the limitation of the recursive comparison about not being able to compare fields with different names is not a show-stopper. These fields would require ad-hoc comparison until a better solution is available.
If the target is to provide assertions in a domain-specific language, like:
assertThat(patientDTO).hasAddress(addressDTO);
then a custom assertion implementation can be added.
Also, there is an assertions generator with plugins for Maven and Gradle that can be used to generate assertions based on the class attributes.
Tools :
Weblogic 12c
Jersey 2.21.1
Jackson 2
public class Profile implements Serializable
{
private List<Status> orderStatus;
public void setOrderStatus(List<Status> orderStatus)
{
this.orderStatus = orderStatus;
}
public void getOrderStatus()
{
return orderStatus;
}
I have a simple POJO class as above.
I am facing below issue while using Jersey 2 with Jackson.
1)When Profile class is serialised , the JSON gets created as : {"OrderStatus":[{}]}
2)So the key generated is OrderStatus and not orderStatus
3)When this JSON gets de-serialised , it throws error -
Caused by: org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "OrderStatus" since it cannot
find field with OrderStatus but has field as orderStatus
I have tried adding :
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY,getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
hoping that serialisation will only use fields as a key (and will not use property) and JSON will be generated as {"orderStatus":[{}]} .
But it is not working and same error is thrown.
Is there any way we can serialise POJO with key as fields and not properties.
Can anybody please help here ?
If you want to make sure orderStatus is the name use specific JsonProperty :
#JsonProperty("orderStatus")
public void getOrderStatus()
#JsonProperty (also indicates that property is to be included) is used to indicate external property name
Here is my entity class:
public class User {
#Id
UserIdentifier userIdentifier;
String name;
}
public class UserIdentifier {
String ssn;
String id;
}
Here is what I am trying to do:
public interface UserRepository extends MongoRepository<User, UserIdentifier>
{
User findBySsn(String ssn);
}
I get an exception message (runtime) saying:
No property ssn found on User!
How can I implement/declare such a query?
According to Spring Data Repositories reference:
Property expressions can refer only to a direct property of the managed entity, as shown in the preceding example. At query creation time you already make sure that the parsed property is a property of the managed domain class. However, you can also define constraints by traversing nested properties.
So, instead of
User findBySsn(String ssn);
the following worked (in my example):
User findByUserIdentifierSsn(String ssn);
I'm trying to get Kotlin working with jsr 303 validation on a spring-data-rest project.
Given the following data class declarartion :
#Entity data class User(
#Id
#GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
var id: Long? = null,
#Size(min=5, max=15)
val name: String
)
The #Size annotation has no effect here, making me able to save a user with a name of 1 character.
It works well when executing the very same example but in a Java class instead of Kotlin.
This makes me think of a Kotlin problem.
Thanks in advance for you help !
You need to use Annotation use-site targets since the default for a property declared in the constructor is to target the annotation on the constructor parameter instead of the getter (which will be seen by JavaBeans compliant hosts) when there are multiple options available. Also using a data class might be inappropriate here (see note at end).
#Entity data class User(
#Id
#GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
var id: Long? = null,
#get:Size(min=5, max=15) // added annotation use-site target here
val name: String
)
The property target from the Kotlin docs may look tempting, but it can only be seen from Kotlin and not Java. Usually get does the trick, and it is not needed on the bean set.
The docs describe the process as:
If you don’t specify a use-site target, the target is chosen according to the #Target annotation of the annotation being used. If there are multiple applicable targets, the first applicable target from the following list is used:
param
property
field
And the #Size annotation is:
#Target(value={METHOD,FIELD,ANNOTATION_TYPE,CONSTRUCTOR,PARAMETER})
Therefore since PARAMETER is a valid target, and multiple targets are available (parameter, field, method [get/set]) it choses PARAMETER which is not what you want. Therefore for a JavaBean host to see the property it will look for the getter (properties are defined by the getter/setter and not the backing field).
In one of the Java samples, it shows:
public class Book {
private String title;
private String description;
// ...
#NotEmpty(groups={FirstLevelCheck.class, Default.class})
#Size(max=30)
public String getTitle() {
return title;
}
// ...
}
Which matches our usage of having it on the getter. If it were to be on the field like some of the validation annotations show, see the field use-site target. Or if the field must also be publicly accessible, see the #JvmField annotation in Kotlin.
NOTE: As mentioned in notes from others, you should likely consider NOT using a data class for entities if they use an auto-generated ID since it will not exist for new objects the same as for retrieved objects; and a data class will generate equals and hashCode to include all fields including the ones it should not. You can read guidance about this from the Hibernate docs.
Use the #get or #field targets for validation annotations. Annotations with the target #param(first default) and #property are not supported.
e.g:
From #NotEmpty To #field:NotEmpty
data class Student(
#field:NotEmpty #field:Size(min= 2, message = "Invalid field") var name: String? = ""
)
GL
Jayson Minard
Annotation use site targets
I am using Spring 4 + Jackson 2 and have written a fully functional POST method using #RequestBody on a custom class. This method has no trouble unmarshalling the object.
#ResponseBody
#RequestMapping(value="store", method = RequestMethod.POST)
public ServiceResponse store(#RequestBody CustomClass list) {
...
}
// Request: { code: "A", amount: 200 }
When I attempted to add another method to handle a collection of the same class instead, my POST requests were returning with the following error.
HTTP Status 400: The request sent by the client was syntactically incorrect.
I note that this error typically occurs when the JSON submitted does not match the entity class. However, all I am doing is submitting an array of the same object instead of the object itself, which has already proven to work.
#ResponseBody
#RequestMapping(value="store-bulk", method = RequestMethod.POST)
public ServiceResponse storeBulk(#RequestBody List<CustomClass> list) {
...
}
// Request: [{ code: "A", amount: 200 }, { code: "B", amount: 400 }]
Am I missing something here?
In Java, type information for generics is erased at runtime, so Spring sees your List<CustomClass> object as List<Object> object, thus it cannot understand how to parse it.
One of ways to solve it, you could capture the type information by creating a wrapper class for your list, like this:
public class CustomClassList extends ArrayList<CustomClass> {
}
Sergey is right that the issue is due to type erasure. Your easiest way out is to bind to an array, so
#ResponseBody
#RequestMapping(value="store-bulk", method = RequestMethod.POST)
public ServiceResponse storeBulk(#RequestBody CustomClass[] object) {
...
}
The answer is that Spring 4 doesn't actually get rid of type erasure, contrary to what some other solutions suggest. While experimenting on debugging via manual unmarshalling, I decided to just handle that step myself instead of an implicit cast that I have no control over. I do hope someone comes along and proves me wrong, demonstrating a more intuitive solution though.
#ResponseBody
#RequestMapping(value="store-bulk", method = RequestMethod.POST)
public ServiceResponse storeBulk(#RequestBody String json) {
try {
List<CustomClass> list = new ObjectMapper().readValue(json, new TypeReference<List<CustomClass>>() { });
...
} catch (Exception e) {
...
}
}
Bonus: Right after I got this working, I bumped into this exception:
IllegalStateException: Already had POJO for id
If anyone gets this, it's because the objects in the list happen to reference some object that another item in the list already references. I could work around this since that object was identical for my entire collection, so I just removed the reference from the JSON side from all but the first object. I then added the missing references back after the JSON was unmarshalled into the List object.
Two-liner for the Java 8 users (the User object reference was the issue in my case):
User user = list.get(0).getUser();
list.stream().filter(c -> c.getUser() == null).forEach(t -> t.setUser(user));