1

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

Codex
  • 1,153
  • 1
  • 20
  • 31
  • I'm afraid it's going to be very difficult to help you on this one without a minimum reproducible example. https://stackoverflow.com/help/minimal-reproducible-example – Captain Sep 04 '19 at 13:15
  • 1
    Please post a [mcve] 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 Sep 04 '19 at 13:15
  • Since you say that it can deserialize `List` 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 Sep 04 '19 at 13:18
  • There are so many things to add here, Models are huge. Let me try to create a https://stackoverflow.com/help/minimal-reproducible-example – Codex Sep 04 '19 at 14:48
  • 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 Sep 05 '19 at 20:17
  • I think that your MapperConfig class is in wrong package. Seems like ComponentScan is not scanning it. Try to move your ObjectMapper bean in class where your @SpringBootApplication is and if that works, you can be sure that MapperConfig should be in folder where ComponentScan can scan it. – Spasoje Petronijević Sep 05 '19 at 21:10

3 Answers3

2

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();
cassiomolin
  • 124,154
  • 35
  • 280
  • 359
  • OMG, Finally, this is what I was missing, it solved the problem. Thank you so much. – Codex Sep 05 '19 at 21:33
  • @vinoth10 Please consider upvoting and accepting my answer then. It's the best way to say _thank you_ :) – cassiomolin Sep 05 '19 at 21:35
  • I am just wondering why objectMapper.readValue(node.toString(), Car.class) - this works But objectMapper1.readValue(jasonParser, Car.class) - it doesn't. Apart from this I got answer for my issue. Thank you. – Codex Sep 05 '19 at 21:35
  • @vinoth10 There's no such `objectMapper1.readValue(jasonParser, Car.class)` in your code. I don't know what you are talking about. – cassiomolin Sep 05 '19 at 21:37
  • I mean after you answer I just did ObjectMapper objectmapper1= (ObjectMapper) jsonParser.getCodec(); Sorry for the confusion – Codex Sep 05 '19 at 21:39
  • @vinoth10 `@Autowired` only works for Spring beans. That deserializer is not a Spring bean and you instantiating it manually. Spring doesn't manage it, so `@Autowired` won't work. In the approach described in by answer, you obtain the `ObjectCodec` (which actually is an instance of `ObjectMapper`) used by the `JsonParser`. There's no magic there. – cassiomolin Sep 05 '19 at 21:44
1

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.

stacker
  • 4,317
  • 2
  • 10
  • 24
  • Sorry @stacker, I dont understand. I think its fine to add a deserializer manually. Please correct me if I am wrong. Thank you. – Codex Sep 05 '19 at 21:37
  • by adding it manually you have to manage the object yourself, spring won't be responsible for autowiring ObjectMapper, so its value will be null, – stacker Sep 06 '19 at 10:06
1

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;
    }
}
Michał Ziober
  • 37,175
  • 18
  • 99
  • 146