Jackson: How do I post-process JsonNode during serialization? - java

I am attempting to implement the HL7 FHIR spec's assertion that JSON representing a FHIR model will not have empty objects nor empty arrays. For the sake of not making the lives of my consumers any harder, I'm not strictly enforcing this during deserialization, but I want to ensure the serialized JSON produced by my library conforms as specified. I am using Java and Jackson ObjectMapper to serialize Objects into JSON. My understanding from writing a custom serializer is that the Object is at one point represented as JsonNode, regardless of what you are converting to.
What I would like to do is intercept the JsonNode as it exits the serializer, make some adjustments to it (find and remove empty arrays and objects), and then let it continue on its way. I need to do this in an environment where I can't tweak the ObjectMapper, because I don't have access to it. And further, the complex hierarchy of models in this library use Jackson's default serialization with annotations etc. heavily, and I cannot eliminate this.
If I go the route of defining a custom serializer for the base type, let's say "Resource", then I have a problem, because I still need the original serializer's output in order to generate my modified output. And further, that needs to accommodate any custom serializers that may already exist on various types within the model.
I got pretty far with the above option using https://www.baeldung.com/jackson-call-default-serializer-from-custom-serializer and the last option, implementing BeanSerializerModifier, but I ran into the issue where I can't control the ObjectMapper that my library consumers use.
Example POJOs (Using Lombok for accessors):
#Data
#JsonInclude(JsonInclude.Include.NON_EMPTY)
#JsonIgnoreProperties(ignoreUnknown = true)
abstract class Resource {
private FhirString id;
private List<Extension> extension;
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
public abstract ResourceType getResourceType();
}
#Data
#Builder
class SomethingElse extends Resource {
FhirUri someProperty;
CodeableConcept someCode;
List<Reference> someReferences;
#Override
public ResourceType getResourceType() {
return ResourceType.SOMETHING_ELSE;
}
}
And an example instance of the SomethingElse class:
SomethingElse somethingElse = SomethingElse.builder()
.someProperty(FhirUri.from("some-simple-uri"))
.someCode(new CodeableConcept())
.someReference(List.of(new Reference()))
.build();
somethingElse.setId(FhirString.randomUuid());
somethingElse.setExtension(new ArrayList<>());
When I tell any mapper (or, for example, use a Spring service) to map the SomethingElse class into JsonNode, I can, for example, end up with empty objects and arrays, like this:
ObjectMapper mapper = getUntouchableMapper();
JsonNode somethingElseNode = mapper.valueToTree(somethingElse);
System.out.println(somethingElseNode.toString());
Becomes:
{
"resourceType": "SomethingElse",
"id": "00000000-0002-0004-0000-000000000000",
"someProperty": "some-simple-uri",
"someCode": {},
"someReferences": [{}],
"extension": []
}
According to FHIR, this should actually look like:
{
"resourceType": "SomethingElse",
"id": "00000000-0002-0004-0000-000000000000",
"someProperty": "some-simple-uri"
}
To summarize
How do I preserve the serialization mechanisms already in place, regardless of the ObjectMapper used, and somehow remove empty lists and objects from outgoing JSON produced by the Jackson serialization process?
Edit:
I also tried #JsonInclude(JsonInclude.Include.NON_EMPTY), which did omit empty list implementations. However, the vast majority of data in this library is represented by POJOs that serialize to maps and primitives, and this annotation only works if they are represented directly by maps and primitives in the model.

The solution is to use a custom #JsonInclude, which is new in Jackson 2.9. Thank you #dai for pointing me back towards this functionality.
On the base Resource class, this looks like:
#JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = FhirJsonValueFilter.class)
class Resource implements FhirTypeInterface {
...
#Override
public boolean isEmpty() {
//Details omitted for simplicity
}
}
For visibility, the interface used above:
interface FhirTypeInterface {
boolean isEmpty();
}
And my custom definition for FhirJsonValueFilter implements all of the functionality of JsonInclude.Include.NON_EMPTY but also adds functionality for checking against a method implemented by FHIR types (implementation of this is not relevant to the answer).
public class FhirJsonValueFilter {
#Override
public boolean equals(Object value) {
return !getWillInclude(value);
}
/**
* Returns true for an object that matched filter criteria (will be
* included) and false for those to omit from the response.
*/
public boolean getWillInclude(Object value) {
//Omit explicit null values
if (null == value) {
return false;
}
//Omit empty collections
if (Collection.class.isAssignableFrom(value.getClass())) {
return !((Collection) value).isEmpty();
}
//Omit empty maps
if (Map.class.isAssignableFrom(value.getClass())) {
return !((Map) value).isEmpty();
}
//Omit empty char sequences (Strings, etc.)
if (CharSequence.class.isAssignableFrom(value.getClass())) {
return ((CharSequence) value).length() > 0;
}
//Omit empty FHIR data represented by an object
if (FhirTypeInterface.class.isAssignableFrom(value.getClass())) {
return !((FhirTypeInterface) value).isEmpty();
}
//If we missed something, default to include it
return true;
}
}
Note that the custom omission filter uses Java's Object.equals functionality, where true means to omit the property, and I've used a second method to reduce confusion in this answer.

