Dynamic property filter using Jackson 2 - java

I am building a REST service platform in which we have to support following query pattern:
format=summary which means we have to deserialize only the POJO attributes annotated with our custom annotation #Summary
format=detail which means we have to deserialize only the POJO attributes annotated with our custom annotation #Detail
fields=prop1,prop2,prop3 which means we have to deserialize the POJO attributes provided in the query.
I am using Jackson 2 (v2.3.0) I tried followings:
Developed custom annotations (#Summary and #Detail)
Developed a JsonFilter (code shown below) and annotated #JsonFilter to my POJO classes.
Location.java
#JsonFilter("customFilter")
public class Location implements Serializable {
#Summary
#Detail
private String id;
#Summary
#Detail
private String name;
#Summary
#Detail
private Address address;
// ... getters n setters
Address.java
#JsonFilter("customFilter")
public class Address implements Serializable {
#Detail
private String addressLine1;
#Detail
private String addressLine2;
#Detail
private String addressLine3;
#Detail
#Summary
private String city;
#Summary
#Detail
private String postalCode;
// ... getters n setters
CustomFilter.java
public class CustomFilter extends SimpleBeanPropertyFilter {
#Override
protected boolean include(BeanPropertyWriter propertyWriter) {
if(logger.isDebugEnabled()) {
logger.debug("include(BeanPropertyWriter) method called..");
}
return this.deserialize(propertyWriter);
}
#Override
protected boolean include(PropertyWriter propertyWriter) {
if(logger.isDebugEnabled()) {
logger.debug("include(PropertyWriter) method called..");
}
return this.deserialize((BeanPropertyWriter) propertyWriter);
}
private boolean deserialize(final BeanPropertyWriter beanPropertyWriter) {
final String format = (String) AppContext.get("format");
if(StringUtils.isNotBlank(format)) {
return deserializeForAnnotation(format, beanPropertyWriter);
} else {
#SuppressWarnings("unchecked")
final Set<String> fieldNames = (Set<String>) AppContext.get("fieldNames");
if(null != fieldNames && !fieldNames.isEmpty()) {
final String serializedPropertyName = beanPropertyWriter.getSerializedName().getValue();
return fieldNames.contains(serializedPropertyName);
}
}
return false;
}
private boolean deserializeForAnnotation(final String format, final BeanPropertyWriter beanPropertyWriter) {
if(StringUtils.equalsIgnoreCase(format, "detail")) {
return (null != beanPropertyWriter.getAnnotation(Detail.class));
} else if(StringUtils.equalsIgnoreCase(format, "summary")) {
return (null != beanPropertyWriter.getAnnotation(Summary.class));
}
return false;
}
}
I am getting intended result with annotations, however my 3rd requirement to support property names to filter is not working.
Could someone help; if possible with some examples?

I wrote a library called Squiggly Filter, which selects fields based on a subset of the Facebook Graph API syntax. For example, to select the zipCode of the address field of the user object, you would use the query string ?fields=address{zipCode}. One of the advantages of Squiggly Filter is that as long as you have access to the ObjectMapper that renders the json, you do not to have to modify the code of any of your controller methods.
Assuming, you are using the servlet API, you can do the following:
1) Register a filter
<filter>
<filter-name>squigglyFilter</filter-name>
<filter-class>com.github.bohnman.squiggly.web.SquigglyRequestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>squigglyFilter</filter-name>
<url-pattern>/**</url-pattern>
</filter-mapping>
2) Initialize the ObjectMapper
Squiggly.init(objectMapper, new RequestSquigglyContextProvider());
3) You can now filter your json
curl https://yourhost/path/to/endpoint?fields=field1,field2{nested1,nested2}
You can also select fields based on annotations as well.
More information on Squiggly Filter is available on github.

If you want to go down the route of having a custom object mappers for each set of fields then your best bet is to retain the created object mappers in a cache somewhere so that the next time a user requests the same fields the object mapper can be reused.
Your cache could be as simple as a Set<String,ObjectMapper>, with the key being the fields as passed in by the user.

Related

Is it possible to map a JPA/Hibernate Entity field to a field in another class without #Embeddable?

I have a very simple Entity (Person.java) that I am wanting to persist via JPA/Hibernate.
The Entity contains two fields: ID and Identification String.
The ID is a simple Integer, and is no problem. The Identification String is currently a String, but for various reasons, I want to instead use a wrapper class for String (IDString), where there are various validation methods among other things.
I am wondering how I can get JPA/Hibernate to use the wrapped string (inside the custom class IDString) when persisting the Person table in the database. I know this can probably be solved by letting the IDString be #Embeddable and then embed IDString in the Person entity with #Embedded, but I am looking for another method, mostly because IDString is in an entirely different package, and I am reluctant to have to go there and change stuff.
Googling, I found https://www.baeldung.com/hibernate-custom-types, but it seems to be mostly about more complicated cases, where you want to convert one class into another type, and I do feel that there is probably a smarter way that I am simply overlooking.
Here is the entity (in theory)
#Entity(name="Person")
#Table(name="DB_TABLE_PERSON")
public class Person implements Serializable {
#Id
Integer id;
// WHAT SHOULD I PUT HERE? I WANT TO SIMPLY USE THE STRING INSIDE IDSTRING AS THE FIELD TO PERSIST
IDString idString;
// getter and setter for ID.
public void getIdString() {
return idString.getValue();
}
public void setIdString(String in) {
idString.setValue(in);
}
}
And here is the class IDString (in theory):
public class IDString {
// I really want to be a POJO
private final String the_string;
public IdString(String input) {
if (isValid(input)) {
the_string = input;
} else {
throw new SomeCoolException("Invalid format of the ID String");
}
public boolean isValid(String input) {
// bunch of code to validate the input string
}
public String getValue() {
return the_string;
}
public void setValue(String input) {
if (isValid(input)) the_string = s;
else throw new SomeCoolException("Invalid format of the ID String");
}
I know that I could place the validation if the IDString inside the Entity, but the IDString will be used elsewhere (it's a general custom class), so I don't want to do that. Is there a simple way?
#Converter(autoApply=true) // autoApply is reasonable, if not use #Converter on field
public class IDStringConverter implements AttributeConverter<IDString,String> {
#Override
public String convertToDatabaseColumn(IDString attribute) {
return attribute != null ? attribute.getValue() : null;
}
#Override
public IDString convertToEntityAttribute(String dbData) {
return dbData != null ? new IDString(dbData) : null;
}
}
With this you should not need any other modifications in your code. One limitation of the AttributeConverter is that it maps from exactly 1 Java field to exactly 1 DB column. If you wanted to map to more columns (not the case here), you would need embeddables.
You could also put a #Column annotation on the getter:
#Entity
public class Person {
private final IdString idString = new IdString();
#Column(name = "ID_STRiNG")
public IdString getIdString() {
return idString.getValue();
}
public void setIdString(String input) {
idString.setValue(input);
}
Another solution could be to convert to/from IdString using #PostLoad and #PrePersit event handlers:
#Entity
public class Person {
#Column(name = "ID_STRiNG")
private String the_string; // no getters & setters
#Transient
private final IdString idString = new IdString();
#PostLoad
public void postLoad() {
idString.setValue(the_string);
}
#PrePersist
public void prePersist() {
the_string = idString.getValue();
}
// getters & setters for idString

Micronaut fails to load and use my `TypeConverter` implementation

I have a controller method, that takes in a POJO.
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Dto {
private LocalDate endDate;
private String token;
private TransactionType type;
}
Transaction type is a simple enum, but I want to use a custom conversion from the inbound value to the transaction type.
#Slf4j
#Controller("/api/transactions")
public class IssuerTransactionController {
#Get(value = "/{?tr*}", produces = APPLICATION_JSON)
public List<String> get(Dto tr) {
return new ArrayList<>();
}
}
I have written a converter:
#Slf4j
#Singleton
public class TransactionTypeConverter implements TypeConverter<String, TransactionType> {
#Override
public Optional<TransactionType> convert(String value, Class<TransactionType> targetType, ConversionContext context) {
return Arrays.stream(TransactionType.values())
.filter(txnType -> StringUtils.equals(txnType.getTransactionType(), value) || StringUtils.equals(txnType.name(), value))
.findFirst();
}
}
Micronaut is not using the type converter to transform the inbound value?
Is some special registration process needed in order for Micronaut to know that it should be using the converter?
If I add a constructor to TransactionTypeConverter I can see that the class is never actually created by Micronaut at all.
If I add it as a regular dependency to the controller, it's loaded (no surprise there), but still not used. Is there a step I am missing?
Seems you are using the Binding from Multiple Query values functionality which under the hood is just creating the map of the query parameters you passed in and uses the Jackson to convert the map into your own POJO. So it does not rely on the system converters but only on the Jackson itself.
What you can do is just use Jacksons #JsonCreator annotation to customize the conversation.
Something like this should work.
public enum TransactionType {
A ("A"),
B ("B");
private final String transactionType;
TransactionType(String transactionType){
this.transactionType = transactionType;
}
public String getTransactionType() {
return transactionType;
}
#JsonCreator
public static TransactionType forValue(Collection<String> values) {
if(values == null || values.isEmpty()){
return null;
}
String value = values.get(0);
return Arrays.stream(TransactionType.values())
.filter(txnType -> StringUtils.equals(txnType.getTransactionType(), value) || StringUtils.equals(txnType.name(), value))
.findFirst().orElse(null);
}
}

Create #ToLowerCase annotation to convert String value to lower case

We have SpringBoot application.
For our pojo's we want to create a custom #ToLowerCase annotation which converts the field variable value to lower case.
Eg:
#Data
Employee {
private String name;
#ToLowerCase
private String emailId;
private String gender;
private String phoneNumber;
}
So my custom #ToLowerCase annotation should convert emailId to lower case.
We want to use this annotation on all kind of Pojos, whether it is rest request pojo or JPA entity pojo.
I have gone through posts on many forums but didn't get any appropriate solution for same.
Is it possible to create such annotation in Spring Boot? If yes then how?
Kindly help
Thanks
Create a custom converter: ToLowerCaseConverter.
public class ToLowerCaseConverter extends StdConverter<String, String> {
#Override
public String convert(String value) {
if (value == null){
return null;
}
return value.toLowerCase();
}
}
After create a new annotation: ToLowerCase. It works for both incoming and outgoing Strings (#JsonDeserialize/#JsonSerialize).
#Retention(RetentionPolicy.RUNTIME)
#JacksonAnnotationsInside
#JsonSerialize(converter = ToLowerCaseConverter.class)
#JsonDeserialize(converter = ToLowerCaseConverter.class)
public #interface ToLowerCase {
}
Finally, your example will work as intended:
#Data
Employee {
#ToLowerCase
private String emailId;
}

Remove a field from entity when passing to Jersey + Jackson

I'm using Jersey + Jackson (built in in Dropwizard) to create a series of web services. I directly map objects in Json by passing them to Response object in Jersey:
myObject object = new myObject(fields...);
return Response.ok(object).build();
Fields are correctly annotated in myObject class with JsonProperty("fieldName").
But, in case I have a field that I need to store to database (ex: a password hash), but I do not want to pass in request responses, how can I remove that field when passing the entity to Response object?
I can't annotate the field with JsonIgnore, otherwise that field won't be serialized at all when I map the Json to database (ElasticSearch).
One option is to simply set the field to null. To configure the ObjectMapper to ignore the field in the JSON altogether when the field is null, you can just do
#Override
public void run(YourConfiguration configuration,
Environment environment) throws Exception {
...
environment.getObjectMapper().setSerializationInclusion(Include.NON_NULL);
}
As an aside, this security reason a one of the reasons to use DTOs (data transfer objects), an extra entity "view" layer that separates the representation we send out from the persistence layer (db entity object). It may seem redundant to create another object with the same/similar attributes, but the security padding is worth it.
Also, though not an official release yet, Dropwizard 0.8.0 uses Jersey 2, which introduced Entity Filtering, which allows us filter out the data we don't want sent out, without the need to create DTOs. Just thought I'd mention it.
You should use both JsonIgnore and JsonProperty to achieve this.
public class User {
private String name;
private String password;
#JsonProperty
public void setPassword(String password) {
this.password = password;
}
#JsonIgnore
public String getPassword() {
return this.password;
}
}
#JsonProperty on setter method will be used in serialization & JsonIgnore on getter method will be used in deserialization.
Actually #Manikandan answer should work for you. See Only using #JsonIgnore during serialization, but not deserialization
In worst case you may try to implement JsonSerializer.
public class MyObjectSerializer extends JsonSerializer<MyObject> {
#Override
public void serialize(MyObject value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeString(value.getField1());
jgen.writeString(value.getField2());
/* and not include field that you don't want to serialize */
jgen.writeEndObject();
}
}
#JsonSerialize(using = MyObjectSerializer.class)
public class MyObject {
String field1;
Integer field2;
String fieldNotToBeSerialized;
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
public Integer getField2() {
return field2;
}
public void setField2(Integer field2) {
this.field2 = field2;
}
public String getFieldNotToBeSerialized() {
return fieldNotToBeSerialized;
}
public void setFieldNotToBeSerialized(String fieldNotToBeSerialized) {
this.fieldNotToBeSerialized = fieldNotToBeSerialized;
}
}

