1
votes

I am using Jackson xml version 2.9.9 in spring boot application.

I have a controller that accepts Vehicle which is an abstract class that has 2 child classes. When I pass JSON data from postman I am not able deserialize Vehicle since object mapper which is auto wired is always null(I configured objectmapper as a bean since I am setting deserialiers as simple modules to mapper). Please look at the example code in https://github.com/vin0010/Jackson-Mapper-Issue

Controller

@RequestMapping(path = "/jackson", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
    public void forSingleObject(@RequestBody MyModel myModel){
        System.out.println("Deserialization successful");
    }

MyModel.java

public class MyModel {
    private String name;
    private Vehicle value;
}

Vehicle.java

public abstract class Vehicle {
    private String vehicleName;
}

Car.java

public class Car extends Vehicle{
    private String carType;
    private String carDriverName;
}

Auto.java

public class Auto extends Vehicle {
    private String autoType;
    private String autoDriverName;
}

ObjectMapper bean configuration

@Configuration
public class MapperConfig {
    @Bean
    public ObjectMapper objectMapper(){
        ObjectMapper objectMapper = new ObjectMapper();//Jackson2ObjectMapperBuilder.json().build();
        List<Module> modules = new ArrayList<>();
        modules.add(new SimpleModule().addDeserializer(Vehicle.class, new VehicleDeserializer()));
        objectMapper.registerModules(modules);
        return objectMapper;
    }
}

VehicleDeserializer.java

public class VehicleDeserializer extends JsonDeserializer<Vehicle> {

    @Autowired
    private ObjectMapper objectMapper;
    @Override
    public Vehicle deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser);
        Vehicle vehicle=null;
        if (!jsonNode.findPath("carDriverName").equals("")){
            vehicle = objectMapper.readValue(jsonParser, Car.class);
        }else if (!jsonNode.findPath("autoDriverName").equals("")){
            vehicle = objectMapper.readValue(jsonParser, Auto.class);
        }
        return vehicle;
    }
}

JSON input

{
    "name": "Bajaj",
    "value": {
        "autoDriverName": "Mr Auto Driver",
        "autoType": "commercial"
    }
}

Problem : Object mapper is not injected and its null.

I just found out that neither object mapper is initialized in VehicleDeserializer.java nor I found a way to deserialize string to Car/Auto without the help of ObjectMapper.

I tried every method in How do I obtain the Jackson ObjectMapper in use by Spring 4.1? but nothing seems to work.

I added this as a separate question because either I need a fix the injection Objectmapper or deserialize Car/Auto without using Objectmapper(still I can only have Fasterxml).

Creating a new ObjectMapper inside VehicleDeserializer.java is not helping since it always return null object for Car/Auto even if I add deserializers to it. I made this example since its just a https://stackoverflow.com/help/minimal-reproducible-example. I need jackson to complete the de serialization

3
I'm afraid it's going to be very difficult to help you on this one without a minimum reproducible example. stackoverflow.com/help/minimal-reproducible-exampleCaptain
Please post a minimal reproducible example that reproduces the problem. Use as few fields / small code as possible, but make sure that what you have posted compiles and runs without anything else. You may even find the cause of the problem yourself while doing that.Erwin Bolwidt
Since you say that it can deserialize List<Metric> but breaks after, try reproducing this with just array of metrics and some other field. Trim both the class and the JSON, that should help. Also, are you quite sure you have all the json text and it doesn't cut at the random point?M. Prokhorov
There are so many things to add here, Models are huge. Let me try to create a stackoverflow.com/help/minimal-reproducible-exampleCodex
I made some findings but yet to fix the complete problem. I think new question is not a good option, let me make edits in the question to update current issue.Codex

3 Answers

2
votes

There's no need to attempt to inject ObjectMapper in your custom deserializer. You could use:

ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();

If you don't need any method from ObjectMapper, casting is not necessary:

ObjectCodec codec = jsonParser.getCodec();
1
votes

the reason for your issue, you are doing this:

modules.add(new SimpleModule().addDeserializer(Vehicle.class, new VehicleDeserializer()));

you are instantiating the object yourself so the mapper will be null.

1
votes

You made some mistakes in your deserialiser:

  • Because you registered your deserialiser to ObjectMapper object inside deserialiser you can get it by (ObjectMapper) parser.getCodec() where parser is JsonParser type.
  • findPath method returns JsonNode not String so .equals("") always return false. You should use isMissingNode method instead.
  • When you use readTree method on parser object you can not use it again because deserialisation was already done and you have a JsonNode. Since now, you should use it. You can use convertValue method to convert it to desired Class.

This is how it should be:

class VehicleDeserializer extends JsonDeserializer<Vehicle> {

    @Override
    public Vehicle deserialize(JsonParser parser, DeserializationContext deserializationContext) throws IOException {
        // cast to ObjectMapper
        ObjectMapper mapper = (ObjectMapper) parser.getCodec();

        // read JSON as tree to JsonNode object
        // since now `parser` should not be used anymore.
        JsonNode root = mapper.readTree(parser);
        Vehicle vehicle = null;
        if (!root.findPath("carDriverName").isMissingNode()) {
            vehicle = mapper.convertValue(root, Car.class);
        } else if (!root.findPath("autoDriverName").isMissingNode()) {
            vehicle = mapper.convertValue(root, Auto.class);
        }
        return vehicle;
    }
}