Deserialize Map key as POJO reference - java

Is it possible in Jackson to get a reference to a previously-deserialized object from the same json string given it's id?
For example, I have the following Java classes:
public class Company {
List<Employee> employeeList;
List<Customer> customerList;
....
}
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Customer {
Long id;
...
}
public class Employee {
Long id;
Map<Customer, int> utilityMap;
...
}
I want to create a Company instance from a json string that looks like:
{
"customerList" : [ {
"id" : 0,
"name" : "customer0"
} ],
"employeeList" : [ {
"id" : 1,
"fullName" : "employee0",
"utilityMap" : {
"0" : 1
}
} ]
}
where the key 0 in utilityMap refers to the customer with id 0.
With KeyDeserializer I'm able to create a new Customer object, but I want the Map's key to be a reference to the corresponding object instead.
A simple solution would be to change the signature of Map<Customer, int> to be Map<Long, int>, but this not possible due to internal dependencies, is there another way to accomplish this without changing the model (similar to xml references)?

Related

Polymorphic model serialization using Jackson

I have to send request to a third party service where the request JSON looks similar to below. There will be another api which has the same exact JSON format as the response. So I need to build object model that will be able to successfully serialize to/ deserialize from this JSON format.
Sample json
{
"name": "Johnny",
"vehicles": [{
"vehicleType": "car",
"vehicleInfo": {
"maxSeatCount": 2,
"coupe": true
}
}, {
"vehicleType": "truck",
"vehicleInfo": {
"towingCapacity": "3000lb",
"bedLength": "6ft"
}
}]
}
Here are the POJOs I created to meet the above model.
PersonInfo.java
public class PersonInfo {
private String name;
private List<Vehicle> vehicles;
}
Vehicle.java
public class Vehicle {
private String vehicleType;
private VehicleInfo vehicleInfo;
}
VehicleInfo.java
#JsonTypeInfo(use = Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "vehicleType")
#JsonSubTypes({
#Type(value = CarInfo.class, name="car"),
#Type(value = TruckInfo.class, name="truck")
})
public abstract class VehicleInfo {
}
CarInfo.java
public class CarInfo extends VehicleInfo{
private int maxSeatCount;
private boolean coupe;
}
TruckInfo.java
public class TruckInfo extends VehicleInfo{
private String towingCapacity;
private String bedLength;
}
I'm running into two problems with this model. During serialization, the JSON generated has the attribute vehicleType inside vehicleInfo object as well. It should not be.
JSON generated using above model.
{
"name" : "Johnny",
"vehicles" : [ {
"vehicleType" : "car",
"vehicleInfo" : {
"vehicleType" : "car", // this shouldn't be here
"maxSeatCount" : 2,
"coupe" : false
}
}, {
"vehicleType" : "truck",
"vehicleInfo" : {
"vehicleType" : "truck", // this shouldn't be here
"towingCapacity" : "3000lb",
"bedLength" : "6ft"
}
} ]
}
Second issue is that during deserialization, Jackson is complaining that it doesn't see the vehicleType attribute in vehicleInfo type.
Exception in thread "main"
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not
resolve subtype of [simple type, class
com.kodakandla.file.VehicleInfo]: missing type id property
'vehicleType' (for POJO property 'vehicleInfo') at [Source:
(String)"{"name":"Johnny","vehicles":[{"vehicleType":"car","vehicleInfo":{"maxSeatCount":2,"coupe":true}},{"vehicleType":"truck","vehicleInfo":{"towingCapacity":"3000lb","bedLength":"6ft"}}]}";
line: 1, column: 95] (through reference chain:
com.kodakandla.file.PersonInfo["vehicles"]->java.util.ArrayList[0]->com.kodakandla.file.Vehicle["vehicleInfo"])
at
com.fasterxml.jackson.databind.exc.InvalidTypeIdException.from(InvalidTypeIdException.java:43)
at
com.fasterxml.jackson.databind.DeserializationContext.missingTypeIdException(DeserializationContext.java:2083)
at
com.fasterxml.jackson.databind.DeserializationContext.handleMissingTypeId(DeserializationContext.java:1596)
What changes do I need to make for this to work?
I figured out what was wrong with my POJOs. Thought I would post the answer just in case if anyone else runs into similar issue.
I have the #JsonTypeInfo annotation in the wrong place. Instead of setting it at the class level in the VehicleInfo class, I had to set it at the field level in the Vehicle class.
public class Vehicle {
private String vehicleType;
#JsonTypeInfo(use = Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "vehicleType")
#JsonSubTypes({
#Type(value = CarInfo.class, name="car"),
#Type(value = TruckInfo.class, name="truck")
})
private VehicleInfo vehicleInfo;
}
Now the serialization and deserialization are working as expected.

How to return list of specific fields in query Java Spring Mongo Repository?

