How to pass parameter to JsonSerializer? - java

I have a simple data service :
#GET
public Data getData(#QueryParam("id") Long id) {
Data data = dataService.getData(id);
return data;
}
And a matching DataSerializer that implements JsonSerializer<Data> :
The DataSerializer is registered to Jackson via :
simpleModule.addSerializer(Data.class , dataSerializer);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(simpleModule);
It works well.
But today , I want to add another Locale parameter , and hope the DataSerializer to output correspondent content :
#GET
public Data getData(#QueryParam("id") Long id , #QueryParam("locale") Locale locale)
The 'Data' itself contains various locale variations , and I hope to get the assigned locale output.
But when I get the locale from the parameter , I don't know how to pass the locale value to the DataSerializer …
Is there anyway to achieve this ?
Except this solution :
Data data = dataService.getData(id.get() , locale);
which is not what I want.
It seems ThreadLocal is the only way to achieve this , but I feel that is ugly. Any other feasible solutions ?
Thanks.
Environments : dropwizard-0.7.0-rc2 , jackson-core:jar:2.3.1
===================== updated ==========
reply to #andrei-i :
Because my data itself already contains various locale versions.
for example :
Data helloData = dataService.get("hello");
helloData.getName(Locale.English) == "Hello";
helloData.getName(Locale.France) == "Bonjour";
helloData.getName(Locale.Germany) == "Hallo";
I want to directly pass the locale from URL to JsonSerializer , to get one version of the data presentation.
And there 'may' be other version (not just locale) , so , inheriting Data mixing Locale is not considered.

I know that this is not a new question but here is what I came up with facing the similar problem:
created custom annotation:
#Target({ ElementType.FIELD, ElementType.TYPE, ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface JsonLocalizable {
public String localizationKey();
}
Jackson serializer:
public class LocalizingSerializer extends StdSerializer<String> implements ContextualSerializer {
private String localizationKey;
public LocalizingSerializer() {
super(String.class);
}
public LocalizingSerializer(String key) {
super(String.class);
this.localizationKey = key;
}
#Override
public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
String localizedValue = //.... get the value using localizationKey
jgen.writeString(localizedValue);
}
#Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
String key = null;
JsonLocalizable ann = null;
if (property != null) {
ann = property.getAnnotation(JsonLocalizable.class);
}
if (ann != null) {
key = ann.localizationKey();
}
//if key== null??
return new LocalizingSerializer(key);
}
}
Annotate the field you want to localize:
public class TestClass {
#JsonSerialize(using = LocalizingSerializer.class)
#JsonLocalizable(localizationKey = "my.key")
private String field;
public String getField() {
return this.field;
}
public void setField(String field) {
this.field = field;
}
}

Solution 1. In your JAX-RS implementation register your own implementation of MessageBodyWriter for JSON requests. Probably your implementation will extend Jackson. Also it might be possible that you will have to unregister Jackson. In a MessageBodyWriter you can inject a UriInfo instance using the #Context annotation, and with it you can get any request parameter.
Solution 2. Change the architecture of your Data, so that it is locale-aware. For example, create a setter setLocale() which will change the returned data, if the locale was set.

Related

Changing how Swagger generates the JSON response in Java

