Conditionally rename Jackson JSON output - java

Let's suppose I have a class holding one variable:
#Data
public class Student {
private List<Grade> allGrades;
}
This will be sent to a producer which is written in Python. They would like to have the property in snake_case and we want to keep the camelCase in java.
This can be achieved with Jackson:
#Data
public class Student {
#JsonProperty("all_grades")
private List<Grade> allGrades;
}
Now, we're building an application that can dynamically switch between producers, so some might be written in Java and some in Python.
In our application we can know this by either storing it in the config or sending it as a request header.
If the producer is Java, we would like to refrain from renaming the property. So the main question is:
(How) is it possible to conditionally rename the property allGrades to all_grades based on a String/boolean?

I am sure this is not supported by Jackson out of the box, nor do I think it should be. If you really have to do such a thing, you could subclass your Student class for different producers, such as
#Data
class Student {
private List<Grade> allGrades;
#JsonProperty
List<Grade> getAllGrades() {
return allGrades;
}
}
#Data
#EqualsAndHashCode(callSuper = true)
class PythonStudent extends Student {
#JsonProperty("all_grades")
#Override
public void getAllGrades() {
super.getAllGrades();
}
}
And then construct the desired object based on your condition in a factory class.
That said, it's really easy to consume JSON strings in both Java and Python using either snake_case or camelCase conventions and map them to fields in the desired format. I don't think what you want to achieve adds any value, but it would certainly introduce a lot of complexity.

Related

How to ignore field/column only when return response from spring boot

I need to ignore the field when return the response from spring boot. Pls find below info,
I have one pojo called Student as below
Student {
id,
name,
lastName
}
i am getting a body for as PostRequest as below
{
id:"1",
name:"Test",
lname:"Test"
}
i want get all the data from frontEnd (id,name,Lname) But i just want to return the same pojo class without id as below,
{
name:"Test",
lName:"Test"
}
I have tried #JsonIgnore for column id, But it makes the id column as null(id=null -it is coming like this even when i send data to id field from postman) when i get the data from frontEnd.
I would like to use only one pojo to get the data with proper data(withoud getting id as Null), and need to send back the data by ignoring the id column.
Is there any way to achieve it instead of using another pojo?
You just need to use #JsonInclude(JsonInclude.Include.NON_NULL) at class level and it will be helpful for ignore all your null fields.
For example :
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Test {
// Fields
// Constructors
// Getters - setters
}
As of now you are using only one POJO it's not good practice because it's your main entity into your project, so good practice is always make DTO for the same.
This is possible via the #JsonView annotation that is part of Jackson. Spring can leverage it to define the views used on the controller.
You'd define your DTO class like this:
class User {
User(String internalId, String externalId, String name) {
this.internalId = internalId;
this.externalId = externalId;
this.name = name;
}
#JsonView(User.Views.Internal.class)
String internalId;
#JsonView(User.Views.Public.class)
String externalId;
#JsonView(User.Views.Public.class)
String name;
static class Views {
static class Public {
}
static class Internal extends Public {
}
}
}
The Views internal class acts as a marker to jackson, in order to tell it which fields to include in which configuration. It does not need to be an inner class, but that makes for a shorter code snippet to paste here. Since Internal extends Public, all fields marked with Public are also included when the Internal view is selected.
You can then define a controller like this:
#RestController
class UserController {
#GetMapping("/user/internal")
#JsonView(User.Views.Internal.class)
User getPublicUser() {
return new User("internal", "external", "john");
}
#GetMapping("/user/public")
#JsonView(User.Views.Public.class)
User getPrivateUser() {
return new User("internal", "external", "john");
}
}
Since Spring is aware of the JsonView annotations, the JSON returned by the /public endpoint will contain only externalId and name, and the /internal endpoint will additionally include the internalId field.
Note that fields with no annotation will not be included if you enable any view. This behaviour can be controlled by MapperFeature.DEFAULT_VIEW_INCLUSION, which was false in the default Spring ObjectMapper when I used this for the last time.
You can also annotate your #RequestBody parameters to controller methods with JsonView, to allow/disallow certain parameters on input objects, and then use a different set of parameters for output objects.

Is there an equivalent of Jackson + Spring's `#JsonView` using Quarkus + JSONB?

