I have to build an json with mandatory fields, defaulted fields (if value is not set), and optional (not mandatory) fields. This class is used for de- and serialization. The final json should look different, if e.g. some not mandatory field is not set or a mandatory value is not set, but should be defaulted.
#JsonSnakeCase
#JsonIgnoreProperties(ignoreUnknown=true)
public class TryoutJson {
#JsonCreator
public TryoutJson(
#NotBlank
#JsonProperty("mandatory")
final String mandatory,
#...WHAT HERE TO set to default?
#JsonProperty("valueDefaultedTo5IfNotSet")
final String valueDefaultedTo5IfNotSet,
#... What here to set just if value is given?
#JsonProperty("thisFieldIsNotMAndatory")
final String thisFieldIsNotMAndatory,
) throws IllegalArgumentException
{
//....
}
}
For example this json strings should be created in the used constructor:
{"mandatory":"mandatoryValue","valueDefaultedTo5IfNotSet":"5"} is the same like {"mandatory":"mandatoryValue"}
or
{"mandatory":"mandatoryValue","valueDefaultedTo5IfNotSet":"2","thisFieldIsNotMAndatory":"ValueXY"}
Is it possible to have only one constructor for all this settings? I dont want do build several constructors for each possible variant.
If important, this imports are used:
import org.hibernate.validator.constraints.NotBlank;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
Related
I'm having a class which used to convert POJO to json string and compare it with another json string(read from logs). Recently we added 2 new boolean parameters to json and POJO and now the order of those 2 newly added variables is getting vary time to time.
I know by adding #JsonPropertyOrder(alphabetic = true) or #JsonPropertyOrder({ "att1", "att2", "att3" }) annotations, we can ensure the order. But I just want to know, how we got the same output earlier(with same order. Note: we had that code since 5years back)
Please find the sample code :
package com.....api;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
public class TestClass {
public String writeAsJson(Object object) throws JsonProcessingException {
JsonMapper.Builder jsonMapperBuilder = JsonMapper.builder();
jsonMapperBuilder.addModule(new Jdk8Module())
.addModule((new JavaTimeModule()))
.addModule((new SimpleModule()))
.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
.disable(new SerializationFeature[]{SerializationFeature.WRITE_DATES_AS_TIMESTAMPS})
.enable(new MapperFeature[]{MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS})
.disable(new MapperFeature[]{MapperFeature.DEFAULT_VIEW_INCLUSION})
.disable(new DeserializationFeature[]{DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES});
ObjectMapper objectMapper = jsonMapperBuilder.build();
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
}
}
As per I checked, we can not ensure the order of the variables in the POJO when it serializing and deserializing json<--> POJO. But I'm curious how we got the same output for such a long period and suddenly how it fail after adding 2 new boolean variables.
I have a REST interface that returns an object. One of the fields in that object is an object from a third party jar. The third party jar object contains fields marked #JsonProperty from Jackson 1+. We are using Jackson 2+ and these annotations aren't being picked up.
I tried creating a MixIn, but I'm still not getting the correct property names.
import org.codehaus.jackson.annotate.JsonProperty;
public class ThirdPartyObject {
#JsonProperty("lastName")
public String ln;
...
}
import com.fasterxml.jackson.annotation.JsonProperty;
public interface NewObject {
#JsonProperty("lastName")
abstract String getLastName();
}
In my test, objectMapper is initialized with:
objectMapper.setMixIns(ImmutableMap.<Class<?>, Class<?>>of(ThirdPartyObject.class, NewObject.class));
It is returning {"ln": "Smith"}
when I'm expecting {"lastName": "Smith"}
I like to make my objects immutable based on this article (Why objects must be immutable).
However, I am trying to parse an object using Jackson Object Mapper. I was initially getting JsonMappingException: No suitable constructor found for type [simple type, class ]: cannot instantiate from JSON object.
I could fix it as mentioned here, by providing a default constructor and making my fields non-final.
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
#AllArgsConstructor
// #NoArgsConstructor(access = AccessLevel.PRIVATE)
#Builder
#Data
public class School {
#NonNull
private final String schoolId;
#NonNull
private final String schoolName;
}
What is a good programming style that I should follow to overcome this problem? Is the only way around is to make my objects mutable?
Can I use a different mapper that does not use the default constructor?
You can use a Jackson factory (method annotated with #JsonCreator) that reads fields off a map and calls your non-default constructor:
class School {
//fields
public School(String id, String name) {
this.schoolId = id;
this.schoolName = name;
}
#JsonCreator
public static School create(Map<String, Object> object) {
return new School((String) object.get("schoolId"),
(String) object.get("schoolName"));
}
//getters
}
Jackson will call the create method with a Map version of the json. And this effectively solves the problem.
I believe your question looks for a Jackson solution, rather than a new pattern/style.
TL;DR: using lombok and avoiding a default constructor
make immutable data class using #Value
annotate all your fields with #JsonProperty("name-of-property")
add lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty to your lombok.config to copy those to generated constructors
create an all-args constructor annotated with #JsonCreator
example:
#Value
#AllArgsConstructor(onConstructor_ = #JsonCreator)
class School {
#JsonProperty("schoolId")
String schoolId;
#JsonProperty("schoolName")
String schoolName;
}
long answer
There is an imo better alternative to a static factory method annotated with #JsonCreator, and that is having a constructor for all Elements (as is required for immutable classes anyway). Annotate that with #JsonCreator and also annotate all parameters with #JsonProperty like this:
class School {
//fields
#JsonCreator
public School(
#JsonProperty("id") String id,
#JsonProperty("name") String name) {
this.schoolId = id;
this.schoolName = name;
}
//getters
}
Those are the options the #JsonCreator annotation gives you. It describes them like this in its documentation:
Single-argument constructor/factory method without JsonProperty annotation for the argument: if so, this is so-called "delegate creator", in which case Jackson first binds JSON into type of the argument, and then calls creator. This is often used in conjunction with JsonValue (used for serialization).
Constructor/factory method where every argument is annotated with either JsonProperty or JacksonInject, to indicate name of property to bind to
You might not even need to explicitly specify the parameter name under some circumstances. The documentation regarding that for #JsonCreator further states:
Also note that all JsonProperty annotations must specify actual name (NOT empty String for "default") unless you use one of extension modules that can detect parameter name; this because default JDK versions before 8 have not been able to store and/or retrieve parameter names from bytecode. But with JDK 8 (or using helper libraries such as Paranamer, or other JVM languages like Scala or Kotlin), specifying name is optional.
Alternatively this will also work nicely with lombok version 1.18.3 or up, where you can add lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty to your lombok.config and therefore have it copy the JsonProperty annotations to the constructor, given that you do annotate all fields with it (which one should do anyway imo). To put the #JsonCreator-annotation on the constructor, you can use the experimental onX feature. Using lombok's #Value for immutable data classes, your DTO then might just look like this (untested):
#Value
//#AllArgsConstructor(onConstructor = #__(#JsonCreator)) // JDK7 or below
#AllArgsConstructor(onConstructor_ = #JsonCreator) // starting from JDK8
class School {
#JsonProperty("schoolId")
String schoolId;
#JsonProperty("schoolName")
String schoolName;
}
Is there a simple to way to apply a custom logic transformation on the value of a specific key during bean deserialization ?
Concrete example, i receive the following json:
{password: "1234"}
and want a special hash function applied to the password value when deserializing :
User [password: "6265b22b66502d70d5f004f08238ac3c"]
I know i could use a setter User.setPassword() and apply the hash transformation here but the transformation need to make use of "Service" classes which are not available in the context of an Entity (bad use of dependency injection..). This transformation must be made outside of the entity code.
Using a custom Deserializer for User class seems to be overkill for just one attribute too.
Use the annotation to define a custom serializer/deserializer for the bean propery.
Here is an example of a bean where you define your custom serializer/deserializer classes:
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
public class User {
#JacksonXmlProperty
private String login;
#JacksonXmlProperty
#JsonSerialize(using=your.class.package.PasswordSerializer.class)
#JsonDeserialize(using=your.class.package.PasswordDeserializer.class)
private String password;
// ...
}
And here the custo, serializer example:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
import java.io.IOException;
public class PasswordSerializer extends JsonSerializer<String> {
#Override
public void serialize(String s, JsonGenerator jg, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
// do your staff here.
}
}
You just need to implement an interface, and you could do in the proper package.
The deserializer is similar.
I have the typical example, where a POST has many TAGS, and a TAG has many POSTs.
Instead of using a typical #ManyToMany, I use a domain object in the middle, called TAGPOST, which also allows me to have useful data in there, such as when a post was tagged with a given tag, etc. Each POST, and TAG resp, is in a #OneToMany relationship with a TAGPOST.
The specific requirement is that a post cannot have the same tag included twice, therefore the TAGPOST.post and TAGPOST.tag pair must always be unique. Normally, I would do that by making a composite primary key pair in the table, responsible for storing TAGPOST objects.
AFAIK, there is no way to express this unique constraint. I have marked jpa.ddl=update, which means that every time I move the application to a new environment, I will have to go and manually fix this in the DB. This is very inconvenient, and error prone, especially when unit testing, because then the database is created and dropped more or less in every iteration.
I was even thinking to do the check manually on #PrePersist, or even move the check in a business layer, say, create a PostService.
What do I do? Am I missing something that Play has by default? Some clever annotation to express the uniqueness of the #ManyToOne properties of the TAGPOST class?
FYI: I am using Play 1.2.5
EDIT: The TAGPOST class looks like this:
#Entity
public class TagPost extends Model {
#ManyToOne
public Tag tag;
#ManyToOne
public Post post;
public Date dateAdded;
...
}
I wrote a custom Check for db uniqueness. Maybe you should customize it.
DBUnique.java
package models.check;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import net.sf.oval.configuration.annotation.Constraint;
import play.db.jpa.GenericModel;
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD, ElementType.PARAMETER })
#Constraint(checkWith = DbUniqueCheck.class)
public #interface DBUnique {
String message() default DbUniqueCheck.mes;
Class<? extends GenericModel> modelClass();
String field() default ""; // field name will be used
}
DbUniqueCheck.java
package models.check;
import net.sf.oval.Validator;
import net.sf.oval.configuration.annotation.AbstractAnnotationCheck;
import net.sf.oval.context.FieldContext;
import net.sf.oval.context.OValContext;
import org.apache.commons.lang.StringUtils;
import play.db.jpa.GenericModel.JPAQuery;
#SuppressWarnings("serial")
public class DbUniqueCheck extends AbstractAnnotationCheck<DBUnique> {
final static String mes = "validation.dbunique";
DBUnique dbUnique;
#Override
public void configure(DBUnique dBUnique) {
this.dbUnique = dBUnique;
setMessage(dBUnique.message());
}
public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) {
try {
String field = dbUnique.field();
if (field == null || field.isEmpty()) {
field = ((FieldContext) context).getField().getName();
}
JPAQuery q = (JPAQuery) dbUnique.modelClass().getMethod("find", String.class, Object[].class)
.invoke(null, "by" + StringUtils.capitalize(field), new Object[] { value });
return q.first() == null;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
usage : link to gist
it simply checks the given field for given class instance is unique in db. Maybe you should make something like these..