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
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.
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());
}
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
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
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?