I'm playing with Quarkus and trying to build a CRUD REST application; I'm trying to get 2 endpoints returning 2 different views of the same entities. Here is an example on how I would have done in Spring + Jackson:
#Entity
public class Car{
public String model;
#ManyToOne( fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
public Owner owner;
// [...]
}
#Entity
public class Owner{
public String name;
// [...]
}
Here it is the important part: now if I were using Jackson I would have create a CarView class:
public class CarView {
public static class Public {};
public static class Private extends Public {};
}
And with that I would have annotated Car.model with #JsonView(CarView.Public.class) and Car.owner with #JsonView(CarView.Private.class) and then just annotate with the same annotations my methods in the REST controller in order to tell Jackson which view I want to use:
#RequestMapping("/car/{id}")
#JsonView(CarView.Public.class)
public Car getPublic(#PathVariable int id) { /*...*/ }
#RequestMapping("/car/private/{id}")
#JsonView(CarView.Private.class)
public Car getPrivate(#PathVariable int id) { /*...*/ }
Can I accomplish the same result using Quarkus & JSON-B?
Quarkus supports usage of JsonViews to manage the serialization/deserialization of request/response.
(Just to let you know, sadly it's not supported (yet) by smallry-openapi implementation, so even if the serialization would work, you'll still see the full model in swagger.)
An example of usage, taken from official guide https://quarkus.io/guides/resteasy-reactive#jsonview-support:
JAX-RS methods can be annotated with #JsonView in order to customize the serialization of the returned POJO, on a per method-basis. This is best explained with an example.
A typical use of #JsonView is to hide certain fields on certain methods. In that vein, let’s define two views:
public class Views {
public static class Public {
}
public static class Private extends Public {
}
}
Let’s assume we have the User POJO on which we want to hide some field during serialization. A simple example of this is:
public class User {
#JsonView(Views.Private.class)
public int id;
#JsonView(Views.Public.class)
public String name;
}
Depending on the JAX-RS method that returns this user, we might want to exclude the id field from serialization - for example you might want an insecure method to not expose this field. The way we can achieve that in RESTEasy Reactive is shown in the following example:
#JsonView(Views.Public.class)
#GET
#Path("/public")
public User userPublic() {
return testUser();
}
#JsonView(Views.Private.class)
#GET
#Path("/private")
public User userPrivate() {
return testUser();
}
When the result the userPublic method is serialized, the id field will not be contained in the response as the Public view does not include it. The result of userPrivate however will include the id as expected when serialized.
Have you checked #JsonbVisibility or "Jsonb adapter" part in
https://javaee.github.io/jsonb-spec/users-guide.html annotation from Jsonb? I am afraid maybe there isn't a solution in Jsonb yet like #JsonView in Jackson. Jsonb adapter is configuration at bean level(you choose the Jsonb instance when you (de)serialize), not at view level.

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

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.

Spring Boot and Jackson: Define which fields to serialize on external class

I'm working on a rest api project with spring boot and hibernate, and I'm wondering on json serialization of RestController using Jackson.
Here is the problem: I use external hibernate entities class defined in a library I cannot edit. This classes are very complex and define lot of field I'm not interested in when I return the object with the rest api.
Actually, I've solved the problem wrapping the original class with a wrapper class that exposes only the values I want to return from the controller.
Eg:
original class
class AccountEntity {
///...
public String getName() {
return this.name;
}
/// ... Lot of code here
}
Wrapper class:
class AccountWrapper {
AccountEntity original;
public AccountWrapper(AccountEntity original) {
this.original = original;
}
public String getName() {
return this.original.getName();
}
}
and the use the Wrapper as following
#RestController("/api/user")
public class UsersController {
#GetMapping("/")
public AccountWrapper getUser() {
AccountEntity account = //get account in some way
AccountWrapper accountWrapper = new AccountWrapper(account);
return accountWrapper;
}
}
The method works well, but it's not very clean and makes stuff more complex (e.g., when I have to return lists), because I always have to wrap the original class.
I didn't found a method to make me able to specify which fields I want to serialize without modify (and I cannot) the original class.
Any help?
Instead of using a wrapper class, create a DTO object for the rest API that will be leaner than the DB entity and a trasformer to create DTO from entity (and vice a verce)
The difference from using a wrapper here is that the DB entity is not part of the DTO, and thus does not need to be serialized on the response.
The big advantage here is that you separate the DB layer from the API layer, which makes it more flexible and easy to manage.
you can read more about this pattern here
Apparently, you can use Jackson Mixins to annotate a class with Jackson annotations.
See this answer for example.
The idea is to create an class with the annotations you want and to use objectMapper.getSerializationConfig().addMixInAnnotations() to register the MixIn with your class.
For example:
//Class you don't controll
public class User {
private String name;
private String password; //attribute we want to omit
//... getters and setters
}
public abstract class UserMixIn {
#JsonIgnore String getPassword();
}
objectMapper.addMixInAnnotations(User.class, UserMixIn.class);
Hope it helps,

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.

Categories

Resources