I would like to deserialize JSON of this structure:
{
"employee_pricing_type":"COMPUTE_BY_OWN_RATE",
"employee_rate":10,
"customer_pricing_type":"COMPUTE_BY_OWN_RATE",
"customer_rate":200
}
I have such POJO to create price setting from a HTTP request:
public class ObjectPricingSetting {
#JsonProperty("pricing_type") // describes output
private final ObjectPricingType pricingType;
#JsonProperty("own_rate") // describes output
private final BigDecimal ownRate;
public ObjectPricingSetting(final ObjectPricingType pricingType, final BigDecimal ownRate) {
AssertUtils.notNull(pricingType, "pricingType");
this.pricingType = pricingType;
if (ownRate != null) {
AssertUtils.isGtZero(ownRate, "ownRate");
this.ownRate = ownRate;
} else {
this.ownRate = null;
}
}
public ObjectPricingType getPricingType() {
return pricingType;
}
public BigDecimal getOwnRate() {
return ownRate;
}
}
this is DTO:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ObjectPricingCommand extends BaseDto<ObjectId> {
#JsonProperty(value = "employee_pricing_setting")
private ObjectPricingSetting employeePricingSetting;
#JsonProperty(value = "customer_pricing_setting")
private ObjectPricingSetting customerPricingSetting;
}
I would like to create these two instances of ObjectPricingSetting with #JsonCreator.
Q: How should I anotate #JsonProperty parameter in ObjectPricingSetting constructor to recognize what JSON value should use to create these two instances?
You can use #JsonUnwrapped with a prefix in your parent class:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ObjectPricingCommand extends BaseDto<ObjectId> {
#JsonUnwrapped(prefix = "employee_")
private ObjectPricingSetting employeePricingSetting;
#JsonUnwrapped(prefix = "customer_")
private ObjectPricingSetting customerPricingSetting;
}
Then you can use the normal #JsonCreator/#JsonProperty in your nested DTO, without the prefix:
public class ObjectPricingSetting {
#JsonCreator
public ObjectPricingSetting(
#JsonProperty("pricing_type") final ObjectPricingType pricingType,
#JsonProperty("rate") final BigDecimal ownRate) {
...
Is it possible to use multiple #JsonSubType annotations in a nested fashion?
For example, imagine the following classes:
#Data
#JsonSubTypeInfo(include=As.EXISTING_PROPERTY, property="species", use=Id.NAME, visible=true)
#JsonSubTypes({
#Type(name="Dog", value=Dog.class)
#Type(name="Cat", value=Cat.class)
})
public abstract class Animal {
private String name;
private String species;
}
#Data
#JsonSubTypeInfo(include=As.EXISTING_PROPERTY, property="breed", use=Id.NAME, visible=true)
#JsonSubTypes({
#Type(name="Labrador", value=Labrador.class)
#Type(name="Bulldog", value=Bulldog.class)
})
public abstract class Dog extends Animal {
private String breed;
}
#Data
public class Cat extends Animal {
private boolean lovesCatnip;
}
#Data
public class Labrador extends Dog {
private String color;
}
#Data
public class Bulldog extends Dog {
private String type; // "frenchy", "english", etc..
}
If I use an object mapper, I can successfully map a Bulldog to JSON, however, when trying to read the resulting JSON and read it back in, I get an error like the following:
Can not construct instance of com.example.Dog abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
Is it possible to get Jackson to work with such subtyping? Would I need to create a custom deserializer for each subclass?
EDIT:
I've modified the classes above slightly from the original posting. I added a Cat class and had that and Dog extend from Animal.
Here is a sample JSON that can be created using the ObjectMapper::writeValueAsString:
{
"name": null,
"species": "Dog",
"breed": "Bulldog",
"type": "B-Dog"
}
The following works if I use #JsonTypeInfo and a similar set up to yours. Maybe your problem is in your deserialization code, so have a look at this:
public class MyTest {
#Test
public void test() throws IOException {
final Bulldog bulldog = new Bulldog();
bulldog.setBreed("Bulldog");
bulldog.setType("B-Dog");
final ObjectMapper om = new ObjectMapper();
final String json = om.writeValueAsString(bulldog);
final Dog deserialized = om.readValue(json, Dog.class);
assertTrue(deserialized instanceof Bulldog);
}
#JsonTypeInfo(include = As.EXISTING_PROPERTY, property = "species", use = Id.NAME, visible = true)
#JsonSubTypes({
#Type(name = "Dog", value = Dog.class),
#Type(name = "Cat", value = Cat.class)
})
public static abstract class Animal {
private String name;
private String species;
}
#JsonTypeInfo(include = As.EXISTING_PROPERTY, property = "breed", use = Id.NAME, visible = true)
#JsonSubTypes({
#Type(name = "Labrador", value = Labrador.class),
#Type(name = "Bulldog", value = Bulldog.class)
})
public static abstract class Dog {
private String breed;
public String getBreed() {
return breed;
}
public void setBreed(final String breed) {
this.breed = breed;
}
}
public static abstract class Cat {
private String name;
}
public static class Labrador extends Dog {
private String color;
public String getColor() {
return color;
}
public void setColor(final String color) {
this.color = color;
}
}
public static class Bulldog extends Dog {
private String type; // "frenchy", "english", etc..
public String getType() {
return type;
}
public void setType(final String type) {
this.type = type;
}
}
}
EDITed for the updated question: If you can use the same property (in the following code the hidden property "#class") for the inheritance hierarchy, it works:
#Test
public void test() throws IOException {
final Bulldog bulldog = new Bulldog();
// bulldog.setSpecies("Dog");
// bulldog.setBreed("Bulldog");
bulldog.setType("B-Dog");
final ObjectMapper om = new ObjectMapper();
final String json = om.writeValueAsString(bulldog);
final Animal deserialized = om.readValue(json, Animal.class);
assertTrue(deserialized instanceof Bulldog);
}
#JsonTypeInfo(include = As.PROPERTY, use = Id.CLASS, visible = false)
#JsonSubTypes({
#Type(Dog.class),
#Type(Cat.class)
})
public static abstract class Animal {
}
#JsonTypeInfo(include = As.PROPERTY, use = Id.CLASS, visible = false)
#JsonSubTypes({
#Type(name = "Labrador", value = Labrador.class),
#Type(name = "Bulldog", value = Bulldog.class)
})
public static abstract class Dog
extends Animal {
}
If you want to set the animal type (e.g. to compute species, breed etc.), you could also use this setup:
#Test
public void test() throws IOException {
final Bulldog bulldog = new Bulldog();
bulldog.setAnimalType("Bulldog");
// bulldog.setSpecies("Dog");
// bulldog.setBreed("Bulldog");
bulldog.setType("B-Dog");
final ObjectMapper om = new ObjectMapper();
final String json = om.writeValueAsString(bulldog);
System.out.println(json);
final Animal deserialized = om.readValue(json, Animal.class);
assertTrue(deserialized instanceof Bulldog);
}
#JsonTypeInfo(include = As.EXISTING_PROPERTY, property = "animalType", use = Id.NAME, visible = true)
#JsonSubTypes({
#Type(Dog.class)
})
public static abstract class Animal {
private String animalType;
public String getAnimalType() {
return animalType;
}
public void setAnimalType(final String animalType) {
this.animalType = animalType;
}
}
#JsonTypeInfo(include = As.EXISTING_PROPERTY, property = "animalType", use = Id.NAME, visible = true)
#JsonSubTypes({
#Type(value = Bulldog.class)
})
public static abstract class Dog
extends Animal {
}
#JsonTypeName("Bulldog")
public static class Bulldog extends Dog {
private String type; // "frenchy", "english", etc..
public String getType() {
return type;
}
public void setType(final String type) {
this.type = type;
}
}
I was able to solve this such that the following JSON translates to a Bulldog object:
{
"species": "Dog",
"breed": "Bulldog",
"name": "Sparky",
"type": "English"
}
I used the following code to do this:
ObjectMapper om = new ObjectMapper();
om.addHandler(new DeserializationProblemHandler() {
#Override
public Object handleMissingInstantiator(DeserializationContext ctxt, Class<?> instClass, JsonParser p, String msg) throws IOException {
JsonNode o = p.readValueAsTree();
JsonNode copy = o.deepCopy();
JsonNode species = o.get("species");
if (species != null) {
Class<? extends Animal> clazz;
switch (species.asText()) {
case "Dog":
clazz = Dog.class;
break;
case "Cat":
clazz = Cat.class;
break;
default:
return NOT_HANDLED;
}
JsonParser parser = new TreeTraversingParser(copy, p.getCodec());
parser.nextToken(); // without this an error is thrown about missing "breed" type
return ctxt.readValue(parser, clazz);
}
return NOT_HANDLED;
}
});
I believe there's probably a better way to find the typed class (I noticed that the there is a cache in one of the inputs to the handleMissingInstantiator method that contains all of the relevant types, that can probably be used to find the type based on name instead of hardcoding values as I'm doing.
I am trying to override the #AdminPresentation of the following attribute defined in ProductImpl:
#Column(name = "DISPLAY_TEMPLATE")
#AdminPresentation(friendlyName = "ProductImpl_Product_Display_Template",
group = GroupName.Advanced)
protected String displayTemplate;
Currently, it is displayed as a text field by default as there is no fieldType attribute provided. But I want to display a dropdown select menu with predefined values such as Product and Plan. Here is what I've tried so far:
I've created a class DisplayTemplateType that implements BroadleafEnumerationType and defined PLAN and PRODUCT enums. Here is the code of that class:
public class DisplayTemplateType implements Serializable, BroadleafEnumerationType {
private static final long serialVersionUID = 7761108654549553693L;
private static final Map<String, DisplayTemplateType> TYPES = new LinkedHashMap<String, DisplayTemplateType>();
public static final DisplayTemplateType PLAN = new DisplayTemplateType("PLAN", "PLAN");
public static final DisplayTemplateType PRODUCT = new DisplayTemplateType("PRODUCT", "PRODUCT");
public static DisplayTemplateType getInstance(final String type) {
return TYPES.get(type);
}
private String type;
private String friendlyType;
public DisplayTemplateType() {
//do nothing
}
public DisplayTemplateType(final String type, final String friendlyType) {
this.friendlyType = friendlyType;
setType(type);
}
#Override
public String getType() {
return type;
}
#Override
public String getFriendlyType() {
return friendlyType;
}
private void setType(final String type) {
this.type = type;
if (!TYPES.containsKey(type)) {
TYPES.put(type, this);
} else {
throw new RuntimeException("Cannot add the type: (" + type + "). It already exists as a type via " + getInstance(type).getClass().getName());
}
}
// equals() and hashCode() implementation is removed for readability
}
Then in applicationContext-admin.xml file, I have added the following override properties:
<mo:override id="blMetadataOverrides">
<mo:overrideItem ceilingEntity="org.broadleafcommerce.core.catalog.domain.Product">
<mo:field name="displayTemplate">
<mo:property name="explicitFieldType" value="BROADLEAF_ENUMERATION"/>
<mo:property name="broadleafEnumeration" value="com.community.core.domain.DisplayTemplateType"/>
</mo:field>
</mo:overrideItem>
</mo:override>
But it didn't change anything. Am I missing something here?
Finally, after trying many things, I came up with a workaround. Instead of going with the XML based approach, I had to extend the ProductImpl class to override #AdminPresentation of its attributes. But for extending I needed to define an #Entity and as a result, I needed to create a useless table to bind to that entity. I know this is not the perfect approach but I couldn't find any better solution for this. Here is my code, so that someone might get help from it in the future:
#Entity
#Immutable
#AdminPresentationMergeOverrides({
#AdminPresentationMergeOverride(name = "displayTemplate", mergeEntries = {
#AdminPresentationMergeEntry(propertyType = PropertyType.AdminPresentation.FIELDTYPE, overrideValue = "BROADLEAF_ENUMERATION"),
#AdminPresentationMergeEntry(propertyType = PropertyType.AdminPresentation.BROADLEAFENUMERATION, overrideValue = "com.community.core.domain.DisplayTemplateType"),
#AdminPresentationMergeEntry(propertyType = PropertyType.AdminPresentation.REQUIREDOVERRIDE, overrideValue = "REQUIRED"),
#AdminPresentationMergeEntry(propertyType = PropertyType.AdminPresentation.DEFAULTVALUE, overrideValue = "PLAN")
})
})
public class CustomProduct extends ProductImpl {
private static final long serialVersionUID = -5745207984235258075L;
}
This is how it is displayed now:
I use Jackson 2.2.3 to serialize POJOs to JSON. Then I had the problem, that I couldn't serialize recursive structures...I solved this problem by using #JsonIdentityInfo => works great.
But, I don't want this annotation on the top of my POJO.
So my question is: Is there any other possibility to set the default behavior of my ObjectMapper to use the feature for every POJO?
So I want to transform this annotation code
#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="#id")
to something like
ObjectMapper om = new ObjectMapper();
om.setDefaultIdentityInfo(ObjectIdGenerators.IntSequenceGenerator.class, "#id");
Any ideas?
You can achieve that using the Jackson mix-in annotations or the Jackson annotation introspector.
Here is an example showing both methods:
public class JacksonJsonIdentityInfo {
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "#id")
static class Bean {
public final String field;
public Bean(final String field) {this.field = field;}
}
static class Bean2 {
public final String field2;
public Bean2(final String field2) {this.field2 = field2;}
}
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "#id2")
static interface Bean2MixIn {
}
static class Bean3 {
public final String field3;
public Bean3(final String field3) {this.field3 = field3;}
}
static class MyJacksonAnnotationIntrospector extends JacksonAnnotationIntrospector {
#Override
public ObjectIdInfo findObjectIdInfo(final Annotated ann) {
if (ann.getRawType() == Bean3.class) {
return new ObjectIdInfo(
PropertyName.construct("#id3", null),
null,
ObjectIdGenerators.IntSequenceGenerator.class,
null);
}
return super.findObjectIdInfo(ann);
}
}
public static void main(String[] args) throws JsonProcessingException {
final Bean bean = new Bean("value");
final Bean2 bean2 = new Bean2("value2");
final Bean3 bean3 = new Bean3("value3");
final ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(Bean2.class, Bean2MixIn.class);
mapper.setAnnotationIntrospector(new MyJacksonAnnotationIntrospector());
System.out.println(mapper.writeValueAsString(bean));
System.out.println(mapper.writeValueAsString(bean2));
System.out.println(mapper.writeValueAsString(bean3));
}
}
Output:
{"#id":1,"field":"value"}
{"#id2":1,"field2":"value2"}
{"#id3":1,"field3":"value3"}
After several months and a lot of research, I've implemented my own solution to keep my domain clear of jackson dependencies.
public class Parent {
private Child child;
public Child getChild(){return child;}
public void setChild(Child child){this.child=child;}
}
public class Child {
private Parent parent;
public Child getParent(){return parent;}
public void setParent(Parent parent){this.parent=parent;}
}
First, you have to declare each of your entities of the bidirectional relationship:
public interface BidirectionalDefinition {
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id", scope=Parent.class)
public interface ParentDef{};
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id", scope=Child.class)
public interface ChildDef{};
}
After that, the object mapper can be automatically configured:
ObjectMapper om = new ObjectMapper();
Class<?>[] definitions = BidirectionalDefinition.class.getDeclaredClasses();
for (Class<?> definition : definitions) {
om.addMixInAnnotations(definition.getAnnotation(JsonIdentityInfo.class).scope(), definition);
}
I have model class like this, for hibernate
#Entity
#Table(name = "user", catalog = "userdb")
#JsonIgnoreProperties(ignoreUnknown = true)
public class User implements java.io.Serializable {
private Integer userId;
private String userName;
private String emailId;
private String encryptedPwd;
private String createdBy;
private String updatedBy;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "UserId", unique = true, nullable = false)
public Integer getUserId() {
return this.userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
#Column(name = "UserName", length = 100)
public String getUserName() {
return this.userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
#Column(name = "EmailId", nullable = false, length = 45)
public String getEmailId() {
return this.emailId;
}
public void setEmailId(String emailId) {
this.emailId = emailId;
}
#Column(name = "EncryptedPwd", length = 100)
public String getEncryptedPwd() {
return this.encryptedPwd;
}
public void setEncryptedPwd(String encryptedPwd) {
this.encryptedPwd = encryptedPwd;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
#Column(name = "UpdatedBy", length = 100)
public String getUpdatedBy() {
return this.updatedBy;
}
public void setUpdatedBy(String updatedBy) {
this.updatedBy = updatedBy;
}
}
In Spring MVC controller, using DAO, I am able to get the object. and returning as JSON Object.
#Controller
public class UserController {
#Autowired
private UserService userService;
#RequestMapping(value = "/getUser/{userId}", method = RequestMethod.GET)
#ResponseBody
public User getUser(#PathVariable Integer userId) throws Exception {
User user = userService.get(userId);
user.setCreatedBy(null);
user.setUpdatedBy(null);
return user;
}
}
View part is done using AngularJS, so it will get JSON like this
{
"userId" :2,
"userName" : "john",
"emailId" : "john#gmail.com",
"encryptedPwd" : "Co7Fwd1fXYk=",
"createdBy" : null,
"updatedBy" : null
}
If I don't want to set encrypted Password, I will set that field also as null.
But I don't want like this, I dont want to send all fields to client side. If I dont want password, updatedby, createdby fields to send, My result JSON should be like
{
"userId" :2,
"userName" : "john",
"emailId" : "john#gmail.com"
}
The list of fields which I don't want to send to client coming from other database table. So it will change based on the user who is logged in. How can I do that?
I hope You got my question.
Add the #JsonIgnoreProperties("fieldname") annotation to your POJO.
Or you can use #JsonIgnore before the name of the field you want to ignore while deserializing JSON. Example:
#JsonIgnore
#JsonProperty(value = "user_password")
public String getUserPassword() {
return userPassword;
}
GitHub example
Can I do it dynamically?
Create view class:
public class View {
static class Public { }
static class ExtendedPublic extends Public { }
static class Internal extends ExtendedPublic { }
}
Annotate you model
#Document
public class User {
#Id
#JsonView(View.Public.class)
private String id;
#JsonView(View.Internal.class)
private String email;
#JsonView(View.Public.class)
private String name;
#JsonView(View.Public.class)
private Instant createdAt = Instant.now();
// getters/setters
}
Specify the view class in your controller
#RequestMapping("/user/{email}")
public class UserController {
private final UserRepository userRepository;
#Autowired
UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
#RequestMapping(method = RequestMethod.GET)
#JsonView(View.Internal.class)
public #ResponseBody Optional<User> get(#PathVariable String email) {
return userRepository.findByEmail(email);
}
}
Data example:
{"id":"5aa2496df863482dc4da2067","name":"test","createdAt":"2018-03-10T09:35:31.050353800Z"}
UPD: keep in mind that it's not best practice to use entity in response. Better use different DTO for each case and fill them using modelmapper
I know I'm a bit late to the party, but I actually ran into this as well a few months back. All of the available solutions weren't very appealing to me (mixins? ugh!), so I ended up creating a new library to make this process cleaner. It's available here if anyone would like to try it out: https://github.com/monitorjbl/spring-json-view.
The basic usage is pretty simple, you use the JsonView object in your controller methods like so:
import com.monitorjbl.json.JsonView;
import static com.monitorjbl.json.Match.match;
#RequestMapping(method = RequestMethod.GET, value = "/myObject")
#ResponseBody
public void getMyObjects() {
//get a list of the objects
List<MyObject> list = myObjectService.list();
//exclude expensive field
JsonView.with(list).onClass(MyObject.class, match().exclude("contains"));
}
You can also use it outside of Spring:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import static com.monitorjbl.json.Match.match;
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(JsonView.class, new JsonViewSerializer());
mapper.registerModule(module);
mapper.writeValueAsString(JsonView.with(list)
.onClass(MyObject.class, match()
.exclude("contains"))
.onClass(MySmallObject.class, match()
.exclude("id"));
Yes, you can specify which fields are serialized as JSON response and which to ignore.
This is what you need to do to implement Dynamically ignore properties.
1) First, you need to add #JsonFilter from com.fasterxml.jackson.annotation.JsonFilter on your entity class as.
import com.fasterxml.jackson.annotation.JsonFilter;
#JsonFilter("SomeBeanFilter")
public class SomeBean {
private String field1;
private String field2;
private String field3;
// getters/setters
}
2) Then in your controller, you have to add create the MappingJacksonValue object and set filters on it and in the end, you have to return this object.
import java.util.Arrays;
import java.util.List;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
#RestController
public class FilteringController {
// Here i want to ignore all properties except field1,field2.
#GetMapping("/ignoreProperties")
public MappingJacksonValue retrieveSomeBean() {
SomeBean someBean = new SomeBean("value1", "value2", "value3");
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("field1", "field2");
FilterProvider filters = new SimpleFilterProvider().addFilter("SomeBeanFilter", filter);
MappingJacksonValue mapping = new MappingJacksonValue(someBean);
mapping.setFilters(filters);
return mapping;
}
}
This is what you will get in response:
{
field1:"value1",
field2:"value2"
}
instead of this:
{
field1:"value1",
field2:"value2",
field3:"value3"
}
Here you can see it ignores other properties(field3 in this case) in response except for property field1 and field2.
Hope this helps.
We can do this by setting access to JsonProperty.Access.WRITE_ONLY while declaring the property.
#JsonProperty( value = "password", access = JsonProperty.Access.WRITE_ONLY)
#SerializedName("password")
private String password;
Add #JsonInclude(JsonInclude.Include.NON_NULL) (forces Jackson to serialize null values) to the class as well as #JsonIgnore to the password field.
You could of course set #JsonIgnore on createdBy and updatedBy as well if you always want to ignore then and not just in this specific case.
UPDATE
In the event that you do not want to add the annotation to the POJO itself, a great option is Jackson's Mixin Annotations. Check out the documentation
I've solved using only #JsonIgnore like #kryger has suggested.
So your getter will become:
#JsonIgnore
public String getEncryptedPwd() {
return this.encryptedPwd;
}
You can set #JsonIgnore of course on field, setter or getter like described here.
And, if you want to protect encrypted password only on serialization side (e.g. when you need to login your users), add this #JsonProperty annotation to your field:
#JsonProperty(access = Access.WRITE_ONLY)
private String encryptedPwd;
More info here.
If I were you and wanted to do so, I wouldn't use my User entity in Controller layer.Instead I create and use UserDto (Data transfer object) to communicate with business(Service) layer and Controller.
You can use Apache BeanUtils(copyProperties method) to copy data from User entity to UserDto.
I have created a JsonUtil which can be used to ignore fields at runtime while giving a response.
Example Usage :
First argument should be any POJO class (Student) and ignoreFields is comma seperated fields you want to ignore in response.
Student st = new Student();
createJsonIgnoreFields(st,"firstname,age");
import java.util.logging.Logger;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectWriter;
import org.codehaus.jackson.map.ser.FilterProvider;
import org.codehaus.jackson.map.ser.impl.SimpleBeanPropertyFilter;
import org.codehaus.jackson.map.ser.impl.SimpleFilterProvider;
public class JsonUtil {
public static String createJsonIgnoreFields(Object object, String ignoreFields) {
try {
ObjectMapper mapper = new ObjectMapper();
mapper.getSerializationConfig().addMixInAnnotations(Object.class, JsonPropertyFilterMixIn.class);
String[] ignoreFieldsArray = ignoreFields.split(",");
FilterProvider filters = new SimpleFilterProvider()
.addFilter("filter properties by field names",
SimpleBeanPropertyFilter.serializeAllExcept(ignoreFieldsArray));
ObjectWriter writer = mapper.writer().withFilters(filters);
return writer.writeValueAsString(object);
} catch (Exception e) {
//handle exception here
}
return "";
}
public static String createJson(Object object) {
try {
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writer().withDefaultPrettyPrinter();
return writer.writeValueAsString(object);
}catch (Exception e) {
//handle exception here
}
return "";
}
}
I've found a solution for me with Spring and jackson
First specify the filter name in the entity
#Entity
#Table(name = "SECTEUR")
#JsonFilter(ModelJsonFilters.SECTEUR_FILTER)
public class Secteur implements Serializable {
/** Serial UID */
private static final long serialVersionUID = 5697181222899184767L;
/**
* Unique ID
*/
#Id
#JsonView(View.SecteurWithoutChildrens.class)
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#JsonView(View.SecteurWithoutChildrens.class)
#Column(name = "code", nullable = false, length = 35)
private String code;
/**
* Identifiant du secteur parent
*/
#JsonView(View.SecteurWithoutChildrens.class)
#Column(name = "id_parent")
private Long idParent;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "id_parent")
private List<Secteur> secteursEnfants = new ArrayList<>(0);
}
Then you can see the constants filters names class with the default FilterProvider used in spring configuration
public class ModelJsonFilters {
public final static String SECTEUR_FILTER = "SecteurFilter";
public final static String APPLICATION_FILTER = "ApplicationFilter";
public final static String SERVICE_FILTER = "ServiceFilter";
public final static String UTILISATEUR_FILTER = "UtilisateurFilter";
public static SimpleFilterProvider getDefaultFilters() {
SimpleBeanPropertyFilter theFilter = SimpleBeanPropertyFilter.serializeAll();
return new SimpleFilterProvider().setDefaultFilter(theFilter);
}
}
Spring configuration :
#EnableWebMvc
#Configuration
#ComponentScan(basePackages = "fr.sodebo")
public class ApiRootConfiguration extends WebMvcConfigurerAdapter {
#Autowired
private EntityManagerFactory entityManagerFactory;
/**
* config qui permet d'éviter les "Lazy loading Error" au moment de la
* conversion json par jackson pour les retours des services REST<br>
* on permet à jackson d'acceder à sessionFactory pour charger ce dont il a
* besoin
*/
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
// config d'hibernate pour la conversion json
mapper.registerModule(getConfiguredHibernateModule());//
// inscrit les filtres json
subscribeFiltersInMapper(mapper);
// config du comportement de json views
mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
converter.setObjectMapper(mapper);
converters.add(converter);
}
/**
* config d'hibernate pour la conversion json
*
* #return Hibernate5Module
*/
private Hibernate5Module getConfiguredHibernateModule() {
SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);
Hibernate5Module module = new Hibernate5Module(sessionFactory);
module.configure(Hibernate5Module.Feature.FORCE_LAZY_LOADING, true);
return module;
}
/**
* inscrit les filtres json
*
* #param mapper
*/
private void subscribeFiltersInMapper(ObjectMapper mapper) {
mapper.setFilterProvider(ModelJsonFilters.getDefaultFilters());
}
}
Endly I can specify a specific filter in restConstoller when i need....
#RequestMapping(value = "/{id}/droits/", method = RequestMethod.GET)
public MappingJacksonValue getListDroits(#PathVariable long id) {
LOGGER.debug("Get all droits of user with id {}", id);
List<Droit> droits = utilisateurService.findDroitsDeUtilisateur(id);
MappingJacksonValue value;
UtilisateurWithSecteurs utilisateurWithSecteurs = droitsUtilisateur.fillLists(droits).get(id);
value = new MappingJacksonValue(utilisateurWithSecteurs);
FilterProvider filters = ModelJsonFilters.getDefaultFilters().addFilter(ModelJsonFilters.SECTEUR_FILTER, SimpleBeanPropertyFilter.serializeAllExcept("secteursEnfants")).addFilter(ModelJsonFilters.APPLICATION_FILTER,
SimpleBeanPropertyFilter.serializeAllExcept("services"));
value.setFilters(filters);
return value;
}
Place #JsonIgnore on the field or its getter, or create a custom dto
#JsonIgnore
private String encryptedPwd;
or as mentioned above by ceekay annotate it with #JsonProperty where access attribute is set to write only
#JsonProperty( value = "password", access = JsonProperty.Access.WRITE_ONLY)
private String encryptedPwd;
Can I do it dynamically?
Yes, you can use a combination of Jackson's PropertyFilter and mixins.
Explanation
Jackson has a PropertyFilter interface to implement a filter to ignore fields dynamically. The problem is that filter has to be defined on the DTO/POJO class using the #JsonFilter annotation.
To avoid adding a #JsonFilter on class we can use ObjectMapper's addMixIn method to "dynamically" add this annotation (and leave our DTO/POJO classes as is).
Code example
Here is my implementation of the idea provided above. We can call toJson() with two arguments: (1) object to be serialized and (2) lambda (Java's Predicate) to be used in PropertyFilter:
public class JsonService {
public String toJson(Object object, Predicate<PropertyWriter> filter) {
ObjectMapper mapper = new ObjectMapper();
FilterProvider filterProvider = new SimpleFilterProvider()
.addFilter("DynamicFilter", new DynamicFilter(filter));
mapper.setFilterProvider(filterProvider);
mapper.addMixIn(object.getClass(), DynamicFilterMixin.class);
try {
return mapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new MyException(e);
}
}
private static final class DynamicFilter extends SimpleBeanPropertyFilter {
private Predicate<PropertyWriter> filter;
private DynamicFilter(Predicate<PropertyWriter> filter) {
this.filter = filter;
}
protected boolean include(BeanPropertyWriter writer) {
return include((PropertyWriter) writer);
}
protected boolean include(PropertyWriter writer) {
return filter.test(writer);
}
}
#JsonFilter("DynamicFilter")
private interface DynamicFilterMixin {
}
}
Now we can call toJson and filter fields during a serialization:
Filtering by name
new JsonService().toJson(object, w -> !w.getName().equals("fieldNameToBeIgnored"));
Filtering by annotation (on the field)
new JsonService().toJson(object, w -> w.getAnnotation(MyAnnotation.class) == null);
Unit tests
Here are the unit tests for the class above:
public class JsonServiceTest {
private JsonService jsonService = new JsonService();
#Test
public void withoutFiltering() {
MyObject object = getObject();
String json = jsonService.toJson(object, w -> true);
assertEquals("{\"myString\":\"stringValue\",\"myInteger\":10,\"myBoolean\":true}", json);
}
#Test
public void filteredByFieldName() {
MyObject object = getObject();
String json = jsonService.toJson(object, w -> !w.getName().equals("myString"));
assertEquals("{\"myInteger\":10,\"myBoolean\":true}", json);
}
#Test
public void filteredByAnnotation() {
MyObject object = getObject();
String json = jsonService.toJson(object, w -> w.getAnnotation(Deprecated.class) == null);
assertEquals("{\"myString\":\"stringValue\",\"myInteger\":10}", json);
}
private MyObject getObject() {
MyObject object = new MyObject();
object.myString = "stringValue";
object.myInteger = 10;
object.myBoolean = true;
return object;
}
private static class MyObject {
private String myString;
private int myInteger;
#Deprecated
private boolean myBoolean;
public String getMyString() {
return myString;
}
public void setMyString(String myString) {
this.myString = myString;
}
public int getMyInteger() {
return myInteger;
}
public void setMyInteger(int myInteger) {
this.myInteger = myInteger;
}
public boolean isMyBoolean() {
return myBoolean;
}
public void setMyBoolean(boolean myBoolean) {
this.myBoolean = myBoolean;
}
}
}
Would not creating a UserJsonResponse class and populating with the wanted fields be a cleaner solution?
Returning directly a JSON seems a great solution when you want to give all the model back. Otherwise it just gets messy.
In the future, for example you might want to have a JSON field that does not match any Model field and then you're in a bigger trouble.
This is a clean utility tool for the above answer :
#GetMapping(value = "/my-url")
public #ResponseBody
MappingJacksonValue getMyBean() {
List<MyBean> myBeans = Service.findAll();
MappingJacksonValue mappingValue = MappingFilterUtils.applyFilter(myBeans, MappingFilterUtils.JsonFilterMode.EXCLUDE_FIELD_MODE, "MyFilterName", "myBiggerObject.mySmallerObject.mySmallestObject");
return mappingValue;
}
//AND THE UTILITY CLASS
public class MappingFilterUtils {
public enum JsonFilterMode {
INCLUDE_FIELD_MODE, EXCLUDE_FIELD_MODE
}
public static MappingJacksonValue applyFilter(Object object, final JsonFilterMode mode, final String filterName, final String... fields) {
if (fields == null || fields.length == 0) {
throw new IllegalArgumentException("You should pass at least one field");
}
return applyFilter(object, mode, filterName, new HashSet<>(Arrays.asList(fields)));
}
public static MappingJacksonValue applyFilter(Object object, final JsonFilterMode mode, final String filterName, final Set<String> fields) {
if (fields == null || fields.isEmpty()) {
throw new IllegalArgumentException("You should pass at least one field");
}
SimpleBeanPropertyFilter filter = null;
switch (mode) {
case EXCLUDE_FIELD_MODE:
filter = SimpleBeanPropertyFilter.serializeAllExcept(fields);
break;
case INCLUDE_FIELD_MODE:
filter = SimpleBeanPropertyFilter.filterOutAllExcept(fields);
break;
}
FilterProvider filters = new SimpleFilterProvider().addFilter(filterName, filter);
MappingJacksonValue mapping = new MappingJacksonValue(object);
mapping.setFilters(filters);
return mapping;
}
}
To acheive dynamic filtering follow the link - https://iamvickyav.medium.com/spring-boot-dynamically-ignore-fields-while-converting-java-object-to-json-e8d642088f55
Add the #JsonFilter("Filter name") annotation to the model class.
Inside the controller function add the code:-
SimpleBeanPropertyFilter simpleBeanPropertyFilter =
SimpleBeanPropertyFilter.serializeAllExcept("id", "dob");
FilterProvider filterProvider = new SimpleFilterProvider()
.addFilter("Filter name", simpleBeanPropertyFilter);
List<User> userList = userService.getAllUsers();
MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(userList);
mappingJacksonValue.setFilters(filterProvider);
return mappingJacksonValue;
make sure the return type is MappingJacksonValue.
Hi I have achieved dynamic filtering by using Gson library like in the below:
JsonObject jsonObj = new Gson().fromJson(mapper.writeValueAsString(sampleObject), JsonObject.class);
jsonObj.remove("someProperty");
String data = new Gson().toJson(jsonObj);
In your entity class add #JsonInclude(JsonInclude.Include.NON_NULL) annotation to resolve the problem
it will look like
#Entity
#JsonInclude(JsonInclude.Include.NON_NULL)