Related

How can I force Spring to serialize my controller's #ResponseBody as the method's declared return type?

I am attempting to use interfaces to define flexible response bodies from my Spring controllers.
What I expect: When I call an endpoint using Curl/Postman/etc, I should receive JSON objects that contain only the fields visible in the interface that the controller returns.
What I'm getting: When I call either endpoint, I receive JSON objects with every field defined in my entity.
Let's say my entity looks like this:
MyEntity.java
public class MyEntity implements ListEntityResponse, GetEntityResponse {
int dbid;
String name;
String description;
public int getDbid() { return dbid; }
public String getName() { return name; }
public String getDescription() { return description; }
}
Let's say MyEntity has many more fields that include complex data types that aren't suitable for serialization as part of a large list, or for certain other use cases. To solve this problem, I've created interfaces to limit which fields are visible in the response object. In this example, the first interface only defines two of the three getters, while the second interface defines all of them.
ListEntityResponse interface:
public interface ListEntityResponse {
int getDbid();
String getName();
}
GetEntityResponse interface:
public interface GetEntityResponse {
int getDbid();
String getName();
String getDescription();
}
And finally, here are my controllers. The important part is that each defines its return type as one of the interfaces:
ListEntityController
#GetMapping(path="/{name}")
public #ResponseBody List<ListEntityResponse> getList() {
return handler.getList(name);
}
GetEntityController
#GetMapping(path="/{name}")
public #ResponseBody GetEntityResponse getByName(#PathVariable("name") String name) {
return handler.getByName(name);
}
To recap, if we assume that our handler returns MyEntity objects, then I want that object to be serialized by Spring as the interface defined in the controller's return type. E.G. each JSON object in the list returned by the ListEntityController should have only the dbid and name fields. Unfortunately, that's not happening, and the returned JSON objects have every field available despite being masked as interface objects.
I have attempted to add #JsonSerialize(as = ListEntityResponse.class) to my first interface, and a similar annotation to the second. This works only if the entity implements just one of those interfaces. Once the entity implements multiple interfaces, each annotated with #JsonSerialize, Spring will serialize it as the first interface in the list regardless of the controller's return type.
How can I force a Spring to always serialize its Controller's responses as the controller function's return type?
Note: I am trying to find a solution that does not require me to use #JsonIgnore or #JsonIgnoreProperties. Additionally, I am trying to find a solution that does not require me to add #JsonView to my entity classes. I am willing to use the #JsonView annotation in the interfaces, but don't see a clean and maintainable way to do so.
How can I force Spring to always serialize its controller's responses as
the controller function's return type?
Please note that I am not interested in using #JsonIgnore,
#JsonIgnoreProperties, or #JsonView to provide the view masking that I
require. They do not fit my use case.
One of the options would be to create a thin wrapper over MyEntity class, which would be responsible for providing the required serialization-shape.
Every shape would be represented by its own wrapper, implemented as a single-field class. To specify serialization-shape, we can use as property of the #JsonSerialize annotation, by assigning the target interface as a value. And since we don't need the wrapper itself to reflected in the resulting JSON, we can make use of the #JsonUnwrapped annotation.
Here's a wrapper for GetEntityResponse shape:
#AllArgsConstructor
public class GetEntityResponseWrapper implements EntityWrapper {
#JsonSerialize(as = GetEntityResponse.class)
#JsonUnwrapped
private MyEntity entity;
}
And that's a wrapper for ListEntityResponse shape:
#AllArgsConstructor
public class ListEntityResponseWrapper implements EntityWrapper {
#JsonSerialize(as = ListEntityResponse.class)
#JsonUnwrapped
private MyEntity entity;
}
Basically, we have finished with serialization logic.
And you can use these lean classes in your controllers as is. But to make the solution more organized and easier to extend, I've introduced a level of abstraction. As you probably noticed both wrapper-classes are implementing EntityWrapper interface, its goal is to abstract away the concrete implementation representing shapes from the code in Controllers/Services.
public interface EntityWrapper {
enum Type { LIST_ENTITY, GET_ENTITY } // each type represents a concrete implementation
static EntityWrapper wrap(Type type, MyEntity entity) {
return switch (type) {
case LIST_ENTITY -> new ListEntityResponseWrapper(entity);
case GET_ENTITY -> new GetEntityResponseWrapper(entity);
};
}
static List<EntityWrapper> wrapAll(Type type, MyEntity... entities) {
return Arrays.stream(entities)
.map(entity -> wrap(type, entity))
.toList();
}
}
Methods EntityWrapper.wrap() and EntityWrapper.wrapAll() are uniform entry points. We can use an enum to represent the target type.
Note that EntityWrapper needs to be used in the return types in your Controller.
Here the two dummy end-points I've used for testing (I've removed the path-variables since they are not related to what I'm going to demonstrate):
#GetMapping("/a")
public List<EntityWrapper> getList() {
// your logic here
return EntityWrapper.wrapAll(
EntityWrapper.Type.LIST_ENTITY,
new MyEntity(1, "Alice", "A"),
new MyEntity(2, "Bob", "B"),
new MyEntity(3, "Carol", "C")
);
}
#GetMapping("/b")
public EntityWrapper getByName() {
// your logic here
return EntityWrapper.wrap(
EntityWrapper.Type.GET_ENTITY,
new MyEntity(2, "Bob", "B")
);
}
Response of the end-point "/a" (only two properties have been serialized):
[
{
"name": "Alice",
"dbid": 1
},
{
"name": "Bob",
"dbid": 2
},
{
"name": "Carol",
"dbid": 3
}
]
Response of the end-point "/b" (all three properties have been serialized):
{
"name": "Bob",
"description": "B",
"dbid": 2
}

