Polymorphic deserialization jackson - java

I am trying to implement polymorphic deserialization in jackson and trying to make the same model work in two places.
I have ShopData object
public class ShopData extends Embeddable implements Serializable
{
private final int id;
private final String name;
private final String logoImageUrl;
private final String heroImageUrl;
public ShopData(#JsonProperty(value = "id", required = true) int id,
#JsonProperty(value = "name", required = true) String name,
#JsonProperty(value = "logoImageUrl", required = true) String logoImageUrl,
#JsonProperty(value = "heroImageUrl", required = true) String heroImageUrl)
{
this.id = id;
this.name = name;
this.logoImageUrl = logoImageUrl;
this.heroImageUrl = heroImageUrl;
}
}
My Embeddable object looks like this
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonSubTypes({#JsonSubTypes.Type(value = AnotherObject.class, name = "AnotherObject"),
#JsonSubTypes.Type(value = ShopData.class, name = "shop")
})
public abstract class Embeddable
{
}
I am trying to make this model work in two places. This model works as expected.
public Order(#JsonProperty(value = "_embedded", required = true) Embeddable embedded)
{
this.embedded = (ShopData) embedded;
}
"_embedded": {
"shop": {
"id": 1,
"name": "",
"freshItems": 5,
"logoImageUrl": "",
"heroImageUrl": "",
"_links": {
"self": {
"href": "/shops/1"
}
}
}
While this doesn't
public ShopList(#JsonProperty(value = "entries", required = true) List<ShopData> entries)
{
this.entries = Collections.unmodifiableList(entries);
}
{
"entries": [
{
"id": 1,
"name": "",
"freshItems": 5,
"logoImageUrl": "",
"heroImageUrl": "",
"_links": {
"self": {
"href": "/shops/1"
}
}
}
]
}
And throws error :Could not resolve type id 'id' into a subtype
I understand the error but do not know how to resolve this. I would like to be able to use the same model in both cases. Is this possible?

Just found out the answer myself. Should have simply added this anotation
#JsonTypeInfo(use= JsonTypeInfo.Id.NONE)
public class ShopData extends Embeddable implements Serializable

Related

How to identify different types automatically using Jackson mapper

I have a simple situation. I have a main DTO class with the following fields:
AnimalDTO
public class AnimalDTO {
#JsonCreator
public AnimalDTODTO(#JsonProperty("error") boolean error,
#JsonProperty("errorMessage") String errorMessage,
#JsonProperty("cat") CatDTO cat,
#JsonProperty("dog") DogDTO dog) {
this.error = error;
this.errorMessage = errorMessage;
this.cat = cat;
this.dog = dog;
}
private boolean error;
private String errorMessage;
private CatDTO cat;
private DogDTO dog;
}
CatDTO:
public class CatDTO {
#JsonCreator
public CatDTO(#JsonProperty("catNumber") String catNumber,
#JsonProperty("catState") String catState) {
this.catNumber = catNumber;
this.catState = catState;
}
private String catNumber;
private String catState;
}
DogDTO:
public class DogDTO {
#JsonCreator
public DogDTO(#JsonProperty("dogNumber") String dogNumber,
#JsonProperty("dogState") String dogState
#JsonProperty("dogColor") String dogColor
#JsonProperty("dogBalance") BigDecimal dogBalance) {
this.dogNumber = dogNumber;
this.dogState = dogState;
this.dogColor = dogColor;
this.dogBalance = dogBalance;
}
private String dogNumber;
private String dogState;
private String dogColor;
private BigDecimal dogBalance;
}
and from external API I have responses (and I can't change it) for dog like:
{
"dogNumber": "23",
"dogState": "good",
"dogColor": "pink",
"dogBalance": 5
}
and for Cat:
{
"catNumber": "1",
"catState": "good"
}
And I want to use Jackson mapper like this: objectMapper.readValue(stringJson, AnimalDTO.class);
I was thinking to add in AnimalDTO:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = CatDTO.class, name = "cat"),
#Type(value = DogDTO.class, name = "dog")
})
but it's not working.
How to handle my case in best way?
Your code will work just fine (without any #JsonTypeInfo or #JsonSubTypes annotations) if the JSON that you get from the external API is as follows:
{
"dog": {
"dogNumber": "23",
"dogState": "good",
"dogColor": "pink",
"dogBalance": 5
},
"cat": {
"catNumber": "1",
"catState": "good"
}
}
If this does not look similar to the JSON you receive then you need to add it to your answer so that we can help you further.

Jackson polymorphism issue without enableDefaultTyping

I'm facing a weird issue during deserializing of a JSON file that has polymorphic types.
My json file looks like this
{
"name": "abc",
"version": 1,
"fileNames": [
"xyz.abc",
"xyz/abc"
]
"vehicles": [
"java.util.ArrayList",
[
{
"name": "Car"
},
{
"name": "Train"
},
]
]
}
My code looks like:
private static final ObjectMapper OBJECT_MAPPER;
static {
OBJECT_MAPPER = new ObjectMapper();
OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
OBJECT_MAPPER.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
OBJECT_MAPPER.addMixIn(Vehicle.class, VehicleMixIn.class);
}
#JsonCreator
public Config(
#JsonProperty(value = "name", required = true) final String name,
#JsonProperty(value = "version") final Integer version,
#JsonProperty(value = "Vehicles") List<Vehicle> listofVehicles,
#JsonProperty(value = "paths") final List<String> paths, {
fileName = name;
versionNumber = version;
vehicles = listofVehicles;
mPaths = paths;
}
/**
* A JSONMixin helper class to deserialize Vehicles.
*/
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "name")
#JsonSubTypes({
#JsonSubTypes.Type(name = "Car", value = Car.class),
#JsonSubTypes.Type(name = "Train", value = Train.class),
#JsonSubTypes.Type(name = "Bus", value = Bus.class)
})
private abstract class VehicleMixIn {
public VehicleMixIn() {
}
}
The way I read the json file
try (BufferedReader reader = Files.newBufferedReader(configFile.toPath(), StandardCharsets.UTF_8)) {
config = Config.fromJson(reader);
}
I'm unable to read the polymorphic vehicle type.
I get the following error:
com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (VALUE_STRING), expected FIELD_NAME: missing property 'name' that is to contain type id (for class abc.def.Vehicle)
This works if I do OBJECT_MAPPER.enableDefaultTyping(), however then I'm unable to read the 'paths' field. How do I make this mixIn work?
I've tried all types of defaultTyping