I use Java spring and MongoDB repository in my project.
Here is DTO definition:
#Document(collection = "Info")
public class Info {
String id,
Integer count,
String type
…
}
I need to return from the query a list of IDs where the count field not zero and type filed has 'binary' text.
Here how I try to implement it:
#Query(value="{ 'count' : 0, 'type' : 'binary' }", fields="{ 'id' : 1 }")
List<String> getInfo();
I get this result from query above:
0={"_id": {"$oid": "5eb97a8139d4c62be4d90e4c"}}
1={"_id": {"$oid": "3ec97a8127d4c60cb4d90e9e"}}
And I expect this result:
{"5eb97a8139d4c62be4d90e4c", "3ec97a8127d4c60cb4d90e9e"}
So as you can see I expect to get a list of id strings from the query above.
Any idea what should I change in the query above to get the expected list of ids results?
No, that's impossible what you are thinking.
Reason : MongoDB can only return JSON Document. You can include the fields that you want to be there.
This suggestion that you can follow :
DTO Definition :
#Document(collection = "Info")
public class Info {
#Id
private String id;
private Integer count;
private String type;
// other fields and getters and setters
}
Sample Repository :
public interface InfoRepository extends MongoRepository<Info, String> {
#Query(value="{ 'count' : 0, 'type' : 'binary' }", fields="{ 'id' : 1 }")
List<Info> getInfo();
}
Sample Service class :
#Service
public class InfoService {
#Autowired
private InfoRepository infoRepository;
public List<String> getIds() {
return infoRepository.getInfo()
.stream()
.map(Info::getId)
.collect(Collectors.toList());
}
}
The returned {"$oid": "5eb97a8139d4c62be4d90e4c"} is the MongoDB Extended JSON representation of an ObjectID.
It is not returning a string because the field stored in the database is of type ObjectID, not type String.
If you want it to return a string, you should use aggregation with the $toString operator to convert it.
the best you can get back is a document with an array field with the found ids like this:
{
"ids" : [
"606018909fb6351e4c34f964",
"606018909fb6351e4c34f965"
]
}
which can be achieved with an aggregation query like so:
db.Info.aggregate([
{
$match: {
count: 0,
type: "binary"
}
},
{
$project: { _id: { $toString: "$_id" } }
},
{
$group: {
_id: null,
ids: { $push: "$_id" }
}
},
{
$project: { _id: 0 }
}
])
I have 2 suggestions.
1. You can use JPA query instead of named query
public interface InfoRepository extends MongoRepository<Info, String> {
List<Info> findByCountAndType(final Integer count, final String type);
}
2. Use java stream api in your business logic to collect all id as List from the above result.
public class InfoServiceImpl {
#Autowired
private InfoRepository repository;
public String getIds(final String type, final Integer count) {
return repository.findByCountAndType(count, type)
.stream()
.map(Info::getId)
.collect(Collectors.toList());
}

Is it possible to group JSON fields into one property using Jackson?

I'm wondering if there is any way to deserialize several JSON fields to just one Java property. E.g. given this JSON:
{
"id" : "1",
"name" : "Bartolo",
"address" : "whatever",
"phone" : "787312212"
}
deserialize it to this class:
public class Person {
public String id;
public String name:
#JsonProperty(names = {"address", "phone"}) //something like this
public String moreInfo;
}
so moreInfo equals to "whatever, 787312212" or something similar.
Is this possible without using custom deserializer?
You could use the #JsonCreator annotation like following:
String json = {"id" : "1", "name" : "Bartolo", "address" : "whatever", "phone" : "787312212" }
ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue(json , Person.class);
and in the constructor of your Person class add this
#JsonCreator
public Person(#JsonProperty("address") String address, #JsonProperty("phone") String phone) {
this.moreInfo = address + "," phone;
}
Another solution, if you don't want to know/handle other fields in the object, but decided to still receive these fields (maybe for logging purposes), then you can put them in a key-value store(Map)
#Getter
private final Map<String, Object> otherFields = new HashMap<>();
#JsonAnySetter
public void set(String name, Object value) {
otherFields.put(name, value);
}
Note that if you have any field with the same name as the Map field(like 'otherFields' in the example above), then you can get MismatchedInputException

Deserialization of Object serialized by #JsonIdentityInfo

I am having my User class annotated like this to remove cyclic format of output:
#Entity
#Table(name = "USER")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = User.class)
public class User extends AbstractValueObject {
private Integer id;
private String name;
.....
}
public class Load extends AbstractValueObject {
private Integer id;
private User postedBy;
}
So whenever i fetch List of Load it is giving me output as below JSON :
[
{
"id" : 1,
"postedBy" : {
"id":1,
"name":"SOF"
}
},
{
"id" : 2,
"postedBy" : 1
}
]
But client side wants it in original format - say each object of load should contain full postedBy object. Client side is in Android - Java.
Is there any way at Android end to de-serialize object in original format at Android ?
Expected output :
[
{
"id" : 1,
"postedBy" : {
"id":1,
"name":"SOF"
}
},
{
"id" : 2,
"postedBy" : {
"id":1,
"name":"SOF"
}
}
]
I tried with JSOG but in some cases it fails.
Any help will be appreciated. :)
You can use Jsog Converter, which provide library to encode and decode objects.
https://github.com/jsog/jsog

Field “ClassName” is inserted into mongodb by morphia

I want to save complex objects as a property of another object:
#Entity(noClassnameStored = true)
public class User {
......
#Embedded
public Map<String, List<Order>> orders;
......
}
#Embedded
public class Order {
String productName;
String description;
..........
}
Than map is set in code using setter:
Map<String, List<Order>> ordersMap = new HashMap<>();
ordersMap.put(...);
user.setOrders(ordersMap);
But in document 'User' every Order is saved with field 'className' in map 'orders' :
"1" :[ {"className": "com.domain.Order",
"productName" : "Milk",
"description": "Fresh"
}
]
What is needed to do to not save field 'className' for Order inside ordersMap?

Categories