Gson custom deserializer for specific fields to String

I have a variety of JSON files (with slightly different schemas) flowing on Kinesis. Their Schema is really complex. They are user raw hits and their schema is super complex. I would like to create a Single POJO to represent all underneath messages (Something Spark does internally by creating a Single schema). I was trying to use GSON library but the only way to accomplish this is, by writing a custom deserializer and in that case, I will end up writing deserialization logic for all the fields in those JSONs.
Is there any way where we can only overwrite the deserialization of a few fields only and the rest of the fields can still be deserialized by GSON as a default way?
JSON-1
{
"first_col":"abc",
"second_col":false
}
JSON-2
{
"first_col":"abc",
"second_col":"false-String"
}
JSON-3
{
"first_col":"abc",
"second_col":{
"col1":"xyz",
"col2":123
}
}
Common POJO
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Example {
private String firstCol;
private String secondCol;
public String getFirstCol() {
return firstCol;
}
public void setFirstCol(String firstCol) {
this.firstCol = firstCol;
}
public Boolean getSecondCol() {
return secondCol;
}
public void setSecondCol(String secondCol) {
this.secondCol = secondCol;
}
}
So basically second_col could be boolean, string, or complex object. first_col is always a string. So I don't want to write deserialize logic for first_col. I want to write deserialize logic for only second_col and deserialize it to string only and the downstream consumer will take care of converting it to the right type before consuming this field.

JSON serialization of different attributes of a single entity for different requests using Jackson

There are several REST calls that require the same JSON entity with a different set of attributes. Example of the entity:
public class JsonEntity
{
public String id;
public String name;
public String type;
public String model;
}
JsonEntity is a part of the complex responses of different calls. The first call requires the whole JsonEntity without changes. Second call requires JsonEntity without type and model attributes. Thrid one requires JsonEntity without name attribute.
Is there any way to retrieve the same JSON entity with a particular set of attributes depending on the particular context (except separating JsonEntity) using Jackson?
I see 3 ways of doing this:
1. Use #JsonGetter
This annotation tells jackson to use a metho rather than a field for serialization.
Create 3 subclasses of JsonEntity, one for each response. Change JsonEntity and use #IgnoreField on every field, make them protected if possible. On each subclasses, create specific getter for the fields you need, example:
public class JsonEntitySecondCall extends JsonEntity
{
#JsonGetter("id")
public String getId(){
return id;
}
#JsonGetter("name")
public String getName(){
return name;
}
}
Also, create a clone/copy constructor for JsonEntity. For your second call, create a new JsonEntitySecondCall by cloning the original JsonEntity, and use it in your API. Because of the anotation, the created Object will only serialisze the given fields. I don't this you can just cast your object, because Jackson uses reflection.
2. Use #AnyGetter
the AnyGetter annotaiton allows you to define a map of what will be serialized:
private Map<String, Object> properties = new HashMap<>();
#JsonAnyGetter
public Map<String, Object> properties() {
return properties;
}
Now you just need to tell your JsonEntity what properties it needs to return before each call (you could create 3 methods, one for each context, and use an enum to set which one must be used.).
3. Use #JsonInclude(Include.NON_NULL)
This annotation tells Jackson not to serialize a field if it is null. You can then clone your object and set null the fields you don't want to send. (this only works if you shouldn't send null elements to the API)
For more on Jackson annotations use this link.

