0

Please note: There are many questions on this site about how to use custom Jackson deserializers...this question is not one more of those! This questions has to do with using a deserializer under very unique circumstances (none of which have previous questions/answers on this site!).


Spring Boot using Jackson for JSON serialization here. I have two POJOs that are used in the @RequestBody (HTTP request entity) for a POST endpoint:

@JsonDeserialize(using = FizzDeserializer.class)
public class Fizz {
  private String name;
  private String label;
  private Integer code;

  // Getters, setters & ctors
}

@JsonDeserialize(using = BuzzDeserializer.class)
public class Buzz {
  private String id;
  private String locale;
  private Set<Fizz> fizzes;

  // Getters, setters & ctors
}

@RestController
@RequestMapping("v1/data/buzzes")
public class BuzzController {
  @PostMapping
  public void updateBuzz(@RequestBody Buzz buzz) {
    // do whatever
  }
}

I want HTTP clients to be able to POST the following JSON to this endpoint:

{
  "id" : "12345-67890",
  "locale" : "en_US",
  "fizzes" : [
    "foo",
    "bar"
  ]
}

...where "foo" and "bar" are the Fizz#names of two different Fizz instances. In other words, I don't want the client to have to specify the entire Fizz object, just specify its name as a JSON string (my app + DB guarantee Fizzes have unique names).

So I'm using a custom JsonDeserializer to accomplish all this mapping:

public BuzzDeserializer extends JsonDeserializer<Buzz> {
  Buzz deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
    JsonNode buzzNode = jsonParser.readValueAsTree();

    String id = buzzNode.get("id");
    String locale = buzzNode.get("locale");

    // TODO: How to read "foo" and "bar" (etc.) into a Set<Fizz> instances?
    Set<Fizz> fizzes = ???

    new Buzz(id, locale, fizzes);
  }
}

public FizzDeserializer extends JsonDeserializer<Fizz> {
  private FizzDAO fizzDAO;

  // Getters, setters & ctors...

  Fizz deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
    JsonNode fizzNode = jsonParser.readValueAsTree();

    // If I can get access to "foo"/"bar"/etc. string somehow, I can look up the Fizz using the DAO:
    String fooBarEtcStr = ???

    return fizzDAO.findFizzByName(fooBarEtcStr);
  }    

However I'm not sure how I can read the JSON fizzes array into a Set<Fizz> inside this deserializer. Any ideas?

smeeb
  • 27,777
  • 57
  • 250
  • 447
  • 1
    you obviously need one of your services/DAOs injected in your deserializer to load `Fizz`es by name. Here's a SO question on how to go about this: https://stackoverflow.com/questions/28393599/autowiring-in-jsondeserializer-springbeanautowiringsupport-vs-handlerinstantiat – msp Jan 18 '18 at 14:06
  • Yes thanks @msparer (+1) thats exactly what I have done, did you look at the code above? The question is about how to parse a JSON array of string names (`["foo","bar"]`) into a `List`. I can then pass each string in that list into the DAO to look up its respective `Fizz`. – smeeb Jan 18 '18 at 16:59

1 Answers1

1

Taken from the comments to the question, the only problem seems to be to get the JSON array. Provided that the service or DAO to lookup is already injected or provided in the serializer, try something along the lines:

final JsonNode arr = buzzNode.get("fizzes");

if (arr.isArray()) {
    final Set<Fizz> fizzes = Sets.newHashSetWithExpectedSize(arr.size());
    for (JsonNode obj : arr) {
        final String name = obj.asText();
        Fizz fizz = // load from DAO
        fizzes.add(fizz);
    }
}

This can of course be optimised by collecting the String values and use only one DAO call. Also some java8 streaming could make the code less verbose.

msp
  • 3,272
  • 7
  • 37
  • 49