Get Embed Document based on DBRef in MongoDB using Spring data - mongodb

I'm developing a spring boot application. I've 2 models that is
PersonEntity
#Data
#Document(collection = "products")
public class ProductEntity implements Serializable {
private static final long serialVersionUID = -8118791879505582652L;
#MongoId(FieldType.STRING)
private String id;
#Indexed
#Field("name")
private String name;
#Field("category")
#DBRef(lazy = true)
private CategoryEntity category;
// getters and setters
}
CategoryEntity
#Data
#Document(collection = "categories")
public class CategoryEntity implements Serializable {
private static final long serialVersionUID = -636356399519451958L;
#MongoId(value = FieldType.STRING)
private String id;
#Field("name")
private String name;
#Field("desc")
private String desc;
public CategoryEntity(String id) {
super();
this.id = id;
}
// getters and setters
}
when I'm saving data into DB then stored data is
Products Document
{
"_id": {
"$oid": "6141eeab92432109463ae8c4"
},
"name": "Adidas Running Shoes",
"category": {
"$ref": "categories",
"$id": "614049d91042cf40b5b9b304"
}
}
Categories Document
{
"_id": {
"$oid": "614049d91042cf40b5b9b304"
},
"name": "Shoes",
"desc": "Men's running Shoes",
}
But when I'm try to findAll() the ProductEntity the data by using Spring-Data-MongoDB the category will return null.
public List<ProductModel> getProducts() {
List<ProductEntity> entities = productRepo.findAll();
return entities.stream().map(entity -> {
ProductModel product = new ProductModel();
BeanUtils.copyProperties(entity, product);
if (Objects.nonNull(entity.getCategory())) {
BeanUtils.copyProperties(entity.getCategory(), product.getCategory());
}
return product;
}).collect(Collectors.toList());
}
and I need the output will be in below format.
{
"_id": {
"$oid": "6141eeab92432109463ae8c4"
},
"name": "Adidas Running Shoes",
"category": {
"_id": {
"$oid": "614049d91042cf40b5b9b304"
},
"name": "Shoes",
"desc": "Men's running Shoes",
}
}
Please let me know how to get desired output.

I've done a minor change and issue has been resolved. I've changed
from
#MongoId(value = FieldType.STRING)
private String id;
to
#MongoId(value = FieldType.OBJECT_ID)
private ObjectId id;
in the both entities and code works!

Related

Spring boot application with JPA, many to many relation with additional attributes returning infinite recursion error

I tried to return the entity directly as response resulted in error as "Could not write JSON: Infinite recursion (StackOverflowError)", I would like to get the response in both forms with out transforming the data. Can some one suggest work around. Adding the Entity structure for your reference.
Employee Entity
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long employeeId;
private String firstName;
private String lastName;
private String role;
#OneToMany(mappedBy = "employee")
private Set<EmployeeStream> streams = new HashSet<>();
}
Stream Entity
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Stream {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long streamId;
private String streamName;
#OneToMany(mappedBy = "stream")
private Set<EmployeeStream> employees = new HashSet<>();
}
EmployeeStream Entity with additional Attribute experience
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class EmployeeStream {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "EMPLPOYEE_ID")
private Employee employee;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "STREAM_ID")
private Stream stream;
private double experience;
}
I would like to retrieve data in both forms
{
"employeeId": 1,
"firstName": "George",
"lastName": "Stephen",
"role": "developer",
"streams": [
{
"id": 1,
"stream": {
"streamId": 1,
"streamName": "Java"
},
"experience": 1.2
},
{
"id": 2,
"stream": {
"streamId": 2,
"streamName": "Node JS"
},
"experience": 2
}
]
}
{
"stream": {
"streamId": 1,
"streamName": "Java",
"employees": [
{
"id": 1,
"employee": {
"employeeId": 1,
"firstName": "George",
"lastName": "Stephen",
"role": "developer"
},
"experience": 2
},
{
"id": 2,
"employee": {
"employeeId": 1,
"firstName": "Davis",
"lastName": "Tom",
"role": "developer"
},
"experience": 0.5
}
]
}
}
You need to use #JsonBackReference and #JsonManagedReference as used in the below in below code.
Employee entity
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long employeeId;
private String firstName;
private String lastName;
private String role;
#OneToMany(mappedBy = "employee")
#JsonManagedReference
private Set<EmployeeStream> streams = new HashSet<>();
}
Stream entity
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Stream {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long streamId;
private String streamName;
#OneToMany(mappedBy = "stream")
#JsonManagedReference
private Set<EmployeeStream> employees = new HashSet<>();
}
EmployeeStream entity
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class EmployeeStream {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "EMPLPOYEE_ID")
#JsonBackReference
private Employee employee;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "STREAM_ID")
#JsonBackReference
private Stream stream;
private double experience;
}
You can find more details here and here
Update: Previously #JsonBackReference and #JsonManagedReference was misplaced, corrected the same.
Note: If still faced any problem then try removing #JsonManagedReference from Stream and Employee entities.