How to apply Single annotation on multiple variables?

I am rookie in Java Annotation and have been searching for applying single annotation on multiple variable simultaneously.
Code:
#Document(collection = "users")
public class User {
private ObjectId id;
#NotNull
private String email;
private String imageURL;
private String authToken;
private Date createdDate;
private Date updateDate;
private boolean isActivated;
private int credits;
.....getter/Setter Method
I want to apply #NotNull property on email, imageURL and authToken too. I can do it by writing #NotNull to each variable but not preferring. How to do it?
#NotNull annotation can be applied at element not at group of elements.
JavaDoc: The annotated element must not be null. Accepts any type.
If you really want to get away with boiler plate code, you can use frameworks like Lombok which can help you to certain extent.
Link : http://projectlombok.org/features/Data.html
OR you can use reflection to validate all the method.
for (Field f : obj.getClass().getDeclaredFields()) {
f.setAccessible(true); // optional
if (f.get(obj) == null) {
f.set(obj, getDefaultValueForType(f.getType()));
// OR throw error
}
}
Java does not support multiple annotation of this type. But you can write something like this
Create a class with annotated field.
Create setters and getters to access the field.
Create all your name,email field as instance of this class.
This way fields will implicitly annotated as NotNull.
public class NotNullString {
#NotNull
String str;
public void set(String str)
{
this.str = str;
}
public String get()
{
return this.str;
}
}
NotNullString name;
NotNullString email;

Categories