I need to create custom jackson generic typed bean deserializer.
T is bean implementing IEntity.
public interface IEntity {
public int getId();
}
#JsonDeserialize(using = RestLoaderRequestDeserializer.class)
#Produces(MediaType.APPLICATION_JSON)
public class RestLoaderRequest<T extends IEntity> Serializable {
private T entity; // entity to load field to
private String className; // actual class of entity
private String fieldName; // fieldName to lazy REST load
// constructors(non parameter & all parameter), getters, setters, hashCode, equals, toString
}
Now what I have is this:
public class RestLoaderRequestDeserializer extends JsonDeserializer<RestLoaderRequest<IEntity>> {
#Override
public RestLoaderRequest<IEntity> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
try {
ObjectCodec objectCodec = jp.getCodec();
JsonNode node = objectCodec.readTree(jp);
String className = node.get("className").textValue();
String fieldName = node.get("fieldName").textValue();
Class<?> clazz = Class.forName(className);
JsonNode rawEntityNode = node.get("entity");
// How to deserialize rawEntityNode to T based on className ?
RestLoaderRequest<IEntity> request = new RestLoaderRequest<IEntity>();
request.setClassName(className);
request.setFieldName(fieldName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
1) How to do auto-deserialization of entity based on className?
Keep in mind that handling of type information and handling of payload data are separate.
So regular JSON (de)serializers are not supposed to handle types at all: rather, separate TypeSerializer and TypeDeserializer are used. This is done to prevent explosion in number of combinations; there are couple of TypeSerializer/-Deserializer implementations (for different inclusion mechanism), but hundreds of value (de)serializers.
Custom (de)serializers can obviously break this separation, when you have full control. But core Jackson has this separation, and you can see examples in standard handlers.
Related
I have an entity with multiple #ManyToOne associations. I am using spring-boot to expose a REST API. Currently, I have multiple REST API's which return a JSON response of the whole entity, including associations.
But I don't want to serialize all associated objects in all REST APIs.
For example
API-1 should return parent + associationA object
API-2 should return parent + associationA + associationB object
API-3 should return parent + associationB + associationC + associationD
So, in my serialization process, I want to ignore all association except associationA for API-1.
For API-2 I want to ignore other associations except A and B
How do I dynamically ignore these properties during Jackson serialization?
Notes:
I'm using the same class for each; I am not interested in creating a DTO for each API.
Any suggestions are kingly appreciated.
I've put together three approaches for performing dynamic filtering in Jackson. One of them must suit your needs.
Using #JsonView
You could use #JsonView:
public class Views {
interface Simple { }
interface Detailed extends Simple { }
}
public class Foo {
#JsonView(Views.Simple.class)
private String name;
#JsonView(Views.Detailed.class)
private String details;
// Getters and setters
}
#RequestMapping("/foo")
#JsonView(Views.Detailed.class)
public Foo getFoo() {
Foo foo = new Foo();
return foo;
}
Alternatively you can set the view dynamically with MappingJacksonValue.
#RequestMapping("/foo")
public MappingJacksonValue getFoo() {
Foo foo = new Foo();
MappingJacksonValue result = new MappingJacksonValue(foo);
result.setSerializationView(Views.Detailed.class);
return result;
}
Using a BeanSerializerModifier
You could extend BeanSerializerModifier and then override the changeProperties() method. It allows you to add, remove or replace any of properties for serialization, according to your needs:
public class CustomSerializerModifier extends BeanSerializerModifier {
#Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
// In this method you can add, remove or replace any of passed properties
return beanProperties;
}
}
Then register the serializer as a module in your ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new SimpleModule() {
#Override
public void setupModule(SetupContext context) {
super.setupModule(context);
context.addBeanSerializerModifier(new CustomSerializerModifier());
}
});
Check examples here and here.
Using #JsonFilter with a SimpleBeanPropertyFilter
Another approach involves #JsonFilter:
#JsonFilter("customPropertyFilter")
public class Foo {
private String name;
private String details;
// Getters and setters
}
Extend SimpleBeanPropertyFilter and override the serializeAsField() method according to your needs:
public class CustomPropertyFilter extends SimpleBeanPropertyFilter {
#Override
public void serializeAsField(Object pojo, JsonGenerator jgen,
SerializerProvider provider,
PropertyWriter writer) throws Exception {
// Serialize a field
// writer.serializeAsField(pojo, jgen, provider, writer);
// Omit a field from serialization
// writer.serializeAsOmittedField(pojo, jgen, provider);
}
}
Then register the filter in your ObjectMapper:
FilterProvider filterProvider = new SimpleFilterProvider()
.addFilter("customPropertyFilter", new CustomPropertyFilter());
ObjectMapper mapper = new ObjectMapper();
mapper.setFilterProvider(filterProvider);
If you want to make your filter "global", that is, to be applied to all beans, you can create a mix-in class and annotate it with #JsonFilter("customPropertyFilter"):
#JsonFilter("customPropertyFilter")
public class CustomPropertyFilterMixIn {
}
Then bind the mix-in class to Object:
mapper.addMixIn(Object.class, CustomPropertyFilterMixIn.class);
public static <T> String getNonNullFieldsSerialized(T object, ObjectMapper objectMapper)throws JsonProcessingException {
Map<String, Object> objectMap = objectMapper.convertValue(object, new TypeReference<Map<String, Object>>() {});
Map<String, Object> objectMapNonNullValues = objectMap.entrySet().stream()
.filter(stringObjectEntry -> Objects.nonNull(stringObjectEntry.getValue()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
return objectMapper.writeValueAsString(objectMapNonNullValues);
}
This will basically ignore all the fields that are non-null. Similarly you can ignore other fields by changing the map filter condition.
I have implemented dynamic filter on data getting from db and returning it using rest api.I have avoided using MappingJacksonValue.As it was getting issue while object chaining
#GetMapping("/courses")
public ResponseEntity<JpaResponse> allCourse() throws Exception {
JpaResponse response = null;
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
List<Course> course = service.findAllCourse();
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("name","reviews");
FilterProvider filterProvider = new SimpleFilterProvider().addFilter("jpafilter", filter).setFailOnUnknownId(false);
ObjectWriter writer = mapper.writer(filterProvider);
String writeValueAsString = writer.writeValueAsString(course);
List<Course> resultcourse = mapper.readValue(writeValueAsString,List.class);
response = new JpaResponse(HttpStatus.OK.name(),resultcourse);
return new ResponseEntity<>(response, HttpStatus.OK);
}
public class JpaResponse {
private String status;
private Object data;
public JpaResponse() {
super();
}
public JpaResponse(String status, Object data) {
super();
this.status = status;
this.data = data;
}
}
Please note: There are many questions on this site about how to use custom Jackson deserializers...this question is not one more of those! This questions has to do with using a deserializer under very unique circumstances (none of which have previous questions/answers on this site!).
Spring Boot using Jackson for JSON serialization here. I have two POJOs that are used in the #RequestBody (HTTP request entity) for a POST endpoint:
#JsonDeserialize(using = FizzDeserializer.class)
public class Fizz {
private String name;
private String label;
private Integer code;
// Getters, setters & ctors
}
#JsonDeserialize(using = BuzzDeserializer.class)
public class Buzz {
private String id;
private String locale;
private Set<Fizz> fizzes;
// Getters, setters & ctors
}
#RestController
#RequestMapping("v1/data/buzzes")
public class BuzzController {
#PostMapping
public void updateBuzz(#RequestBody Buzz buzz) {
// do whatever
}
}
I want HTTP clients to be able to POST the following JSON to this endpoint:
{
"id" : "12345-67890",
"locale" : "en_US",
"fizzes" : [
"foo",
"bar"
]
}
...where "foo" and "bar" are the Fizz#names of two different Fizz instances. In other words, I don't want the client to have to specify the entire Fizz object, just specify its name as a JSON string (my app + DB guarantee Fizzes have unique names).
So I'm using a custom JsonDeserializer to accomplish all this mapping:
public BuzzDeserializer extends JsonDeserializer<Buzz> {
Buzz deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode buzzNode = jsonParser.readValueAsTree();
String id = buzzNode.get("id");
String locale = buzzNode.get("locale");
// TODO: How to read "foo" and "bar" (etc.) into a Set<Fizz> instances?
Set<Fizz> fizzes = ???
new Buzz(id, locale, fizzes);
}
}
public FizzDeserializer extends JsonDeserializer<Fizz> {
private FizzDAO fizzDAO;
// Getters, setters & ctors...
Fizz deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode fizzNode = jsonParser.readValueAsTree();
// If I can get access to "foo"/"bar"/etc. string somehow, I can look up the Fizz using the DAO:
String fooBarEtcStr = ???
return fizzDAO.findFizzByName(fooBarEtcStr);
}
However I'm not sure how I can read the JSON fizzes array into a Set<Fizz> inside this deserializer. Any ideas?
Taken from the comments to the question, the only problem seems to be to get the JSON array. Provided that the service or DAO to lookup is already injected or provided in the serializer, try something along the lines:
final JsonNode arr = buzzNode.get("fizzes");
if (arr.isArray()) {
final Set<Fizz> fizzes = Sets.newHashSetWithExpectedSize(arr.size());
for (JsonNode obj : arr) {
final String name = obj.asText();
Fizz fizz = // load from DAO
fizzes.add(fizz);
}
}
This can of course be optimised by collecting the String values and use only one DAO call. Also some java8 streaming could make the code less verbose.
I have following Incoming json, which I deserialize to Model.java and then copy that java object to ModelView.java. I wan't to convert date from String to milliseconds and send the Outgoing json as response.
How do I go for it ?
I've specific reason to copy the value from Model.java to ModelView.java using object mapper. So please don't suggest to modify that part. I'm looking to do this via annotation. I'm pretty sure that it can be done, but don't know how.
The json provided here is a simplistic one. I have a large json in actual scenario.
Incoming json
{
"date":"2016-03-31"
}
Outgoing Json
{
"date":236484625196
}
My Controller Class
#Controller
public class SomeController extends BaseController {
#Autowired
private SomeService someService;
#RequestMapping(method = RequestMethod.GET, produces = "application/json")
public #ResponseBody
ResponseEntity<RestResponse> getDetails(HttpServletRequest request, HttpServletResponse response) {
Model model = someService.getData();
ModelView modelView = ModelView.valueOf(model);
return getSuccessResponse(modelView);
}
}
Model.java
#JsonInclude(Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Model implements Serializable {
private String date;
//normal getters and setters
}
ModelView.java
#JsonInclude(Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class ModelView implements Serializable {
private Long date;
//normal getters and setters
public static ModelView valueOf(Model model){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd");
ObjectMapper mapper = new ObjectMapper();
ModelView modelView = mapper.convertValue(model, ModelView.class);
try {
modelView.setDate(sdf.parse(model.getDate()).getTime());
} catch (ParseException e) {
IntLogger.error("logging error here");
}
return modelView;
}
}
I'm open to change the variable name from "date" to something else in ModelView.java but the outgoing json should remain same.
Jackson has some build in date formatting, for example, you can set the DateFormatter on the object mapper, but i believe this only works if the serialization and deserialization format is the same.
A simpler approach to date serialization and deserialization, if you want serialization and deserialization to be different format, is to use #JsonSerialize and #JsonDeserialize annotations on your Model.class directly (this could obsolete the need for ModelView if your only purpose was to convert the date).
You can create two classes for serialization and deserialization:
public class JsonDateDeserializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String dateString = jsonParser.getText();
try {
return dateFormat.parse(dateString);
}
catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
Then for the serialization to your Outgoing json:
public class JsonDateSerializer extends JsonSerializer<Date> {
#Override
public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeString(Long.toString(date.getTime()));
}
}
Now, you an just annotate your Model.java:
#JsonInclude(Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Model implements Serializable {
#JsonSerialize(using = JsonDateSerializer.class)
#JsonDeserialize(using = JsonDateDeserializer.class)
private String date;
//normal getters and setters
}
gson is such a great serialize/deserialization tool. It's really simple to get a JSON representation of an arbitrary object by using the toJson-function.
Now I want to send the data of my object to the browser to be used within javascript/jQuery. Thus, I need one additional JSON element defining the dom class of the object which is coded within my object as a dynamic/memberless function
public String buildDomClass()
How to add this string to my String created by the toJson function?
Any ideas?
Thanks a lot
An easy way is to combine a TypeAdapterFactory and an interface.
First an interface for your method :
public interface MyInterface {
public String buildDomClass();
}
then the factory :
final class MyAdapter implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> tokenType) {
final TypeAdapter<T> adapter = gson.getDelegateAdapter(this, tokenType);
return new TypeAdapter<T>() {
#Override
public T read(JsonReader reader) throws IOException {
return adapter.read(reader);
}
#Override
public void write(JsonWriter writer, T value) throws IOException {
JsonElement tree = adapter.toJsonTree(value);
if (value instanceof MyInterface) {
String dom = ((MyInterface) value).buildDomClass();
JsonObject jo = (JsonObject) tree;
jo.addProperty("dom", dom );
}
gson.getAdapter(JsonElement.class).write(writer, tree);
}
};
}
}
Easy to understand, if the object you want to serialize implement the interface, you delegate the serializing, and then you add an extra property holding you DOM.
In case you don't know, you register a factory like this
Gson gson = new GsonBuilder().registerTypeAdapterFactory(new MyAdapter()).create();
Good day,
I am currently integration attempting to consume a REST service that produces JSON (written in .NET) using Jackson (with Jersey). The JSON consists of a possible error message and an array of objects. Below is a sample of the JSON returned as produced by Jersey's logging filter:
{
"error":null,
"object":"[{\"Id\":16,\"Class\":\"ReportType\",\"ClassID\":\"4\",\"ListItemParent_ID\":4,\"Item\":\"Pothole\",\"Description\":\"Pothole\",\"Sequence\":1,\"LastEditDate\":null,\"LastEditor\":null,\"ItemStatus\":\"Active\",\"ItemColor\":\"#00AF64\"}]"
}
I have two classes to represent the type (the outer ListResponse):
public class ListResponse {
public String error;
public ArrayList<ListItem> object;
public ListResponse() {
}
}
and (the inner ListItem):
public class ListItem {
#JsonProperty("Id")
public int id;
#JsonProperty("Class")
public String classType;
#JsonProperty("ClassID")
public String classId;
#JsonProperty("ListItemParent_ID")
public int parentId;
#JsonProperty("Item")
public String item;
#JsonProperty("Description")
public String description;
#JsonAnySetter
public void handleUnknown(String key, Object value) {}
public ListItem() {
}
}
The class that invokes and returns the JSON looks like this:
public class CitizenPlusService {
private Client client = null;
private WebResource service = null;
public CitizenPlusService() {
initializeService("http://localhost:59105/PlusService/");
}
private void initializeService(String baseURI) {
// Use the default client configuration.
ClientConfig clientConfig = new DefaultClientConfig();
clientConfig.getClasses().add(JacksonJsonProvider.class);
client = Client.create(clientConfig);
// Add a logging filter to track communication between server and client.
client.addFilter(new LoggingFilter());
// Add the base URI
service = client.resource(UriBuilder.fromUri(baseURI).build());
}
public ListResponse getListItems(String id) throws Exception
{
ListResponse response = service.path("GetListItems").path(id).accept(MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE).get(ListResponse.class);
return response;
}
}
The important call here is the getListItems method. Running the code in a test harness, produces the following:
org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of VALUE_STRING token
at [Source: java.io.StringReader#49497eb8; line: 1, column: 14] (through reference chain: citizenplus.types.ListResponse["object"])
Please assist.
Regards,
Carl-Peter Meyer
You may be missing a #JsonDeserialize attribute as the type information does get lost in generics at run-time. Also you should avoid using concrete classes for collections if you can.
public class ListResponse {
public String error;
#JsonDeserialize(as=ArrayList.class, contentAs=ListItem.class)
public List<ListItem> object;
}
Your problem is that the 'object' property value is a String and not an array! The string contains a JSON array but Jackson expects a native array (without the wrapping quotes).
I had the same problem and I created a custom deserializer, which will deserialize a string value to a generic collection of the desired type:
public class JsonCollectionDeserializer extends StdDeserializer<Object> implements ContextualDeserializer {
private final BeanProperty property;
/**
* Default constructor needed by Jackson to be able to call 'createContextual'.
* Beware, that the object created here will cause a NPE when used for deserializing!
*/
public JsonCollectionDeserializer() {
super(Collection.class);
this.property = null;
}
/**
* Constructor for the actual object to be used for deserializing.
*
* #param property this is the property/field which is to be serialized
*/
private JsonCollectionDeserializer(BeanProperty property) {
super(property.getType());
this.property = property;
}
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
return new JsonCollectionDeserializer(property);
}
#Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
switch (jp.getCurrentToken()) {
case VALUE_STRING:
// value is a string but we want it to be something else: unescape the string and convert it
return JacksonUtil.MAPPER.readValue(StringUtil.unescapeXml(jp.getText()), property.getType());
default:
// continue as normal: find the correct deserializer for the type and call it
return ctxt.findContextualValueDeserializer(property.getType(), property).deserialize(jp, ctxt);
}
}
}
Note that this deserializer will also work if the value actually is an array and not a string, because it delegates the actual deserialization accordingly.
In your example you would now have to annotate your collection field like so:
public class ListResponse {
public String error;
#JsonDeserialize(using = JsonCollectionDeserializer.class)
public ArrayList<ListItem> object;
public ListResponse() {}
}
And that should be it.
Note: JacksonUtil and StringUtil are custom classes, but you can easily replace them. For example by using new ObjectMapper() and org.apache.commons.lang3.StringEscapeUtils.
The register subTypes works!
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
public interface Geometry {
}
public class Point implements Geometry{
private String type="Point";
....
}
public class Polygon implements Geometry{
private String type="Polygon";
....
}
public class LineString implements Geometry{
private String type="LineString";
....
}
GeoJson geojson= null;
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.registerSubtypes(Polygon.class,LineString.class,Point.class);
try {
geojson=mapper.readValue(source, GeoJson.class);
} catch (IOException e) {
e.printStackTrace();
}