Deserialize a JSON wrapped in an object with an unknown property name using Jackson

I'm using Jackson to deserialize JSON from a ReST API to Java objects using Jackson.
The issue I've run into is that one particular ReST response comes back wrapped in an object referenced by a numeric identifier, like this:
{
"1443": [
/* these are the objects I actually care about */
{
"name": "V1",
"count": 1999,
"distinctCount": 1999
/* other properties */
},
{
"name": "V2",
"count": 1999,
"distinctCount": 42
/* other properties */
},
...
]
}
My (perhaps naive) approach to deserializing JSON up until this point has been to create mirror-image POJOs and letting Jackson map all of the fields simply and automatically, which it does nicely.
The problem is that the ReST response JSON has a dynamic, numeric reference to the array of POJOs that I actually need. I can't create a mirror-image wrapper POJO because the property name itself is both dynamic and an illegal Java property name.
I'd appreciate any and all suggestions for routes I can investigate.
The easiest solution without custom deserializers is to use #JsonAnySetter. Jackson will call the method with this annotation for every unmapped property.
For example:
public class Wrapper {
public List<Stuff> stuff;
// this will get called for every key in the root object
#JsonAnySetter
public void set(String code, List<Stuff> stuff) {
// code is "1443", stuff is the list with stuff
this.stuff = stuff;
}
}
// simple stuff class with everything public for demonstration only
public class Stuff {
public String name;
public int count;
public int distinctCount;
}
To use it you can just do:
new ObjectMapper().readValue(myJson, Wrapper.class);
For the other way around you can use #JsonAnyGetter which should return a Map<String, List<Stuff>) in this case.
I think the easiest solution is to use a custom JsonDeserializer. It allows you to parse the input step by step and to extract only those information you need to build your object.
Here is a simple example how to implement a custom deserializer: custom jackson deserializer

Jackson vs Gson for simple deserialisation

For parsing JSON like this twitter API users/show response I've been using Jackson and Gson Java libraries as candidates to do this work. I'm only interested in a small subset of properties of the JSON so Gson was nice because of its very concise syntax but I'm losing an internal battle to continue to use Gson as Jackson is already used elsewhere in our application and it has documented better performance (which I concede are both good reasons to lose Gson).
For a POJO like
public class TwitterUser {
private String id_str;
private String screen_name;
public String getId_str() {
return id_str;
}
public void setId_str(String id_str) {
this.id_str = id_str;
}
public String getScreen_name() {
return screen_name;
}
public void setScreen_name(String screen_name) {
this.screen_name = screen_name;
}
}
The only code for Gson needed to build this is one line,
TwitterUser user = new Gson().fromJson(jsonStr, TwitterUser.class);
That's pretty nice to me; scales well and is opt-in for the properties you want. Jackson on the other hand is a little more laborious for building a POJO from selected fields.
Map<String,Object> userData = new ObjectMapper().readValue(jsonStr, Map.class);
//then build TwitterUser manually
or
TwitterUser user = new ObjectMapper().readValue(jsonStr, TwitterUser.class);
//each unused property must be marked as ignorable. Yikes! For 30 odd ignored fields thats too much configuration.
So after that long winded explanation, is there a way I can use Jackson with less code than is demonstrated above?
With Jackson 1.4+ you can use the class-level #JsonIgnoreProperties annotation to silently ignore unknown fields, with ignoreUnknown set to true.
#JsonIgnoreProperties(ignoreUnknown = true)
public class TwitterUser {
// snip...
}
http://wiki.fasterxml.com/JacksonAnnotations
http://wiki.fasterxml.com/JacksonHowToIgnoreUnknown

Categories

Resources