How to get data with referenced object in Spring data mongo?

This is City document class which i used.
#Document("city")
public class City {
#Id
private String id;
#Indexed(unique = true)
private String name;
#DBRef(lazy = true)
private District district;
public City() {
}
public City(String id) {
this.id = id;
}
public City(String id, String name, District district) {
this.id = id;
this.name = name;
this.district = district;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public District getDistrict() {
return district;
}
public void setDistrict(District district) {
this.district = district;
}
}
and this is the District document class
#Document("district")
public class District {
#Id
private String id;
#Indexed
private String name;
public District() {
}
public District(String id) {
this.id = id;
}
public District(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
and this is the district collection data.
/* 1 */
{
"_id" : ObjectId("5d9482f3ff7ab743f0542070"),
"name" : "Ampara",
"_class" : "com.test.sample.model.District"
}
/* 2 */
{
"_id" : ObjectId("5d9482f3ff7ab743f0542071"),
"name" : "Anuradhapura",
"_class" : "com.test.sample.model.District"
}
and this is the city collection data.
/* 1 */
{
"_id" : ObjectId("5d948333ff7ab743f0542089"),
"name" : "Polgampola",
"district" : {
"$ref" : "district",
"$id" : ObjectId("5d9482f3ff7ab743f0542070")
},
"_class" : "com.test.sample.model.City"
}
/* 2 */
{
"_id" : ObjectId("5d948333ff7ab743f054208a"),
"name" : "Porawagama",
"district" : {
"$ref" : "district",
"$id" : ObjectId("5d9482f3ff7ab743f0542070")
},
"_class" : "com.test.sample.model.City"
}
/* 3 */
{
"_id" : ObjectId("5d948333ff7ab743f054208b"),
"name" : "Akkaraipattu",
"district" : {
"$ref" : "district",
"$id" : ObjectId("5d9482f3ff7ab743f0542071")
},
"_class" : "com.test.sample.model.City"
}
this is the repository class for the above city document class
#Repository
public interface CityDao extends MongoRepository<City,String> {
List<City> findByDistrict(String id);
}
in the city class, i have used referenced document class called District. this a another document. I need to get the cities which belong to one district. findByDistrict(String id) method doesn't return values. Its just returning empty List. So How can i do this ?
I have tried with another alternative.As #pvpkirn said, You cannot query with #DBRef. But i tried with MongoOperations. With this we can pass the Filter objects as Json object. Using this i got the answer. This is what i tried.
public List<City> findByDistrictId(String districtId) {
final Bson filter = eq("district.$id", new ObjectId(districtId));
FindIterable<Document> documents = super.mongoOperations.getCollection("city").find(filter);
List<City> cities = new ArrayList<>();
for(Document document : documents){
City city = new City(document.getObjectId("_id").toString(),document.getString("name"),new District(districtId));
cities.add(city);
}
return cities;
}
in here i have used com.mongodb.client.model.Filters.eq to get the relevant bson object.

Included property returns object that it's included to

I'm working on my side project with is a WebAPI in ASP.NET Core 2.1. I'm using Entity Framework Core 2.1.
I have a User:
public class User : Account
{
public UInt64 UserEmail { get; protected set; }
public UserImage UserImage { get; set; }
public virtual ICollection<Recipe> Recipes { get; set; }
public User() { }
public User(string nick, UInt64 login, byte[] salt, byte[] passwordHash,
string restoreKey, UInt64 userEmail) : base(nick, login, salt, passwordHash, restoreKey)
{
UserEmail = userEmail;
Role = "user";
}
public void Update(UInt64 login, UInt64 userEmail)
{
Login = login;
UserEmail = userEmail;
UpdatedAt = DateTime.UtcNow;
}
public void UpdatePassword(byte[] newPassword)
{
PasswordHash = newPassword;
UpdatedAt = DateTime.UtcNow;
}
}
and UserImage:
public class UserImage : Image
{
public int? UserRef { get; set; }
public virtual User User { get; set; }
public UserImage() : base() { }
public UserImage(string content) : base(content) { }
}
In my DbContext I have something like this:
modelBuilder.Entity<User>()
.HasOne(x => x.UserImage)
.WithOne(y => y.User)
.HasForeignKey<UserImage>(y => y.UserRef)
.IsRequired(false);
I have a method in my service that returns a user with his image:
public async Task<User> GetAsync(int id)
{
var user = await _context.Users.GetById(id)
.Include(x => x.UserImage)
.SingleOrDefaultAsync();
if (user == null)
throw new CorruptedOperationException("Invalid id");
return user;
}
The problem is that when I use a Postman to get a user I get that kind of response:
{
"userEmail": 2606810040825320252,
"userImage": {
"userRef": 16,
"user": {
"userEmail": 2606810040825320252,
"recipes": null,
"nick": "MadBear123",
"login": 10458175107962595193,
"salt": "Fv/S1pnpu1u0RA6RxE1wfwCmqhbkb0Fu0W2sOuFgv//PHyizyPtmuaX8OtYkCgSJPlKMGmE2qFgg2rgs70Ee9bbMU26iVhtIApqV/Zxac54P9EXBvgkAXede3YHzSPzHkvGz3WchUUDIQqHF+EmdvPT9KuYR1Djgywxh0bDbSJk=",
"passwordHash": "Yk5S3jutaKJpwSQoRH0nk2At3nYL/Wzi+8QGRFOZuByi54o+YJHuhPYRMMSG3Vmimv1UMRWe+VA8ym2xQxoEJA==",
"role": "user",
"restoreKey": "Vub!#g17#kcP",
"id": 16,
"createdAt": "2018-10-14T11:11:43.9902857",
"updatedAt": "2018-10-14T11:11:43.990384"
},
"imageContent": "/9j/4AAQSkZJRgABAQEASABIAAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAAgACADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDjZ/jPZ6rHocDfFz4iNI19b3BU+FdFjYiEho2/5A6l8Mi53Eg4bIbkj3Sx/bi0/T/2dde0CP8AaL1xdvhK+i+w3EeiQCeR4r7bA2NMj3rtnLhScEyLtJZRt+a/2fH8S/Ha5t77wn4x+Hfiqy0q8neCHRrW8vL2dTpuq3bhbeOOWbcqQNshdDNI8kaRqzAkYXiT9tDxF8DLbxlp+peE9b8davp8b6fdaB4ZsL9LqKFZJI57m9lubJhp6o6JGYZojch2w8UIwzEpSneEd9OtjOPJDV7el/yP2V+Hv7VvhvxLrhcftfaTHYl5CkWpXfhhHwJJFCsBBG4OAp6Y57cVjfAL9rHwj4Y/Zx+HMM37YXwb0KSPw7pkLWep3OjF4X+zQp5J/wBJjbcGIXB5yQOtfLn7FH/BbZpPA0eqN8N/EXgLVoI7u5utD8f6nqVrDeRxh5B/Z15baNLFdyNiSMwlIpt6/LG6q7pa0T/gtFefD79kf4e3uo6h8CdBsdA8N2S2clz8ablL7Xo47GMFhpy6FJM8i5IKhSySoACzqm6fehG1rtFq0pXvZH4L/sdftNWH7OOo61f65pt94g0W7sWsY9OsPEUmky3N23EMjT2zrcxLFG9w6tGrK0gjjcFJHFdR8ef2620zXZtY+EPi/wCLWh3HisTy+JLbxFqqXVxb3MgdHe0voPLdoZI3QFJIw6vACXkDkD550T4a61qHmC3jhZmwCd3T9K0JPgT4vZvlsvM3DGVfj9a6oymlaK179TCXI37z07dD6k+B3/BT/VPF+ixt8dvGnxw+JMvhXVDqnhfQtP8AEUVnZ3FxPb/Zp5r69niuLhsQBo4lEbGP7ROysm+RJfIf21v2pNU/a++KknjTVJLiBZ4I9IhtL3xFNq1zp0NuCY4fOu5ZLuaNVkAEk5JJ3ruYoSeb8IfslfEDxEFa2t7W3C95p9nqewNaXxW/Z48caV4b0e21OOzm/s8zKskV2824OwY/KVG0jpkZJGAeAMaSjVlG8l87av5mcalGMrRa9L6L0Wx//9k=",
"id": 10,
"createdAt": "2018-10-16T05:45:42.4644513",
"updatedAt": "2018-10-16T05:45:42.4645292"
},
"recipes": null,
"nick": "MadBear123",
"login": 10458175107962595193,
"salt": "Fv/S1pnpu1u0RA6RxE1wfwCmqhbkb0Fu0W2sOuFgv//PHyizyPtmuaX8OtYkCgSJPlKMGmE2qFgg2rgs70Ee9bbMU26iVhtIApqV/Zxac54P9EXBvgkAXede3YHzSPzHkvGz3WchUUDIQqHF+EmdvPT9KuYR1Djgywxh0bDbSJk=",
"passwordHash": "Yk5S3jutaKJpwSQoRH0nk2At3nYL/Wzi+8QGRFOZuByi54o+YJHuhPYRMMSG3Vmimv1UMRWe+VA8ym2xQxoEJA==",
"role": "user",
"restoreKey": "Vub!#g17#kcP",
"id": 16,
"createdAt": "2018-10-14T11:11:43.9902857",
"updatedAt": "2018-10-14T11:11:43.990384"
}
Like you can see I get a user with his image and in this image i get (again) this user.
I would like to get response like this:
{
"userEmail": 2606810040825320252,
"userImage": {
"userRef": 16,
"user": null,
"imageContent": "/9j/4AAQSkZJRgABAQEASABIAAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAAgACADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDjZ/jPZ6rHocDfFz4iNI19b3BU+FdFjYiEho2/5A6l8Mi53Eg4bIbkj3Sx/bi0/T/2dde0CP8AaL1xdvhK+i+w3EeiQCeR4r7bA2NMj3rtnLhScEyLtJZRt+a/2fH8S/Ha5t77wn4x+Hfiqy0q8neCHRrW8vL2dTpuq3bhbeOOWbcqQNshdDNI8kaRqzAkYXiT9tDxF8DLbxlp+peE9b8davp8b6fdaB4ZsL9LqKFZJI57m9lubJhp6o6JGYZojch2w8UIwzEpSneEd9OtjOPJDV7el/yP2V+Hv7VvhvxLrhcftfaTHYl5CkWpXfhhHwJJFCsBBG4OAp6Y57cVjfAL9rHwj4Y/Zx+HMM37YXwb0KSPw7pkLWep3OjF4X+zQp5J/wBJjbcGIXB5yQOtfLn7FH/BbZpPA0eqN8N/EXgLVoI7u5utD8f6nqVrDeRxh5B/Z15baNLFdyNiSMwlIpt6/LG6q7pa0T/gtFefD79kf4e3uo6h8CdBsdA8N2S2clz8ablL7Xo47GMFhpy6FJM8i5IKhSySoACzqm6fehG1rtFq0pXvZH4L/sdftNWH7OOo61f65pt94g0W7sWsY9OsPEUmky3N23EMjT2zrcxLFG9w6tGrK0gjjcFJHFdR8ef2620zXZtY+EPi/wCLWh3HisTy+JLbxFqqXVxb3MgdHe0voPLdoZI3QFJIw6vACXkDkD550T4a61qHmC3jhZmwCd3T9K0JPgT4vZvlsvM3DGVfj9a6oymlaK179TCXI37z07dD6k+B3/BT/VPF+ixt8dvGnxw+JMvhXVDqnhfQtP8AEUVnZ3FxPb/Zp5r69niuLhsQBo4lEbGP7ROysm+RJfIf21v2pNU/a++KknjTVJLiBZ4I9IhtL3xFNq1zp0NuCY4fOu5ZLuaNVkAEk5JJ3ruYoSeb8IfslfEDxEFa2t7W3C95p9nqewNaXxW/Z48caV4b0e21OOzm/s8zKskV2824OwY/KVG0jpkZJGAeAMaSjVlG8l87av5mcalGMrRa9L6L0Wx//9k=",
"id": 10,
"createdAt": "2018-10-16T05:45:42.4644513",
"updatedAt": "2018-10-16T05:45:42.4645292"
},
"recipes": null,
"nick": "MadBear123",
"login": 10458175107962595193,
"salt": "Fv/S1pnpu1u0RA6RxE1wfwCmqhbkb0Fu0W2sOuFgv//PHyizyPtmuaX8OtYkCgSJPlKMGmE2qFgg2rgs70Ee9bbMU26iVhtIApqV/Zxac54P9EXBvgkAXede3YHzSPzHkvGz3WchUUDIQqHF+EmdvPT9KuYR1Djgywxh0bDbSJk=",
"passwordHash": "Yk5S3jutaKJpwSQoRH0nk2At3nYL/Wzi+8QGRFOZuByi54o+YJHuhPYRMMSG3Vmimv1UMRWe+VA8ym2xQxoEJA==",
"role": "user",
"restoreKey": "Vub!#g17#kcP",
"id": 16,
"createdAt": "2018-10-14T11:11:43.9902857",
"updatedAt": "2018-10-14T11:11:43.990384"
}
Or something similar. I tried to work my way with Include() and (or without) virtual but I ended-up with this.
UPDATE
Definition for GetById():
public static IQueryable<User> GetById(this IQueryable<User> value,int id)
=> value.Where(x => x.Id == id);
You can find full project on GitHub. I'm working on branch MB#20.
For looping reference, you could not control it in EF Core.
For general handling, we configure it in Startup.cs like
.AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json
.ReferenceLoopHandling.Ignore;
options.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver();
});
And usecase:
var user = await _userService.GetAsync(id);
return Ok(user);
For your issue, you modified user before return by return Ok(user.ShapeData(fields)); which made JsonSerialize fail to know its the loop reference.
For a workaround, before converting to ExpandoObject, handle the loop reference like
public static ExpandoObject ShapeData<TSource>(this TSource source, string fields)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
var result =JsonConvert.SerializeObject(source, new JsonSerializerSettings{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
} );
source = JsonConvert.DeserializeObject<TSource>(result);
var dataShapedObject = new ExpandoObject();
//your rest code
return dataShapedObject;
}
Update
For null in UserImage, it is caused by you specify the properties as public string ImageContent { get; protected set; }.
Try to custom DefaultContractResolver to deserialize the protected properties.
IncludePrivateStateContractResolver.cs
public class IncludePrivateStateContractResolver : DefaultContractResolver
{
protected override List<MemberInfo> GetSerializableMembers(Type objectType)
{
const BindingFlags BindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
var properties = objectType.GetProperties(BindingFlags);//.Where(p => p.HasSetter() && p.HasGetter());
var fields = objectType.GetFields(BindingFlags);
var allMembers = properties.Cast<MemberInfo>().Union(fields);
return allMembers.ToList();
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var prop = base.CreateProperty(member, memberSerialization);
if (!prop.Writable)
{
var property = member as PropertyInfo;
if (property != null)
{
prop.Writable = property.HasSetter();
}
else
{
var field = member as FieldInfo;
if (field != null)
{
prop.Writable = true;
}
}
}
if (!prop.Readable)
{
var field = member as FieldInfo;
if (field != null)
{
prop.Readable = true;
}
}
return prop;
}
}
public static class TypeExtensions
{
public static bool HasSetter(this PropertyInfo property)
{
//In this way we can check for private setters in base classes
return property.DeclaringType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
.Any(m => m.Name == "set_" + property.Name);
}
public static bool HasGetter(this PropertyInfo property)
{
//In this way we can check for private getters in base classes
return property.DeclaringType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
.Any(m => m.Name == "get_" + property.Name);
}
}
usecase:
public static ExpandoObject ShapeData<TSource>(this TSource source, string fields)
{
var soureType = source.GetType();
if (source == null)
{
throw new ArgumentNullException("source");
}
var serializeSettings = new JsonSerializerSettings{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
var deserializeSettings = new JsonSerializerSettings{
ContractResolver = new IncludePrivateStateContractResolver()
};
var result =JsonConvert.SerializeObject(source, serializeSettings );
var castType = JsonConvert.DeserializeObject<TSource>(result, deserializeSettings);
var dataShapedObject = new ExpandoObject();
I managed to fix my problem. I changed all private setters in models properties to public an used Tao Zhou original answer.
My ShapeData method:
public static ExpandoObject ShapeData<TSource>(this TSource source, string fields)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
var result = JsonConvert.SerializeObject(source, new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
source = JsonConvert.DeserializeObject<TSource>(result);
var dataShapedObject = new ExpandoObject();
// rest of my code
}
User:
public class User : Account
{
public UInt64 UserEmail { get; set; }
public UserImage UserImage { get; set; }
public virtual ICollection<Recipe> Recipes { get; set; }
// rest of the code
}
UserImage:
public class UserImage : Image
{
public int? UserRef { get; set; }
public virtual User User { get; set; }
public UserImage() : base() { }
public UserImage(string content) : base(content) { }
}
Example of the returned data:
{
"userEmail": 2606810040825320252,
"userImage": {
"userRef": 16,
"user": null,
"imageContent": "/9j/4AAQSkZJRgABAQEASABIAAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAAgACADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDjZ/jPZ6rHocDfFz4iNI19b3BU+FdFjYiEho2/5A6l8Mi53Eg4bIbkj3Sx/bi0/T/2dde0CP8AaL1xdvhK+i+w3EeiQCeR4r7bA2NMj3rtnLhScEyLtJZRt+a/2fH8S/Ha5t77wn4x+Hfiqy0q8neCHRrW8vL2dTpuq3bhbeOOWbcqQNshdDNI8kaRqzAkYXiT9tDxF8DLbxlp+peE9b8davp8b6fdaB4ZsL9LqKFZJI57m9lubJhp6o6JGYZojch2w8UIwzEpSneEd9OtjOPJDV7el/yP2V+Hv7VvhvxLrhcftfaTHYl5CkWpXfhhHwJJFCsBBG4OAp6Y57cVjfAL9rHwj4Y/Zx+HMM37YXwb0KSPw7pkLWep3OjF4X+zQp5J/wBJjbcGIXB5yQOtfLn7FH/BbZpPA0eqN8N/EXgLVoI7u5utD8f6nqVrDeRxh5B/Z15baNLFdyNiSMwlIpt6/LG6q7pa0T/gtFefD79kf4e3uo6h8CdBsdA8N2S2clz8ablL7Xo47GMFhpy6FJM8i5IKhSySoACzqm6fehG1rtFq0pXvZH4L/sdftNWH7OOo61f65pt94g0W7sWsY9OsPEUmky3N23EMjT2zrcxLFG9w6tGrK0gjjcFJHFdR8ef2620zXZtY+EPi/wCLWh3HisTy+JLbxFqqXVxb3MgdHe0voPLdoZI3QFJIw6vACXkDkD550T4a61qHmC3jhZmwCd3T9K0JPgT4vZvlsvM3DGVfj9a6oymlaK179TCXI37z07dD6k+B3/BT/VPF+ixt8dvGnxw+JMvhXVDqnhfQtP8AEUVnZ3FxPb/Zp5r69niuLhsQBo4lEbGP7ROysm+RJfIf21v2pNU/a++KknjTVJLiBZ4I9IhtL3xFNq1zp0NuCY4fOu5ZLuaNVkAEk5JJ3ruYoSeb8IfslfEDxEFa2t7W3C95p9nqewNaXxW/Z48caV4b0e21OOzm/s8zKskV2824OwY/KVG0jpkZJGAeAMaSjVlG8l87av5mcalGMrRa9L6L0Wx//9k=",
"id": 10,
"createdAt": "2018-10-16T05:45:42.4644513",
"updatedAt": "2018-10-16T05:45:42.4645292"
},
"recipes": null,
"nick": "MadBear123",
"login": 10458175107962595193,
"salt": "Fv/S1pnpu1u0RA6RxE1wfwCmqhbkb0Fu0W2sOuFgv//PHyizyPtmuaX8OtYkCgSJPlKMGmE2qFgg2rgs70Ee9bbMU26iVhtIApqV/Zxac54P9EXBvgkAXede3YHzSPzHkvGz3WchUUDIQqHF+EmdvPT9KuYR1Djgywxh0bDbSJk=",
"passwordHash": "Yk5S3jutaKJpwSQoRH0nk2At3nYL/Wzi+8QGRFOZuByi54o+YJHuhPYRMMSG3Vmimv1UMRWe+VA8ym2xQxoEJA==",
"role": "user",
"restoreKey": "Vub!#g17#kcP",
"id": 16,
"createdAt": "2018-10-14T11:11:43.9902857",
"updatedAt": "2018-10-14T11:11:43.990384"
}

Fetch document by array of value in MONGODB and Spring data?

How to fetch the document by array of value from MONGODB using SPRING DATA.?
My document
{
"id": "5b18fdef89d67e272025f2e3",
"date": "2018-05-10 11:31:37",
"active": true,
"obsolete": false,
"tenant": {
"id": "5ad847e54925fb0fa4424e1a"
},
"plan": {
"id": "5ad7115f7b4152204c86fce6"
},
"log": [
"5b18fdef89d67e272025f2e4"
],
"lineItem": [
"5b18fdef89d67e272025f2e5",
"5b18fdef89d67e2720259899",
"5b18fdef89d67e272025sd5s"
]
}
{
"id": "5b18fdef89d67e272025f232",
"date": "2018-05-12 11:31:37",
"active": true,
"obsolete": false,
"tenant": {
"id": "5ad847e54925fb0fa4424e1a"
},
"plan": {
"id": "5ad7115f7b4152204c86fce6"
},
"log": [
"5b18fdef89d67e272025f23434"
],
"lineItem": [
"5b18fdef89d67e272025f111",
"5b18fdef89d67e2720259222",
"5b18fdef89d67e272025s333"
]
}
I want filter the document by lineItem array. If I give the lineItem value "5b18fdef89d67e2720259222" it will be return document which holds the same lineitem.
Model class
#Document(collection = "trn_inventory")
public class Inventory {
#Id
private String id;
private String date;
private List<String> lineitem;
private String tenant, plan;
private List<String> log;
private boolean active, obsolete;
//getters and setters
}
Repository
#Repository
public interface InventoryRep extends MongoRepository<Inventory, String> {
public List<Inventory> findByLineitemIn(String lineitem);
}
I have done the mistake in Repository. After spend the long time, I found it myself (stupid coder). The problem is parameter of the findByLineitemIn method is String that should be List type.
Here is the corrected Repository...
#Repository
public interface InventoryRep extends MongoRepository<Inventory, String> {
public List<Inventory> findByLineitemIn(List<String> lineitem);
}
How foolish is this.. Isn't it? I think this will helpful for some other coder like me. ;)

Query results in nested DTO

Are nested DTOs in JPQL queries not allowed in Spring Data ? :
#Query("SELECT new test.customresult.CategoryCounter(c.name, "
+ "new test.customresult.CarResult(COUNT(e.category), e.category)"
+ "FROM error e JOIN e.car c "
+ "GROUP BY c.name,e.category")
List<CategoryCounter>countErrors();
Because, I get the following error message when trying to use the above mentioned JPQL query :
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: , near line 1,
I want to return such a JSON file from the JPQL query :
[{
"Car 1":
[{
"count": 1,
"category": "A"
}, {
"count": 2,
"category": "B"
}, {
"count": 0,
"category": "C"
}, {
"count": 0,
"category": "D"
}
]
}, {
"Car 2":
[{
"count": 0,
"category": "A"
}, {
"count": 0,
"category": "B"
}, {
"count": 4,
"category": "C"
}, {
"count": 5,
"category": "D"
}
]
}
]
There is a car table and an error table which contains the categories and a foreign key to the car table.
I wanted to use the nested DTO to represent the desired output :
The "wrapper" DTO
public class CategoryCounter {
private String name;
private CarResult carResult ;
public CategoryCounter (String name, CarResult carResult ) {
this.name= name;
this.carResult = carResult ;
}
public CarResult getCarResult () {
return carResult ;
}
public void setCarResult(CarResult carResult ) {
this.carResult = carResult ;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name= name;
}
}
and the "nested" DTO :
public class CarResult {
private Long count;
private String category;
public CarResult (Long count, String category) {
this.count = count;
this.category= category;
}
public Long getCount() {
return count;
}
public void setCount(Long count) {
this.count = count;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category= category;
}
}
I'm not sure if this could be the problem... but you have your FROM clause starting immediately after the ) it says:
+ "new test.customresult.CarResult(COUNT(e.category), e.category)"
+ "FROM error e JOIN e.car c "
that is the same to: e.category)FROM so put a space in that part and try again...
+ "new test.customresult.CarResult(COUNT(e.category), e.category)"
+ " FROM error e JOIN e.car c "
the thing is that the error (Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException) is a syntax error