I use Swagger to set up an REST Api Service. For this I used following versions:
swagger (YAML)-file version 2.0
swagger codegen cli version v2.3.0
springfox version 2.5.0
The task of the API Service is to respond with data that we store in our database, as well as its relations.
However, we have circular relations in our data. This means that each Object a can have a relation to Object b, which can have a backward relation to Object a.
Now using Swagger, this will generate an endless long JSON, resulting in an error.
The code looks like this:
public class Service extends DataObject {
#SerializedName("provides")
private List<Utility> provides;
/**
* Get provides
*
* #return provides
**/
#ApiModelProperty(required = true, value = "")
public List<Utility> getProvides() {
return provides;
}
}
public class Utility extends DataObject {
#SerializedName("providedBy")
private List<Service> providedBy;
/**
* Get providedBy
*
* #return providedBy
**/
#ApiModelProperty(required = true, value = "")
public List<Service> getProvidedBy() {
return providedBy;
}
}
Then in the response:
#Override
public ResponseEntity<List<Service>> servicesGet(
#Min(0) #ApiParam(value = "The number of layers of relations the object is enriched by. Lower numbers typically increase performance.", defaultValue = "0") #Valid #RequestParam(value = "layersOfRelations", required = false, defaultValue = "0") final Integer layersOfRelations) {
String accept = this.request.getHeader("Accept");
List<Service> services = Util.getServices();
return new ResponseEntity<List<Service>>(services, HttpStatus.OK);
}
My Question is, where and how can I change the output that will be automatically generated for the return in the method servicesGet()?
I would like to not transform the whole object b into JSON, but rather only its title, so that there will be no endless recursion.
So after some research I found out, that swagger is using Jackson to convert objects into JSON (even though the #SerializedName parameter contained in the generated code was from the gson library, at least in my case).
This means that in this case it I can simply use customization techniques that Jackson provides, and it worked like a charm.
I only had to write a custom Jackson adapter annotation:
#SerializedName("provides")
#JsonSerialize(using = DataObjectAdapter.class)
private List<Utility> provides;
and the adapter looks like this:
public static class DataObjectAdapter extends JsonSerializer<List<Utility>> {
#Override
public void serialize(final List<Utility> arg0, final JsonGenerator arg1, final SerializerProvider arg2)
throws IOException {
arg1.writeStartArray();
for (Utility object : arg0) {
arg1.writeStartObject();
arg1.writeStringField("id", object.getId());
arg1.writeStringField("title", object.getTitle());
arg1.writeStringField("description", object.getDescription());
arg1.writeEndObject();
}
arg1.writeEndArray();
}
}
So now instead of writing the whole object into JSON (and thus recursivly adding all child objects into it, he will only write the information I described in the adapter into JSON.
Similarly a deserializer, where I read out the ID in the JSON and map it to my data, would look like this
public static class ServiceDeserializer extends StdDeserializer<Service> {
public ServiceDeserializer() {
super(Service.class);
}
#Override
public Service deserialize(final JsonParser jp, final DeserializationContext ctxt)
throws IOException, JsonProcessingException {
List<Utility> objects = Util.getUtilities();
JsonNode node = jp.getCodec().readTree(jp);
String id = node.get("id").asText();
String title = node.get("title").asText();
String description = node.get("description").asText();
Service service = new Service(id, title, description);
for (JsonNode utility : node.get("provides")) {
String checkId = utility.get("id").asText();
for (DataObject object : objects) {
if (object.getId().equals(checkId)) {
service.addUtility(object);
break;
}
}
}
return service;
}
}

Custom Jackson serializer on specific fields

I'm looking to have multiple jackson deserializers for the same object(s) all based on a custom annotation.
Ideally I'd have a single POJO like:
public class UserInfo {
#Redacted
String ssn;
String name;
}
Under "normal" conditions I want this object to be serialized the default way:
{"ssn":"123-45-6789", "name":"Bob Smith"}
but for logging purposes (for example) I want to redact the SSN so it doesn't get saved in our logs:
{"ssn":"xxx-xx-xxxx", "name":"Bob Smith"}
I've also looked into using #JsonSerialize and come up with:
public class UserInfo {
#JsonSerialize(using = RedactedSerializer.class, as=String.class)
String firstName;
String lastName;
}
The problem with this is that it ALWAYS uses this rule. Can multiple #JsonSerializers be added and only the specified one be used within the runtime code?
I've also seen "views" but ideally I'd like to atleast show that the field was present on the request - even if I dont know the value.
The 100% safe way would be to use different DTO in different requests. But yeah, if you cant do that, use #JsonView and custom serializer, something like:
class Views {
public static class ShowSSN {}
}
private static class MyBean{
#JsonSerialize(using = MyBeanSerializer.class)
#JsonView(Views.ShowSSN.class)
String ssn;
//getter setter constructor
}
private class MyBeanSerializer extends JsonSerializer<String> {
#Override
public void serialize(String value, JsonGenerator gen,
SerializerProvider serializers) throws IOException {
Class<?> jsonView = serializers.getActiveView();
if (jsonView == Views.ShowSSN.class)
gen.writeString(value); // your custom serialization code here
else
gen.writeString("xxx-xx-xxxx");
}
}
And use it like:
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
MyBean bean = new MyBean("123-45-6789");
System.out.println(mapper.writerWithView(Views.ShowSSN.class)
.writeValueAsString(bean));
// results in {"ssn":"123-45-6789"}
System.out.println(mapper.writeValueAsString(bean));
// results in {"ssn":"xxx-xx-xxxx"}
}
Also for example in spring it would be really easy to use
#Controller
public class MyController {
#GetMapping("/withView") // results in {"ssn":"123-45-6789"}
#JsonView(Views.ShowSSN.class)
public #ResponseBody MyBean withJsonView() {
return new MyBean("123-45-6789");
}
#GetMapping("/withoutView") // results in {"ssn":"xxx-xx-xxxx"}
public #ResponseBody MyBean withoutJsonView() {
return new MyBean("123-45-6789");
}
}
I think you could achieve that dynamically by coding not annotations,
inside your methods, you can set the proper Serializer and switch between them
(The code depends on your Jackson version)
ObjectMapper mapper = new ObjectMapper();
SimpleModule testModule = new SimpleModule("MyModule", new Version(1, 0, 0, null));
testModule.addSerializer(new RedactedSerializer()); // assuming serializer declares correct class to bind to
mapper.registerModule(testModule);
https://github.com/FasterXML/jackson-docs/wiki/JacksonHowToCustomSerializers