Returning a NodeEntity from neo4j doesn't contain relationships or connected nodes

I'm setting up a P.O.C. using Neo4j, and technically have everything I need working but would like it set up properly.
As a quick overview - I can create nodes and relationships, and traverse the graph (i.e. return all features available in a specific market) so I know these nodes/relationships have been created.
However, when I query to simply return a Node based on ID, it returns ONLY the data for that node - and not any relationships or connected nodes, for example, the markets its available in.
I've looked various places online that have not only a Node returned but also the subsequent nodes - though I follow what they're doing I cant seem to get it to work with mine.
Feature Repository:
#Repository
public interface FeatureRepository<T extends Feature> extends Neo4jRepository<T, Long> {
...
}
Colour Repository:
#Repository
public interface ColourRepository extends FeatureRepository<Colour>{
#Query("CREATE(feat:Colour:Feature {marketingDesc:{marketing}, engineeringDesc:{engineering}, code:{code}})")
Colour createColour(#Param("marketing") String marketingDesc, #Param("engineering") String engineeringDesc, #Param("code") String code);
#Query("MATCH (c:Colour {code:{colourCode}}) MATCH (c)-[:AVAILABLE_IN]->(market) RETURN c AS colour, COLLECT(market) AS markets")
Colour getColourByCode(#Param("colourCode") String colourCode);
Colour findByCode(#Param("code") String code);
}
Feature Entity:
#NodeEntity(label = "Feature")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Feature {
#Id
#GeneratedValue
private Long id;
private String marketingDesc;
private String engineeringDesc;
#Index(unique = true)
private String code;
#Relationship(type = "HAS_OPTION", direction = Relationship.INCOMING)
private List<Option> options = new ArrayList<>();
#Relationship(type = "AVAILABLE_IN")
private List<Market> markets = new ArrayList<>();
#Relationship(type = "HAS_PREREQUISITE", direction = Relationship.UNDIRECTED)
private List<Prerequisite> prerequisites = new ArrayList<>();
}
Colour Entity:
#AllArgsConstructor
#NodeEntity(label = "Colour")
public class Colour extends Feature {
}
Market Entity:
#NodeEntity(label = "Market")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Market {
#Id
#GeneratedValue
private Long id;
#Index(unique = true)
private String code;
private String market;
#Relationship(type = "AVAILABLE_IN", direction = Relationship.INCOMING)
private List<Option> features = new ArrayList<>();
}
Relationship Entity (for features to be connected to markets they can be bought in):
#RelationshipEntity(type = "AVAILABLE_IN")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Available {
#Id
#GeneratedValue
private Long Id;
private List<String> availableIn = new ArrayList<>();
#StartNode
private Feature feature;
#EndNode
private Market market;
}
Controller:
#RestController
public class ConfigController {
private final Handler configHandler;
public ConfigController(Handler configHandler) {
this.configHandler = configHandler;
}
#PostMapping(path = "/create/colour", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public SimpleResponse createColour(#RequestBody Colour request) {
ColourService service = new ColourService(configHandler);
Colour created = service.createColour(request);
return SimpleResponse.builder().result("Created:", created).build();
}
#PostMapping(path = "/create/market", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public SimpleResponse createMarket(#RequestBody Market request) {
MarketService service = new MarketService(configHandler);
Market created = service.createMarket(request);
return SimpleResponse.builder().result("Created", created).build();
}
#PostMapping(path = "/create/relationship/availableIn", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public SimpleResponse createAvailableInRelationship(#RequestBody OptionAvailInRequest request){
RelationshipService service = new RelationshipService(configHandler);
Object result = service.createAvailableInRelationship(request);
return SimpleResponse.builder().result("Result:", result).build();
}
#GetMapping(path = "/colour/{code}")
public SimpleResponse getColourByCode(#PathVariable(value = "code") String code) {
ColourService service = new ColourService(configHandler);
Colour colour = service.getColourByCode(code);
return SimpleResponse.builder().result("Colour:", colour).build();
}
#GetMapping(path = "/features/available/{mrktCode}")
public SimpleResponse getFeaturesInMarket(#PathVariable(value = "mrktCode") String mrktCode){
RelationshipService service = new RelationshipService(configHandler);
Collection<Feature> features = service.getFeaturesInMarket(mrktCode);
return SimpleResponse.builder().result("Features:", features).build();
}
}
Neo4jConfig file:
#Configuration
#EnableNeo4jRepositories(basePackages = "package.location")
#EnableTransactionManagement
public class Neo4jConfig {
#Bean
public org.neo4j.ogm.config.Configuration configuration() {
org.neo4j.ogm.config.Configuration configuration =
new org.neo4j.ogm.config.Configuration.Builder().build();
return configuration;
}
#Bean
public SessionFactory sessionFactory(org.neo4j.ogm.config.Configuration configuration) {
return new SessionFactory(configuration,"package.location");
}
#Bean
public Neo4jTransactionManager transactionManager(SessionFactory sessionFactory) {
return new Neo4jTransactionManager(sessionFactory);
}
}
So, for example, here I can create a Colour Node:
Example value:
{
"code": "string",
"engineeringDesc": "string",
"id": 0,
"marketingDesc": "string",
"markets": [
{
"code": "string",
"features": [
{}
],
"id": 0,
"market": "string"
}
],
"options": [
{}
],
"prerequisites": [
{}
]
}
What I send:
{
"code": "BLU",
"engineeringDesc": "Blue engineering",
"marketingDesc": "Blue marketing"
}
And this creates a Colour Node successfully:
{
"result": {
"Created:": {
"id": 0,
"marketingDesc": "Blue marketing",
"engineeringDesc": "Blue engineering",
"code": "BLU",
"options": [],
"markets": [],
"prerequisites": []
}
},
"error": null
}
I can create a Market Node:
Example Value:
{
"code": "string",
"features": [
{}
],
"id": 0,
"market": "string"
}
What I send:
{
"code": "UB",
"market": "England"
}
Which creates a Market Node successfully:
{
"result": {
"Created": {
"id": 1,
"code": "UB",
"market": "England",
"features": []
}
},
"error": null
}
I can then create a relationship between the two, to say that colour is available in that market:
{
"featureCode": "BLU",
"marketCode": "UB"
}
Which I can verify has been created by hitting:
localhost:8080/features/available/UB
{
"result": {
"Features:": [
{
"id": 0,
"marketingDesc": "Blue marketing",
"engineeringDesc": "Blue engineering",
"code": "BLU",
"options": [],
"markets": [],
"prerequisites": []
}
]
},
"error": null
}
However when I then go to return the Colour Node itself:
localhost:8080/colour/BLU
{
"result": {
"Colour:": {
"id": 0,
"marketingDesc": "Blue marketing",
"engineeringDesc": "Blue engineering",
"code": "BLU",
"options": [],
"markets": [],
"prerequisites": []
}
},
"error": null
}
The 'markets' option is always null. I have tried custom queries and building queries using the neo4j helper (e.g. findByCode etc.), and every example I can find will sucessfully return the related nodes, but I cant seem to get mine to.
Can anyone help?
P.S. Please let me know if there is anything else that would be helpful for you to see. Been trying to get this sorted for days....
Got the answer to this question...
Feature Entity should have been:
#Relationship(type = "AVAILABLE_IN")
#ApiModelProperty(hidden = true)
private Set<Available> markets = new HashSet<>();
Market Entity should have been:
#Relationship(type = "AVAILABLE_IN", direction = Relationship.INCOMING)
#ApiModelProperty(hidden = true)
private Set<Available> features = new HashSet<>();
Which gets the markets section of the feature JSON no longer null...
Now I have the problem that there's an infinite recursion loop between the two classes, with a feature displaying the markets and the markets displaying the features
EDIT:
For anyone else with this/similar issues, I've found a really good github resource.
GitHub neo4j ogm walkthrough
Helped a lot.

