I want to consume a json with jax-rs my method stamp look like that.
#PostMapping("/test")
public ResponseEntity<String> consumeJson(#RequestBody TestPojo testPojo)
My json look like that
{
"code": "<code>",
"display": "<display>",
"activities": [
{
"categoryCode": "drug",
"drugDisplay" : "Ceforanide"
},{
"categoryCode": "observation",
"measurementWeight" : "80kg",
}
]
}
And i have the following pojos
public class TestPojo implements Serializable{
private String code;
private String display;
private List<ActivityPojo> activities;
// Getters & Setters
}
Now i have a super class and couple of classes inherit from it
public class ActivityPojo implements Serializable{
private String categoryCode;
}
The child classes
public class DrugPojo extends ActivityPojo implements Serializable{
private String drugDisplay;
// Getters & Setters
}
public class ObservationPojo extends ActivityPojo implements Serializable{
private String measurementWeight;
// Getters & Setters
}
Inside my webservice method i want to do something like that
List<ActivityPojo> activities = testPojo.getActivities();
for(int i = 0; i < activities.size(); i++){
if( activities.get(i) instanceof DrugPojo){
// do stuff
}
else if( activities.get(i) instanceof ObservationPojo){
// do stuff
}
}
So can polymorphically serialize my json in order to do that. Any help would be appreciated.
This question is very interresting so I did a few tests.
If I understood correctly the problem, I think this class (and the inner one) can solve it :
#Component
#SuppressWarnings("serial")
public class ActivityPojoJsonModule extends SimpleModule {
public ActivityPojoJsonModule() {
this.addDeserializer(ActivityPojo.class, new ActivityPojoDeserializer());
}
public static class ActivityPojoDeserializer extends JsonDeserializer<ActivityPojo> {
#Override
public ActivityPojo deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
ObjectCodec codec = parser.getCodec();
JsonNode node = codec.readTree(parser);
if(this.isDrug(node)) {
return codec.treeToValue(node, DrugPojo.class);
}
return codec.treeToValue(node, ObservationPojo.class);
}
private boolean isDrug(JsonNode node) {
return node.get("categoryCode").asText().equals("drug");
}
}
}
It adds a component to the Spring context that will deserialize ActivityPojo with a logic based on the value of the field categoryCode. You just have to add this class in the a scanned package and it will override the default behaviour of Jackson.
Related
I have this JSON that I want to parse:
{
"name": "john",
}
I must use the following hierarchy. The classes are immutable and I must access them by the static factory method (this is imperative, so it makes no sense to suggest modifications to either Name or Person).
class Name {
static Name valueOf(String name) {...}
private Name() {}
String name();
}
class Person {
static Person create(Name name) {...}
private Person() {...}
Name name();
}
To that end, I want to deserialize a Person with Jackson, so I wrote this:
public class NameJsonDeserializer extends JsonDeserializer<Name> {
#Override public Name deserialize(JsonParser parser, DeserializationContext context) {
var tree = parser.getCodec().readTree(parser);
var name = tree.asToken().asString();
return Name.valueOf(name);
}
}
public class PersonJsonDeserializer extends JsonDeserializer<Person> {
#Override public Person deserialize(JsonParser parser, DeserializationContext context) {
var tree = parser.getCodec().readTree(parser);
var name = (ObjectNode) tree.get("name");
return Person.create(name);
}
}
But of course, this doesn't work. It doesn't even compile.
I know I can write something similar to the following, but Name is used all over the place, and not always within a Person, so I really need a separate deserializer for Name.
var tree = parser.getCodec().readTree(parser);
var name = (TextNode) tree.get("name");
return Person.create(Name.valueOf(name.asText()));
How can I deserialize without having recourse to intermediary POJOs?
I must use parser.getValueAsString() for NameDeserializer and codec.treeToValue() for PersonDeserializer:
public class NameJsonDeserializer extends JsonDeserializer<Name> {
#Override public Name deserialize(JsonParser parser, DeserializationContext context) {
var name = parser.getValueAsString();
return Name.valueOf(name);
}
}
public class PersonJsonDeserializer extends JsonDeserializer<Person> {
#Override public Person deserialize(JsonParser parser, DeserializationContext context) {
var codec = parser.getCodec();
var tree = codec.readTree(parser);
var name = codec.treeToValue(tree.get("name"), Name.class);
return Person.create(name);
}
}
If you can modify Person, you could use #JsonCreator:
#JsonCreator
public static Person create(#JsonProperty("name") String name) {
Name nameInstance = Name.valueOf(name);
return new Person(nameInstance);
}
This will consume that JSON properly, mapping the "name" property to the first param of the method.
If you cannot modify the class you can use the mixin approach, creating the mixin:
interface PersonMixin {
#JsonCreator
public static Person create(#JsonProperty("name") Name name) {return null;}
}
and register it in the mapper:
mapper.addMixIn(Person.class, PersonMixin.class);
I'd like to deserialize an object from YAML with the following properties, using Jackson in a Spring Boot application:
Abstract class Vehicle, implemented by Boat and Car
For simplicity, imagine both have a name, but only Boat has also a seaworthy property, while Car has a top-speed.
mode-of-transport:
type: boat
name: 'SS Boatface'
seaworthy: true
----
mode-of-transport:
type: car`
name: 'KITT'
top-speed: 123
This all works fine in my annotated subclasses using #JsonTypeInfo and #JsonSubTypes!
Now, I'd like to create a shorthand using only a String value, which should create a Car by default with that name:
mode-of-transport: 'KITT'
I tried creating my own custom serializer, but got stuck on most of the relevant details. Please help me fill this in, if this is the right approach:
public class VehicleDeserializer extends StdDeserializer<Merger> {
/* Constructors here */
#Override
public Vehicle deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
if (/* it is an OBJECT */){
// Use the default polymorphic deserializer
} else if (/* it is a STRING */) {
Car car = new Car();
car.setName( /* the String value */ );
return car;
}
return ???; /* what to return here? */
}
}
I found these 2 answers for inspiration, but it looks like combining it with polymorphic types makes it more difficult: How do I call the default deserializer from a custom deserializer in Jackson and Deserialize to String or Object using Jackson
A few things are different than the solutions offered in those questions:
I am processing YAML, not JSON. Not sure about the subtle differences there.
I have no problem hardcoding the 'default' type for Strings inside my Deserializer, hopefully making it simpler.
This was actually easier than I thought to solve it. I got it working using the following:
Custom deserializer implementation:
public class VehicleDeserializer extends StdDeserializer<Vehicle> {
public VehicleDeserializer() {
super(Vehicle.class);
}
#Override
public Vehicle deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
if (jp.currentToken() == JsonToken.VALUE_STRING) {
Car car = new Car();
car.setName(jp.readValueAs(String.class));
return car;
}
return jp.readValueAs(Vehicle.class);
}
}
To avoid circular dependencies and to make the custom deserializer work with the polymorphic #JsonTypeInfo and #JsonSubTypes annotations I kept those annotations on the class level of Vehicle, but put the following annotations on the container object I am deserializing:
public class Transport {
#JsonDeserialize(using = VehicleDeserializer.class)
#JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
private Vehicle modeOfTransport;
// Getter, setters
}
This means that by default a Vehicle is deserialized as a polymorphic object, unless explicitly specified to deserialize it using my custom deserializer. This deserializer will then in turn defer to the polymorphism if the input is not a String.
Hopefully this will help someone running into this issue :)
So there is a solution that requires you to handle the jackson errors using a DeserializationProblemHandler (since you want to parse the same type using different inputs, this is not achieved easily using regular means):
public class MyTest {
#Test
public void doTest() throws JsonParseException, JsonMappingException, IOException {
final ObjectMapper om = new ObjectMapper();
om.addHandler(new DeserializationProblemHandler() {
#Override
public Object handleMissingInstantiator(final DeserializationContext ctxt, final Class<?> instClass, final JsonParser p, final String msg) throws IOException {
if (instClass.equals(Car.class)) {
final JsonParser parser = ctxt.getParser();
final String text = parser.getText();
switch (text) {
case "KITT":
return new Car();
}
}
return NOT_HANDLED;
}
#Override
public JavaType handleMissingTypeId(final DeserializationContext ctxt, final JavaType baseType, final TypeIdResolver idResolver, final String failureMsg) throws IOException {
// if (baseType.isTypeOrSubTypeOf(Vehicle.class)) {
final JsonParser parser = ctxt.getParser();
final String text = parser.getText();
switch (text) {
case "KITT":
return TypeFactory.defaultInstance().constructType(Car.class);
}
return super.handleMissingTypeId(ctxt, baseType, idResolver, failureMsg);
}
});
final Container objectValue = om.readValue(getObjectJson(), Container.class);
assertTrue(objectValue.getModeOfTransport() instanceof Car);
final Container stringValue = om.readValue(getStringJson(), Container.class);
assertTrue(stringValue.getModeOfTransport() instanceof Car);
}
private String getObjectJson() {
return "{ \"modeOfTransport\": { \"type\": \"car\", \"name\": \"KITT\", \"speed\": 1}}";
}
private String getStringJson() {
return "{ \"modeOfTransport\": \"KITT\"}";
}
}
class Container {
private Vehicle modeOfTransport;
public Vehicle getModeOfTransport() {
return modeOfTransport;
}
public void setModeOfTransport(final Vehicle modeOfTransport) {
this.modeOfTransport = modeOfTransport;
}
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
#JsonSubTypes({
#Type(name = "car", value = Car.class)
})
abstract class Vehicle {
protected String type;
protected String name;
public String getType() {
return type;
}
public void setType(final String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
}
#JsonTypeName("car")
class Car extends Vehicle {
private int speed;
public int getSpeed() {
return speed;
}
public void setSpeed(final int speed) {
this.speed = speed;
}
}
Note that I used JSON, not YAML, and you need to add your other subtypes as well.
I need to deserialize a JSON string using Jackson into Lists of different Object types depending on the value set in one of the fields. I want to know what the best approach is for creating the different List Types and how i can implement this?
My JSON's would look something like this:
{"test":
{"conditions":[....],
"consequence": {"actionType":"string",
"action": ["value 1","value 2"]}
}
}
So when parsed the above would return a List<String>
{"test":
{"conditions":[....],
"consequence": {"actionType":"test",
"action": ["test","test"]}
}
}
and the above would return a List<Test>
My Pojo just contains:
#Data
public class Consequence {
public Consequence(String actionType){
this.actionType = actionType;
};
#JsonProperty("ACTIONTYPE")
private String actionType;
#JsonProperty("ACTION")
private List<????> action;
}
UPDATE:
After i updated my POJO's using the following hierarchy:
#Data
public abstract class BaseConsequence {
public BaseConsequence(String actionType){
this.actionType = actionType;
};
#JsonProperty("ACTIONTYPE")
private String actionType;
}
#Data
#DiscriminatorValue(value = "CONCATENATE")
public class ConcatenateConsequence extends BaseConsequence {
public ConcatenateConsequence(String actionType, List<String> concatenateValues) {
super(actionType);
this.concatenateValues = concatenateValues;
}
private List<String> concatenateValues;
}
#Data
#DiscriminatorValue(value = "test")
public class TestConsequence extends BaseConsequence {
public TestConsequence(String actionType, List<Test> tests){
super(actionType);
this.tests = tests;
}
private List<Test> tests;
}
#Data
public class Test {
public Test(){};
public Test(List<Condition> conditions, BaseConsequence baseConsequence){
this.conditions = conditions;
this.baseConsequence = baseConsequence;
}
#JsonProperty("CONDITIONS")
private List<Condition> conditions;
#JsonProperty("CONSEQUENCE")
private BaseConsequence baseConsequence;
#Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Test)) {
return false;
}
Test test = (Test) o;
return Objects.equals(conditions, test.conditions) && Objects.equals(baseConsequence, test.baseConsequence);
}
#Override
public int hashCode() {
return Objects.hash(conditions, baseConsequence);
}
}
I get the following error:
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
at [Source: {"TEST":{"CONDITIONS":[{"KEY":"KEY1","VALUES":["FLOAT"],"OPERATOR":""}],"CONSEQUENCE":{"ACTIONTYPE" :{"CONCATENATE": ["VALUE1","VALUE2"]}}}}; line: 1, column: 9] (through reference chain: package.TestCase["TEST"])
There are two variants you can use:
Create custom deserializer. See here for full descriptions and examples http://www.baeldung.com/jackson-deserialization
The best way is to use one base class and two children. Each children should be marked with #DiscriminatorValue. See here for full description and examples http://www.baeldung.com/jackson-inheritance
I'm trying to deserialize some JSON to a generic class. The structure is roughly as follows:
public abstract class AbstractRequest implements Constants
{
public abstract Class<?> getClazz();
}
public class GetTransaction extends AbstractTransactionRequest
{
#Override
public Class<Transaction> getClazz()
{
return Transaction.class;
}
}
And the Transaction class is as follows:
public class Transaction implements Serializable
{
#SerializedName("_id")
private String id;
private int amount;
#SerializedName("details")
private Map<String, String> transactionDetails;
private class Details {
private String issuer;
#SerializedName("redirect_url")
private String redirectUrl;
#SerializedName("approval_url")
private String approvalUrl;
}
}
All classes are slightly more complicated but I removed irrelevant variables.
Here's a JSON sample:
{
"_id": "2740096e-58a0-4677-8947-84fcc54cfaad",
"amount": 456,
"details": {
"issuer": "MYBANK",
"redirect_url": "https://example.com/redirect/MYBANK",
"approval_url": "https://example.com/v1/transaction/2740096e-58a0-4677-8947-84fcc54cfaad/MYBANK/authorize"
}
}
Now, I deserialize this code by doing
response.setData(Gson.fromJson(this.getResponse(), this.request.getClazz()));
Where setData accepts a Object, and getResponse returns the JSON as a String. I then do (Transaction) response.getData() which casts data to a Transaction. However, this is always null. Can anyone tell my why?
Sorry for the potentially confusing code!
I need to decode the following JSON-Structure:
{
"id":1
"categories": [
value_of_category1,
value_of_category2,
value_of_category3
]
}
The object I am trying to deserialize into is of the following class:
class MyClass {
public Integer id;
public Category1 category1;
public Category2 category2;
public Category3 category3;
...
}
public enum Category1{
...
}
public enum Category2{
...
}
public enum Category3{
...
}
In the JSON the first entry of the categories-Array is always a value of the enum Category1, the second entry is always a value of the enum Category2 and the third entry is always a value of the enum Category3.
How can I deserialize this JSON into my class structure using Jackson?
You can create your custom deserializer by extending the JsonDeserializer<T> class.
Here is a sample implementation to fit your needs
public class MyClassDeserializer extends JsonDeserializer<MyClass>{
#Override
public MyClass deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
int id = (Integer) ((IntNode) node.get("id")).numberValue();
JsonNode categories = node.get("categories");
Iterator<JsonNode> iter = categories.elements();
while(iter.hasNext()){
//create Category object based on an incrementing counter
}
MyClass myClass = //compose myClass from the values deserialized above.
return myClass;
}
}
To use it, you just have to annotate MyClass with #JsonDeserialize pointing to your custom deserializer.
#JsonDeserialize(using = MyClassDeserializer.class)
public class MyClass {
private Integer id;
private Category1 category1;
private Category2 category2;
private Category3 category3;
}
Not related, but as a good practice, make all your fields private then expose public setters and getters in order to control the fields of the class.