Configure Jackson to honor SerializationInclusion configuration for custom Serializer classes

I'm having a rather interesting problem attempting to get Jackson to properly remove null fields from resulting JSON when they have been created by a custom serializer class. I've searched pretty thoroughly for information regarding Serializer and the SerializationInclusion configuration, but I haven't found anything that seems to explain what I'm seeing.
I have a Jackson object mapper configured and autowired in via Spring. The object mapper configuration and POJOs (edited for brevity) look more or less like the code below.
For some reason, when I call our REST endpoint to get a Bar object back that contains the above example values I see the following behavior:
The SerializationInclusion setting is being applied to all properties that are empty or null on their own (name, aList, objId).
The SerializationInclusion setting is NOT being applied to any properties that are set to null by a custom Serializer class, and we get back JSON that has a null value present.
My thought here is that the Serializer logic gets called AFTER the Jackson has already removed all null and empty values from the JSON, and therefore the "foo" property is properly set to null, but is not removed because the inclusion logic has already executed.
Does anyone have any thoughts about what might be going on here? Is this a quirk in how Jackson-databind library is implemented in version 2.2.2?
Jackson Config -
#Bean
public JacksonObjectMapper jacksonMapper() {
final JacksonObjectMapper mapper = new JacksonObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
mapper.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
mapper.registerModule(agJacksonModule());
return mapper;
}
#Bean
public SimpleModule agJacksonModule() {
final SimpleModule module = new SimpleModule();
module.addSerializer(Foo.class, new FooSerializer());
return module;
}
Custom Serializer -
public class FooSerializer extends JsonSerializer<Foo> {
#Override
public void serialize(Sponsor sponsor, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException {
// write null value for sponsor json property, if sponsor object has all empty or null fields
if(sponsor == null || isObjectEmpty(sponsor)) {
jsonGenerator.writeNull();
return;
}
// write out object
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("imgUrl", sponsor.getImgUrl());
jsonGenerator.writeStringField("clickUrl", sponsor.getClickUrl());
jsonGenerator.writeStringField("sponsorName", sponsor.getSponsorName());
jsonGenerator.writeStringField("sponsorText", sponsor.getSponsorText());
jsonGenerator.writeEndObject();
}
private boolean isObjectEmpty(Sponsor sponsor) {
return Strings.isNullOrEmpty(sponsor.getClickUrl())
&& Strings.isNullOrEmpty(sponsor.getImgUrl())
&& Strings.isNullOrEmpty(sponsor.getSponsorName())
&& Strings.isNullOrEmpty(sponsor.getSponsorText());
}
}
The object model looks something like this (again edited for brevity, sample values are set on the class members as example data):
Bar POJO -
public abstract class Bar {
protected Foo foo = aFoo;
protected String name = "";
protected ArrayList aList = Lists.newArrayList();
protected String objId = null;
// some getters and setters for the above properties
}
Foo POJO -
public abstract class Foo {
protected String aString = "";
protected String bString = "";
protected String cString = "";
protected String dString = "";
// some getters and setters for the above properties
}
Override and implement the isEmpty method of JsonSerializer to achieve what you want.
For custom definition of what emptymeans, your JsonSerializer implementation needs to override this method:
public boolean isEmpty(SerializerProvider provider, T value);
Note that it is the caller that has to handle filtering as it writes field name; serializer will only be called in case actual serialization is needed.

Jackson JSON, filtering properties by path

I need to filter bean properties dynamiclly on serialization.
The #JsonView isn't an option for me.
Assume my Bean (as Json notation):
{
id: '1',
name: 'test',
children: [
{ id: '1.1', childName: 'Name 1.1' },
{ id: '1.2', childName: 'Name 1.2' }
]
}
I want to write the JSON with the following properties:
// configure the ObjectMapper to only serialize this properties:
[ "name", "children.childName" ]
The expected JSON result is:
{
name: 'test',
children: [
{ childName: 'Name 1.1' },
{ childName: 'Name 1.2' }
]
}
Finally I will create an annotation (#JsonFilterProperties) to use with Spring in my RestControllers, something like this:
#JsonFilterProperties({"name", "children.childName"}) // display only this fields
#RequestMapping("/rest/entity")
#ResponseBody
public List<Entity> findAll() {
return serviceEntity.findAll(); // this will return all fields populated!
}
Well, it's tricky but doable. You can do this using Jacksons Filter feature (http://wiki.fasterxml.com/JacksonFeatureJsonFilter) with some minor alterations. To start, we are going to use class name for filter id, this way you won't have to add #JsonFIlter to every entity you use:
public class CustomIntrospector extends JacksonAnnotationIntrospector {
#Override
public Object findFilterId(AnnotatedClass ac) {
return ac.getRawType();
}
}
Next step, make that filter of super class will apply to all of its subclasses:
public class CustomFilterProvider extends SimpleFilterProvider {
#Override
public BeanPropertyFilter findFilter(Object filterId) {
Class id = (Class) filterId;
BeanPropertyFilter f = null;
while (id != Object.class && f == null) {
f = _filtersById.get(id.getName());
id = id.getSuperclass();
}
// Part from superclass
if (f == null) {
f = _defaultFilter;
if (f == null && _cfgFailOnUnknownId) {
throw new IllegalArgumentException("No filter configured with id '" + filterId + "' (type " + filterId.getClass().getName() + ")");
}
}
return f;
}
}
Custom version of ObjectMapper that utilizes our custom classes:
public class JsonObjectMapper extends ObjectMapper {
CustomFilterProvider filters;
public JsonObjectMapper() {
filters = new CustomFilterProvider();
filters.setFailOnUnknownId(false);
this.setFilters(this.filters);
this.setAnnotationIntrospector(new CustomIntrospector());
}
/* You can change methods below as you see fit. */
public JsonObjectMapper addFilterAllExceptFilter(Class clazz, String... property) {
filters.addFilter(clazz.getName(), SimpleBeanPropertyFilter.filterOutAllExcept(property));
return this;
}
public JsonObjectMapper addSerializeAllExceptFilter(Class clazz, String... property) {
filters.addFilter(clazz.getName(), SimpleBeanPropertyFilter.serializeAllExcept(property));
return this;
}
}
Now take a look at MappingJackson2HttpMessageConverter, you will see that it uses one instane of ObjectMapper internaly, ergo you cannot use it if you want different configurations simultaneously (for different requests). You need request scoped ObjectMapper and appropriate message converter that uses it:
public abstract class DynamicMappingJacksonHttpMessageConverter extends MappingJackson2HttpMessageConverter {
// Spring will override this method with one that provides request scoped bean
#Override
public abstract ObjectMapper getObjectMapper();
#Override
public void setObjectMapper(ObjectMapper objectMapper) {
// We dont need that anymore
}
/* Additionally, you need to override all methods that use objectMapper attribute and change them to use getObjectMapper() method instead */
}
Add some bean definitions:
<bean id="jsonObjectMapper" class="your.package.name.JsonObjectMapper" scope="request">
<aop:scoped-proxy/>
</bean>
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="your.package.name.DynamicMappingJacksonHttpMessageConverter">
<lookup-method name="getObjectMapper" bean="jsonObjectMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
And the last part is to implement something that will detect your annotation and perform actual configuration. For that you can create an #Aspect. Something like:
#Aspect
public class JsonResponseConfigurationAspect {
#Autowired
private JsonObjectMapper objectMapper;
#Around("#annotation(jsonFilterProperties)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
/* Here you will have to determine return type and annotation value from jointPoint object. */
/* See http://stackoverflow.com/questions/2559255/spring-aop-how-to-get-the-annotations-of-the-adviced-method for more info */
/* If you want to use things like 'children.childName' you will have to use reflection to determine 'children' type, and so on. */
}
}
Personally, I use this in a different way. I dont use annotations and just do configuration manually:
#Autowired
private JsonObjectMapper objectMapper;
#RequestMapping("/rest/entity")
#ResponseBody
public List<Entity> findAll() {
objectMapper.addFilterAllExceptFilter(Entity.class, "name", "children");
objectMapper.addFilterAllExceptFilter(EntityChildren.class, "childName");
return serviceEntity.findAll();
}
P.S. This approach has one major flaw: you cannot add two different filters for one class.
There's Jackson plugin called squiggly for doing exactly this.
String filter = "name,children[childName]";
ObjectMapper mapper = Squiggly.init(this.objectMapper, filter);
mapper.writeValue(response.getOutputStream(), myBean);
You could integrate it into a MessageConverter or similar, driven by annotations, as you see fit.
If you have a fixed number of possible options, then there is a static solution too: #JsonView
public interface NameAndChildName {}
#JsonView(NameAndChildName.class)
#ResponseBody
public List<Entity> findAll() {
return serviceEntity.findAll();
}
public class Entity {
public String id;
#JsonView(NameAndChildName.class)
public String name;
#JsonView({NameAndChildName.class, SomeOtherView.class})
public List<Child> children;
}
public class Child {
#JsonView(SomeOtherView.class)
public String id;
#JsonView(NameAndChildName.class)
public String childName;
}

Deserializing non-string map keys with Jackson

I have a a map that looks like this:
public class VerbResult {
#JsonProperty("similarVerbs")
private Map<Verb, List<Verb>> similarVerbs;
}
My verb class looks like this:
public class Verb extends Word {
#JsonCreator
public Verb(#JsonProperty("start") int start, #JsonProperty("length") int length,
#JsonProperty("type") String type, #JsonProperty("value") VerbInfo value) {
super(length, length, type, value);
}
//...
}
I want to serialize and deserialize instances of my VerbResult class, but when I do I get this error: Can not find a (Map) Key deserializer for type [simple type, class my.package.Verb]
I read online that you need to tell Jackson how to deserialize map keys, but I didn't find any information explaining how to go about doing this. The verb class needs to be serialized and deserialzed outside of the map as well, so any solution should preserve this functionality.
Thank you for your help.
After a day of searching, I came across a simpler way of doing it based on this question. The solution was to add the #JsonDeserialize(keyUsing = YourCustomDeserializer.class) annotation to the map. Then implement your custom deserializer by extending KeyDeserializer and override the deserializeKey method. The method will be called with the string key and you can use the string to build the real object, or even fetch an existing one from the database.
So first in the map declaration:
#JsonDeserialize(keyUsing = MyCustomDeserializer.class)
private Map<Verb, List<Verb>> similarVerbs;
Then create the deserializer that will be called with the string key.
public class MyCustomDeserializer extends KeyDeserializer {
#Override
public MyMapKey deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException {
//Use the string key here to return a real map key object
return mapKey;
}
}
Works with Jersey and Jackson 2.x
As mentioned above the trick is that you need a key deserializer (this caught me out as well). In my case a non-String map key was configured on my class but it wasn't in the JSON I was parsing so an extremely simple solution worked for me (simply returning null in the key deserializer).
public class ExampleClassKeyDeserializer extends KeyDeserializer
{
#Override
public Object deserializeKey( final String key,
final DeserializationContext ctxt )
throws IOException, JsonProcessingException
{
return null;
}
}
public class ExampleJacksonModule extends SimpleModule
{
public ExampleJacksonModule()
{
addKeyDeserializer(
ExampleClass.class,
new ExampleClassKeyDeserializer() );
}
}
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule( new ExampleJacksonModule() );
Building on the answer given here that suggests to implement a Module with a deserializer. The JodaTime Module is an easy to understand full example of a module containing serializers and deserializers.
Please note that the Module feature was introduced in Jackson version 1.7 so you might need to upgrade.
So step by step:
create a module containing a (de)serializer for your class based on the Joda example
register that module with mapper.registerModule(module);
and you'll be all set
Assuming we have a Map property, like the following:
class MyDTO{
#JsonSerialize(keyUsing = MyObjectKeySerializer.class)
#JsonDeserialize(keyUsing = MyObjectKeyDeserilazer.class)
private Map<MyObjectKey , List<?>> map;
}
We serilize the MyObjectKey as a json string, while call objectMapper.writeAsString;
And deserilize from the json string,to MyObjectKey
public class MyObjectKeySerializer extends StdSerializer<MyObjectKey> {
public Serializer() {
super(MyObjectKey.class);
}
#Override
public void serialize(MyObjectKey value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeFieldName(JsonUtil.toJSONString(value));
}
}
public class MyObjectKeyDeserializer extends KeyDeserializer {
#Override
public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
return JsonUtil.toObject(key, MyObjectKey.class);
}
}
After scouring the web, I think I have a decent solution for how to handle POJO-style keys (although, as always, you are best served not using a full object as a map key).
Serializer (registered as a Jackson module, inside of Spring Boot):
#Bean
fun addKeySerializer(): Module =
SimpleModule().addKeySerializer(YourClass::class.java, YourClassSerializer())
class YourClassSerializer() : JsonSerializer<YourClass>() {
override fun serialize(value: DataElement, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeFieldName(jacksonObjectMapper().writeValueAsString(value))
}
}
(note that, in a standard Java environment, you will have to instantiate your own objectMapper instance here)
Deserializer:
#Bean
fun addKeyDeserializer(): Module =
SimpleModule().addKeyDeserializer(YourClass::class.java, YourClassDeserializer())
class YourClassDeserializer() : KeyDeserializer() {
override fun deserializeKey(key: String, ctxt: DeserializationContext): YourClass? {
return ctxt.parser.readValueAs(YourClass::class.java)
}
}

Categories