How to set JsonProperty value dynamically

I want to create a json request like below:
"additionalData": {
"riskdata.basket.item1.sku": "045155",
"riskdata.basket.item1.quantity": "1"
"riskdata.basket.item2.sku": "0451166",
"riskdata.basket.item2.quantity": "1"
...
"riskdata.basket.item4.sku": "0451111",
"riskdata.basket.item4.quantity": "2"
Please suggest how to set the JsonProperty value dynamically in the object mapping.
Example: deliveryMethod is a constant field hence I am able to map like below using JsonProperty annotation. However, how I can use the JsonProperty for sku and quantity so that it will accept as many number as possible. Any suggestion would be helpful.
public class AdditionalData implements java.io.Serializable
{
#JsonProperty(value = "riskdata.deliveryMethod")
private String deliveryMethod;
#JsonProperty(value = "riskdata.basket.item??.sku")
private String sku;
#JsonProperty(value = "riskdata.basket.item??.quantity")
private String quantity;
}
You can create a basket[] array property in your AdditionalDataclass.
public class AdditionalData implements java.io.Serializable
{
#JsonProperty(value = "riskdata.deliveryMethod")
private String deliveryMethod;
#JsonProperty(value = "riskdata.basket")
private Basket[] basket;
}
public class Basket implements java.io.Serializable
{
#JsonProperty(value = "sku")
private String sku;
#JsonProperty(value = "quantity")
private String quantity;
}
And the change your json structure like this:
"additionalData": {
"riskdata.basket": [
{
"sku": "045155",
"quantity": 1"
},
{
"sku": "045156",
"quantity": 1"
}]
}

Convert ObjectNode to Java Object using Jackson

I have a json:
{
"response": {
"GeoObjectCollection": {
"featureMember": [
{
"GeoObject": {
"description": "Country",
"name": "City",
"Point": {
"pos": "31.992615 45.057626"
}
}
},
{
"GeoObject": {
"description": "Country",
"name": "City",
"Point": {
"pos": "49.242414 49.895935"
}
}
}
]
}
}
}
I created DTO:
GeographicCoordinateDto.java:
#Data
#JsonIgnoreProperties(ignoreUnknown = true)
public class GeographicCoordinateDto {
#JsonProperty("description")
private String location;
#JsonProperty("name")
private String cityName;
#JsonProperty("Point")
private GeographicCoordinatesDto geoCoordinates;
}
GeographicCoordinatesDto.java:
#Data
#JsonIgnoreProperties(ignoreUnknown = true)
public class GeographicCoordinatesDto {
#JsonProperty("pos")
private String geoCoordinates;
}
Then I get JsonNode:
List<JsonNode> responseArrayOfObjects = mapper.readValue(new URL(yandexGeoCoderRestUrl+address), ObjectNode.class).findValues("GeoObject");
And I'm trying to convert to my DTO:
GeographicCoordinatesDto geo = mapper.convertValue(responseArrayOfObjects.get(0), GeographicCoordinatesDto.class);
But, I've null object:
GeographicCoordinatesDto(geoCoordinates=null)
What could be wrong?
UPDATE:
responseArrayOfObjects contains:
You are trying to get pos from the GeographicCoordinatesDto object, but it is inside the Point object of GeographicCoordinatesDto.
You can do this instead:
List<JsonNode> responseArrayOfObjects = mapper.readValue(new URL(yandexGeoCoderRestUrl+address), ObjectNode.class).findValues("Point");
or create another class for Point:
#JsonIgnoreProperties(ignoreUnknown = true)
class Point {
#JsonProperty("pos")
private String geoCoordinates;
}
and use it in GeographicCoordinatesDto:
#JsonIgnoreProperties(ignoreUnknown = true)
class GeographicCoordinatesDto {
#JsonProperty("Point")
private Point point;
}

Categories

Resources