I had a Java Class linked to a MongoDB Collection:
#Document(collection = "my_collection")
public class Ev extends MyDTO{
#Id
private String id;
#Indexed
private String sessionId;
private List<String> findings;
}
I had to change findings in this
private List<MyObject> findings;
Declared as
public class MyObject {
private String find;
private String description;
private int number;
private List<SecondaryObj> details;
}
Here are the constructors
public MyObject(String find, int number) {
super();
this.find= find;
this.number= number;
}
public MyObject(String find, int number, List<SecondaryObj> details) {
super();
this.find= find;
this.details = details;
this.number= number;
}
So in mongoDB I have a situation similar to
{
"_id" : ObjectId("5b487a2667a1aa18f*******"),
"sessionId" : "abc123mySessionId",
"findings" : [
{
"find" : "HTTPS",
"description" : "I found HTTPS",
"number" : 10,
"details": [
{"a":"1", "b":"2"},
{"a":"2", "b":"3"}
]
},
{
"find" : "NAME",
"description" : "I found name",
"number" : 3,
"details": [
{"a":"1", "b":"2"},
{"a":"2", "b":"3"}
]
}
]
}
I obviously updated all the methods to match the new data set, but if I try to retrieve
Query searchQuery = new Query(Criteria.where("sessionId").is("abc123mySessionId"));
Ev result = mongoTemplate.findOne(searchQuery, Ev.class);
I obtain this error
Request processing failed; nested exception is org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate com.my.project.domain.MyObject using constructor NO_CONSTRUCTOR with arguments
with root cause
java.lang.NoSuchMethodException: om.my.project.domain.MyObject.<init>()
I'm using spring-data-mongodb version 2.0.8 and mongo-java-driver version 3.8.0
I think I should declare MyObject somewhere, but I'm pretty new in Spring, so I'm trying in a kinda blind way... Any suggestion?
You have two non-zero-argument constructors and Spring does not know which one to call. It tries to call no-args constructor, but your class does not have that one.
Check Spring Data Mongo docs
You can create no-args constructor and mark it with #PersistenceConstructor annotation. This way Spring calls it to create an object and sets fields via reflection based on a document fields names, so no setters are required.
#Document(collection = "my_collection")
public class Ev extends MyDTO{
#Id
private String id;
#Indexed
private String sessionId;
private List<MyObject> findings;}
public class MyObject {
private String find;
private String description;
private int number;}
In this it work fine for me in spring-boot-starter-data-mongodb - version 2.0.3.RELEASE
Related
we are using spring-boot 2.2.1 and query-dsl-mongoDB 4.2.1.
we are using spring-data.mongodb findAll method tp find books from book collection using
various predicates like bookUid, authorId, customerId, status, isbn and provisioningId.
I can able to construct for all the attributes except bookInfo.
Please find the sample collection for reference.
{
"_id" : ObjectId("6036323daa819c04005cff68"),
"bookUid" : "spring_boot",
"authorId" : "602bc44827e37ca2ba281f54",
"customerId" : "75e1c48e",
"status" : "ACTIVE",
"name" : "Spring boot",
"statusTimestamp" : ISODate("2021-02-24T11:07:28.000Z"),
"deleted" : false,
"bookInfo" : {
"isbn" : "240220211202",
"provisioningId" : "240220211202"
},
"customInfo" : {},
"version" : 1,
"countryCode" : "CZ",
"_class" : "book-collection"
}
And this is the Java class,
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Book {
#Id
private String id;
#Indexed
#WhiteSpaceTrim
private String bookUid;
private String authorId;
private String customerId;
private String status;
private String name;
private Date statusTimestamp;
private boolean deleted;
#WhiteSpaceTrim
private Map<String, String> provisionInfo;
private SmartObject customInfo;
private int version;
private String countryCode;
}
While looking into query DSL autogenerated class I see the types for BookInfo is QMap
public final ext.java.util.QMap provisionInfo = new
ext.java.util.QMap(forProperty("bookInfo"));
I tried to construct below predicate whether bookInfo matches the bey key and value
Map<String, String> expre = new HashMap<>();
expre.put(key, value);
predicates.add(QBook.book.provisionInfo.in(expre).isTrue());
But no luck it was not working and thrown exception, Then tried following expression
PathBuilder entityPath = new PathBuilder<>(Book.class,
"bookInfo");
predicates.add(entityPath.getMap("map", String.class, String.class).get(key).eq(value));
But it returns an empty collection always even though we have matching isbn or provisioningId.
I was looking into the documentation but I couldn't find any help either.
Any help would be really appreciable.
How can I unmarhsaller json file like this?
{
"packageId": "11",
"jsScript": "var divideFn = function(a,b) { return a/b} ",
"functionName": "divideFn",
"tests": [
{
"testName": "test1",
"expectedResult": "2.0",
"params": [
2,
1
]
}
]
}
I have a class that works well with packageId, jsScrript, functionName, but not with the tests
public class Data {
private final int packageId;
private final String jsScript;
private final String functionName;
private final List<Tests> tests;
#JsonCreator
public Data(#JsonProperty("packageId") String packageId,
#JsonProperty("jsScript") String jsScript,
#JsonProperty("functionName") String functionName,
#JsonProperty("tests") List<Tests> tests) {
this.packageId = Integer.parseInt(packageId);
this.jsScript= jsScript;
this.functionName = functionName;
this.tests = tests;
}
}
public class Tests{
public final String testName;
public final int expectedResult;
#JsonCreator
public Tests(#JsonProperty("testName") String testName,
#JsonProperty("expectedResult") String expectedResult){
this.testName= testName;
this.expectedResult = Integer.parseInt(expectedResult);
}
}
What should I change in classes to make it work well?
I also tried to read tests like String, but it didn't help
It seems that you have encountered some problems for deserializing the JSON array tests to objects. There are several methods to solve this.
Method 1
Add #JsonIgnoreProperties(ignoreUnknown = true) on your class Tests to prevent your code from the exception of Unrecognized field "params".
Method 2
Deserialize JSON array tests to List<JsonNode> if it is not important and you won't further parse it in the future.
Method 3
Use follwing class for mapping JSON array tests to List<Test>.
class Test {
private String testName;
private Float expectedResult;
private List<Integer> params;
//general getters ans setters
}
BTW, I don't think you need to use #JsonCreator for deserialization.
I have this POJO :
public class PlayerDto {
private Long id;
private String name;
private String past;
}
And I have this entity :
public class Player {
private Long id;
private String name;
private List<String> past;
}
How can I map the List<String> past into the String past of the DTO wih MapStruct ? For example the List is containing [ Monty , Boto , Flaouri ] and the String of the DTO has to contain "Monty, Boto, Flaouri" in a single String.
This classic way doesn't work with the target and source :
#Mappings({
#Mapping(target = "past", source = "past"),
})
PlayerDto entityToDto(final Player entity);
Thanks
I guess you need to define a default method in your mapper interface to handle data conversion from List<String> to String. Mapstruct will automatically use the default method.
The default method signature for your mapping should be like this :
String map(List<String> past)
Example :
default String map(List<String> past) {
return past.stream().collect(Collectors.joining(","));
}
Project I'm working on has the following come through via MQ:
example.json
{
"templateName": "testTemplate",
"to": [
"support#test.com"
],
"cc": [
"testCc#test.com
],
"bcc": [
"testBcc#test.com
],
"from": "testFrom#test.com",
"subject": "testSubject",
"replacementValues": {
"replacementValue1": "lorem",
"replacementValue2": "ipsum"
},
"jsonObject": {
//omitted for brevity
}
}
And as is, it will map to the following object:
NotificationV1.java
public class NotificationV1 {
private String templateName;
private List<String> to;
private List<String> cc;
private List<String> bcc;
private String from;
private String subject;
private Map<String, String> replacementValues;
private Map<String, String> images;
private Object jsonObject;
//getters & setters omitted for brevity
using the following mapper:
//no special config
notificationMessage = new ObjectMapper().readValue(jsonMessage, EmailNotificationMessage.class);
As part of a project wide refactor, the data class above has been altered to instead look like this:
NotificationV2.java
public class NotificationV2 {
private EmailHeaders emailHeaders;
private TemplateData templateData;
//getters and setters omitted
EmailHeaders.java
public class EmailHeaders {
private String from;
private List<String> toAddresses;
private List<String> ccAddresses;
private List<String> bccAddresses;
private String subject;
//getters and setters omitted
TemplateData.java
public class TemplateData {
private String templateName;
private Map<String, String> replacementValues;
private Map<String, String> images;
private Object jsonObject;
//getters and setters omitted
Naturally, the existing mapping throws errors around unrecognised properties in the json vs. the new object; cant map templateNAme, found emailHeaders and templateData, and so on. I cant change the structure of the json in order to fit the new object but havent found a resource that demonstrates a use case like the above for mapping. Are there annotations I can use on NotificationV2 and/or some sort of mapper configuration I can put together in order to hook all of this up?
To flatten your nested classes, you can use the annotation #JsonUnwrapped.
Example:
public class Parent {
public int age;
public Name name;
}
public class Name {
public String first, last;
}
This would normally be serialized as follows:
{
"age" : 18,
"name" : {
"first" : "Joey",
"last" : "Sixpack"
}
}
By updating the parent to use #JsonUnwrapped, we can flatten the nested objects:
public class Parent {
public int age;
#JsonUnwrapped
public Name name;
}
This will output the following:
{
"age" : 18,
"first" : "Joey",
"last" : "Sixpack"
}
See docs for more information
Trying to save One to Many JPA relationship. I have written a custom controller. I am getting only the first id in giftSet and not all the ids. I have simplified the code.
My Post request-
{
"name": "Project 7",
"giftSet": [
{
"id": "1"
},
{
"id":"33"
}
]
}
class Holiday{
private String name;
private Set<GiftConfig> giftSets;
}
class GiftSet {
private Integer id;
private Holiday holiday;
}
class GiftConfig {
private Integer id;
private String name;
}
#RequestMapping(method = RequestMethod.POST, value="/api/saveholiday")
public ResponseEntity<Map<String, Holiday>> saveHoliday(#RequestBody Holiday holiday) {
System.out.println(holiday);
return null;
}
First, I add multiple GiftConfig. After that, while creating Holiday, I add details for GiftSet as well.
In debug mode, I see only id 1 in giftSet and not both ids 1 and 33.
Note- Changing Set to List is not an option.
Introduction
I see 2 problems and one possible last issue.
You are missing setters/getters in order for de-serialization to work on the JSON.
Your payload doesn't seem to be working for me.
As pcoates mentioned in a comment, you could also use #JsonAutoDetect(fieldVisibility = Visibility.ANY) - but I haven't tested this.
Finally, also be careful about having a circular reference if you convert from java back to JSON. I see that a Holiday has a set of giftSets, but a giftSet points to a holiday.
If the gitset points to the same parent holiday, this is a circular reference and will crash.
Getters and Setters
Your problem is that you are missing getters and setters.
Either use lombok and add a #data annotation or add a getter and setter .
#Data
public static class Holiday{
private String name;
private Set<GiftSet> giftSets;
}
#Data
public static class GiftSet {
private Integer id;
private Holiday holiday;
}
Payload
I used the following payload:
{
"name": "HolidaySet",
"giftSets": [
{
"id": 1111,
"holiday": {
"name": null,
"giftSets": null
}
},
{
"id": 1112,
"holiday": {
"name": null,
"giftSets": null
}
}
]
}
Quick Test
I did a quick test to see what the payload should be like.
#RequestMapping(method = RequestMethod.POST, value="/api/saveholiday")
public ResponseEntity<Map<String, Holiday>> saveHoliday(#RequestBody Holiday holiday) throws JsonProcessingException {
System.out.println(holiday);
fakeItTest();
return null;
}
private void fakeItTest() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
Set<GiftSet> giftSets2 = new HashSet<>();
GiftSet gg = new GiftSet();
gg.setId(1111);
gg.setHoliday(new Holiday());
giftSets2.add(gg);
GiftSet gg2 = new GiftSet();
gg2.setId(1112);
gg2.setHoliday(new Holiday());
giftSets2.add(gg2);
Holiday holiday2 = new Holiday();
holiday2.setName("HolidaySet");
holiday2.setGiftSets(giftSets2);
String a = objectMapper.writeValueAsString(holiday2);
System.out.println(a);
}
#Data
public static class Holiday{
private String name;
private Set<GiftSet> giftSets;
}
#Data
public static class GiftSet {
private Integer id;
private Holiday holiday;
}