Jackson: How to use a custom deserializer with #JsonAnySetter annotation? - java

I have several YAML config files I want to deserialize into a class. The YAML in the files consists of simple name value pairs with no nesting. There's a handful of properties that will have explicit fields, but the rest I just want dumped into a Map.
This all works fine, but I also want all the values of the properties that get deserialized into the Map through .add() to be run through a custom deserializer. I've tried using #JsonDeserialize on the setter value parameter and the setter method itself but Jackson seems to ignore it altogether.
Here's how it's set up:
public class ConfigData {
private Map<String, Object> dynamicConfig = new LinkedHashMap<>();
#JsonAnyGetter
public Map<String, Object> getConfig() {
return dynamicConfig;
}
#JsonAnySetter
public void add(String name, #JsonDeserialize(using = FooDeserializer.class) Object value) {
dynamicConfig.put(name, value);
}
#JsonProperty("some_special_property")
public String setSomeSpecialProperty(String value) {
add("some_special_property", value);
}
}
And to deserialize:
public static ConfigData getConfig(URL configResource) throws IOException {
try (InputStream stream = configResource.openStream()) {
ObjectMapper mapper = new YAMLMapper();
return mapper.readValue(new InputStreamReader(stream, StandardCharsets.UTF_8), ConfigData.class);
}
}

I discovered the problem was that I was specifying the deserializer class with the using property of the #JsonDeserialize annotation. For this specific use case I needed to use the contentUsing property instead, which is used for things like the value field of a Map entry.
This is what my setter looks like now:
#JsonAnySetter
#JsonDeserialize(contentUsing = FooDeserializer.class)
public void add(String name, Object value) {
dynamicConfig.put(name, value);
}
Now all the values will be serialized using FooDeserializer, except for "some_special_property" which has its own setter.

Related

Jackson Custom Serializer shows the same context for 2 different field during the Json Serialization

I am trying to create a JSON based on my Object class POJO. For some fields, I would like to use the CustomSerializer as I would like to create the fields according to my requirement. Hence, I have created the CustomSerializer.class.
The CustomSerializer will be called by 2 different fields in my POJO and I would like to handle the things differently based on which field is making the call. For one of the fields (extensions) I would like to have the fieldName and for other field (withoutExtensions) I do not wish to have the fieldname in my JSON.
The problem I am facing is that when CustomSerializer is called then I am getting the same fieldname for both the calls due to which I am unable to make a differentiation which field is currently calling the CustomSerializer.
Following code samples will provide more clarity on the issue I am facing:
Customer POJO class used for serializing the JSON:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#Data
#NoArgsConstructor
public class Customer {
private String isA;
private String name;
#JsonSerialize(using = CustomSerializer.class)
private Map<String, Object> extensions = new HashMap<>();
private Map<String, Object> withoutExtensions = new HashMap<>();
#JsonAnyGetter
#JsonSerialize(using = CustomSerializer.class)
public Map<String, Object> getWithoutExtensions() {
return withoutExtensions;
}
}
Following is my CustomSerializer which will be called by 2 fields (extensions and withoutExtensions) during the creation of JSON:
public class CustomSerializer extends JsonSerializer<Map<String, Object>> {
#Override
public void serialize(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) {
//I would like to create the outer object for "Extensions" but do not want to create outer object for "WithoutExtensions"
System.out.println(gen.getOutputContext().getCurrentName());
//In my case for both "Extensions" and "WithoutExtensions" i get the "currentName" as "Extensions" how can I ensure which field is calling this sealizer at
// present
}
}
Following is my Main class which will create a JSON:
public class Main {
public static void main(String[] args) throws JsonProcessingException {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
final Customer customer = new Customer();
customer.setName("Jackson");
Map<String, Object> extensions = new HashMap<>();
extensions.put("WithObject", "With");
customer.setExtensions(extensions);
Map<String, Object> withoutExtensions = new HashMap<>();
extensions.put("WithoutObject", "Without");
customer.setWithoutExtensions(withoutExtensions);
final String eventAsJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer);
System.out.println(eventAsJson);
}
}
As we can see when I run the application the CustomSerializer would print extensions in both cases. I believe it should print extensions only once and in the next case either it should provide withoutExtensions or empty string.
I just wanted to know if this an bug on the Jackson part or is there any work-around that I can try to differentiate which field is making a call to my CustomSerializer.
Any help would be really appreciated. Thanks.
A. Create two Map serialisers where one creates outer object and another not
Pros:
Easy to implement
Easy to test
One class does exactly one thing
Map serialiser which does not create outer object could be replaced by custom Map serialiser (if possible)
Cons:
Could be problematic if they need to share state.
Possibly duplicated code
B. Implement ContextualSerializer interface
Pros:
Can be configured for every field separately
Can share state if needed. User control how many instances are created.
Cons:
Does more than 1 thing
Can be easily over complicated
Examples:
Need Jackson serializer for Double and need to specify precision at runtime
Jackson custom annotation for custom value serialization
Deserialize to String or Object using Jackson
Jackson - deserialize inner list of objects to list of one higher level
Based on the response from #Michal I modified the code and it worked for both the scenario. Posting the complete code sample as it can be helpful to someone in the future:
Customer.class added the #Extensions on required fields:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#Data
#NoArgsConstructor
public class Customer {
private String isA;
private String name;
#JsonSerialize(using = CustomSerializer.class)
#Extensions(extension = "extensions")
private Map<String, Object> extensions = new HashMap<>();
private Map<String, Object> withoutExtensions = new HashMap<>();
#JsonAnyGetter
#JsonSerialize(using = CustomSerializer.class)
#Extensions(extension = "withoutExtensions")
public Map<String, Object> getWithoutExtensions() {
return withoutExtensions;
}
}
CustomSerializer:
#NoArgsConstructor
public class CustomSerializer extends JsonSerializer<Map<String, Object>> implements ContextualSerializer {
private String context = "";
public CustomSerializer(String context) {
this.context = context;
}
#Override
public void serialize(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) {
if (this.context.equals("extensions")) {
System.out.println("Extensions : " + this.context);
} else if (this.context.equals("withoutExtensions")) {
System.out.println("Without Extensions : " + this.context);
}
}
#Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
Extensions extensions = beanProperty.getAnnotation(Extensions.class);
if (extensions != null) {
return new CustomSerializer(extensions.extension());
}
return this;
}
}
#Target({ElementType.FIELD, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#interface Extensions {
String extension();
}

mapstruct convert list to map

I am a very new to mapstruct. I am trying to convert List to Map, I've searched a lot online, I've got some solutions like its not yet implemented in mapstruct seems. I will be glad if someone could able to provide some alternative solution.
All I am looking to convert mapping as below:
#Mapping
Map<String, Object> toMap(List<MyObj>)
#Mapping
List<MyObj> toList(Map<String, Object>)
where MyObj as below:
class MyObj {
String key; //map key
String value; //map value
String field1;
}
In above, only use key and value fields from MyObj class. I've found one solution but below is converting some object to MAP, but using Jackson below:
#Mapper
public interface ModelMapper {
ObjectMapper OBJECT_MAPPER = new ObjectMapper();
default HashMap<String, Object> toMap(Object filter) {
TypeFactory typeFactory = OBJECT_MAPPER.getTypeFactory();
return OBJECT_MAPPER.convertValue(filter, typeFactory.constructMapType(Map.class, String.class, Object.class));
}
}
is there anyway now to implement using mapstruct?
Map struct doesn't have implicit conversion for your desired List to Map. You can have a custom mapping method as follows:
#Mapper
public interface FooMapper {
default Map<String, Foo> convertFooListToMap(List<Foo> foos) {
// custom logic using streams or however you like.
}
}
Other options include custom mapper implementations that you write and refer with something like #Mapper(uses=CustomMapper.class)

Jackson filtering out fields without annotations

I was trying to filter out certain fields from serialization via SimpleBeanPropertyFilter using the following (simplified) code:
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter("test",
SimpleBeanPropertyFilter.filterOutAllExcept("data1"));
try {
String json = mapper.writer(filterProvider).writeValueAsString(new Data());
System.out.println(json); // output: {"data1":"value1","data2":"value2"}
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
private static class Data {
public String data1 = "value1";
public String data2 = "value2";
}
Us I use SimpleBeanPropertyFilter.filterOutAllExcept("data1")); I was expecting that the created serialized Json string contains only {"data1":"value1"}, however I get {"data1":"value1","data2":"value2"}.
How to create a temporary writer that respects the specified filter (the ObjectMapper can not be re-configured in my case).
Note: Because of the usage scenario in my application I can only accept answers that do not use Jackson annotations.
If for some reason MixIns does not suit you. You can try this approach:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector(){
#Override
public boolean hasIgnoreMarker(final AnnotatedMember m) {
List<String> exclusions = Arrays.asList("field1", "field2");
return exclusions.contains(m.getName())|| super.hasIgnoreMarker(m);
}
});
You would normally annotate your Data class to have the filter applied:
#JsonFilter("test")
class Data {
You have specified that you can't use annotations on the class. You could use mix-ins to avoid annotating Data class.
#JsonFilter("test")
class DataMixIn {}
Mixins have to be specified on an ObjectMapper and you specify you don't want to reconfigure that. In such a case, you can always copy the ObjectMapper with its configuration and then modify the configuration of the copy. That will not affect the original ObjectMapper used elsewhere in your code. E.g.
ObjectMapper myMapper = mapper.copy();
myMapper.addMixIn(Data.class, DataMixIn.class);
And then write with the new ObjectMapper
String json = myMapper.writer(filterProvider).writeValueAsString(new Data());
System.out.println(json); // output: {"data1":"value1"}
The example of excluding properties by name:
public Class User {
private String name = "abc";
private Integer age = 1;
//getters
}
#JsonFilter("dynamicFilter")
public class DynamicMixIn {
}
User user = new User();
String[] propertiesToExclude = {"name"};
ObjectMapper mapper = new ObjectMapper()
.addMixIn(Object.class, DynamicMixIn.class);
FilterProvider filterProvider = new SimpleFilterProvider()
.addFilter("dynamicFilter", SimpleBeanPropertyFilter.filterOutAllExcept(propertiesToExclude));
mapper.setFilterProvider(filterProvider);
mapper.writeValueAsString(user); // {"name":"abc"}
You can instead of DynamicMixIn create MixInByPropName
#JsonIgnoreProperties(value = {"age"})
public class MixInByPropName {
}
ObjectMapper mapper = new ObjectMapper()
.addMixIn(Object.class, MixInByPropName.class);
mapper.writeValueAsString(user); // {"name":"abc"}
Note: If you want exclude property only for User you can change parameter Object.class of method addMixIn to User.class
Excluding properties by type you can create MixInByType
#JsonIgnoreType
public class MixInByType {
}
ObjectMapper mapper = new ObjectMapper()
.addMixIn(Integer.class, MixInByType.class);
mapper.writeValueAsString(user); // {"name":"abc"}
It seems you have to add an annotation which indicts which filter to use when doing the serialization to the bean class if you want the filter to work:
#JsonFilter("test")
public class Data {
public String data1 = "value1";
public String data2 = "value2";
}
EDIT
The OP has just added a note that just take the answer that not using a bean animation, then if the field you want to export is very less amount, you can just retrieve that data and build a Map of List yourself, there seems no other way to do that.
Map<String, Object> map = new HashMap<String, Object>();
map.put("data1", obj.getData1());
...
// do the serilization on the map object just created.
If you want to exclude specific field and kept the most field, maybe you could do that with reflect. Following is a method I have written to transfer a bean to a map you could change the code to meet your own needs:
protected Map<String, Object> transBean2Map(Object beanObj){
if(beanObj == null){
return null;
}
Map<String, Object> map = new HashMap<String, Object>();
try {
BeanInfo beanInfo = Introspector.getBeanInfo(beanObj.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor property : propertyDescriptors) {
String key = property.getName();
if (!key.equals("class")
&& !key.endsWith("Entity")
&& !key.endsWith("Entities")
&& !key.endsWith("LazyInitializer")
&& !key.equals("handler")) {
Method getter = property.getReadMethod();
if(key.endsWith("List")){
Annotation[] annotations = getter.getAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof javax.persistence.OneToMany){
if(((javax.persistence.OneToMany)annotation).fetch().equals(FetchType.EAGER)){
List entityList = (List) getter.invoke(beanObj);
List<Map<String, Object>> dataList = new ArrayList<>();
for(Object childEntity: entityList){
dataList.add(transBean2Map(childEntity));
}
map.put(key,dataList);
}
}
}
continue;
}
Object value = getter.invoke(beanObj);
map.put(key, value);
}
}
} catch (Exception e) {
Logger.getAnonymousLogger().log(Level.SEVERE,"transBean2Map Error " + e);
}
return map;
}
But I recommend you to use Google Gson as the JSON deserializer/serializer And the main reason is I hate dealing with exception stuff, it just messed up with the coding style.
And it's pretty easy to satisfy your need with taking advantage of the version control annotation on the bean class like this:
#Since(GifMiaoMacro.GSON_SENSITIVE) //mark the field as sensitive data and will not export to JSON
private boolean firstFrameStored; // won't export this field to JSON.
You can define the Macro whether to export or hide the field like this:
public static final double GSON_SENSITIVE = 2.0f;
public static final double GSON_INSENSITIVE = 1.0f;
By default, Gson will export all field that not annotated by #Since So you don't have to do anything if you do not care about the field and it just exports the field.
And if some field you are not want to export to json, ie sensitive info just add an annotation to the field. And generate json string with this:
private static Gson gsonInsensitive = new GsonBuilder()
.registerTypeAdapter(ObjectId.class,new ObjectIdSerializer()) // you can omit this line and the following line if you are not using mongodb
.registerTypeAdapter(ObjectId.class, new ObjectIdDeserializer()) //you can omit this
.setVersion(GifMiaoMacro.GSON_INSENSITIVE)
.disableHtmlEscaping()
.create();
public static String toInsensitiveJson(Object o){
return gsonInsensitive.toJson(o);
}
Then just use this:
String jsonStr = StringUtils.toInsensitiveJson(yourObj);
Since Gson is stateless, it's fine to use a static method to do your job, I have tried a lot of JSON serialize/deserialize framework with Java, but found Gson to be the sharp one both performance and handily.

Jackson JSON Array Value Deserialization

I am trying to de-serialize this JSON object using Jackson 2.8 as part of Retrofit response. Here is the JSON response I get from the server.
{
"id":"8938209912"
"version":"1.1"
"cars":{
"mercedes":[
{
"property":"color"
},
{
"property":"price"
},
{
"property":"location"
}
],
"tesla":[
{
"property":"environment"
}
]
}
}
Based on the query, the cars above may have one or more models returned. I cannot create a class each for each model as these get created/removed arbitrarily. For each model of the car (say tesla), there may be one or more property key-value pairs.
I am new to Jackson. I have been looking at several examples and looks like a custom #JsonDeserialize is the best way to go. So, I created Root class and Cars class like this:
// In file Root.java
public class Root {
#JsonProperty("id")
private String id = null;
#JsonProperty("version")
private String version = null;
#JsonProperty("cars")
private Cars cars = null;
}
// In file Cars.java
public class Cars {
public Cars(){}
#JsonDeserialize(using = CarDeserializer.class)
private Map<String, List<Property>> properties;
public Map<String, List<Property>> getProperties() {
return properties;
}
public void setProperties(Map<String, List<Property>> properties) {
this.properties = properties;
}
}
// Property.java
public class Property {
#JsonProperty("property")
private String property;
}
My de-serializer is below. However, even though the empty constructor gets called, the parse method itself is not called at all!
// CarDeserializer.class
public class RelationshipDeserializer extends StdDeserializer<Map<String, List<Action>>>{
protected RelationshipDeserializer(){
super(Class.class);
}
#Override
public Map<String, List<Action>> deserialize(JsonParser parser, DeserializationContext ctx)
throws IOException, JsonProcessingException
{
// This method never gets invoked.
}
}
My questions:
Is this the right approach in the first place?
Why do you think the execution never gets to the deserialize()? (I checked, the cars object is present in JSON.
Are there better approaches to parse this JSON using Jackson?
The "properties" deserializer is never called because that does not match anything in that JSON. The field name in the JSON is "property" and it does not match Map<String, List<Property>>. It looks like it would be closer to List<Property>
Do you control the in coming JSON? It would be better for the car name/type to be in its own field rather than the name of the object. Then you can use a generic object. What you have now is going to break. Any time they add a new name/type and you do not have a matching object for it.

Java Jackson writing object twice

I have the following class which contains a String field and a Map field. I want to use Jackson to serialize it to json.
public class Mapping
private String mAttribute;
#JsonIgnore
private Map<String, String> mMap;
#JsonAnyGetter
public Map<String, String> getMap() {
//some logic to populate map
}
#JsonAnySetter
public void put(// some params) {
//some more logic
}
#JsonProperty(value = "attribute")
public String getAttribute() {
return mAttribute;
}
public void setAttribute(String aAttribute) {
mAttribute= aAttribute;
}
}
I instantiate a Mapping object and then use ObjectMapper to write it to a file.
ObjectMapper om = new ObjectMapper();
om.writeValue(destFile, myMappingObject);
For some reason, it's writing the Mapping instance myMappingObject twice. I'm assuming I've not set some visibility option somewhere but I don't know where.
The json looks like this, only it comes up twice in the file.
{
"attribute" : "someValue",
"map-key1" : "map-value1",
"map-key2" : "map-value2"
}
There's this, but apparently it was fixed in previous version of Jackson. I also tried changing the name of the method to random() and it still gets called twice (the number of times it should).
The problem had nothing to do with the above class. I was using another class that had a list of Mappings. Before:
public class MappingsList {
#JsonProperty
private List<Mapping> mappings;
public List<Mapping> getMappings() {return mappings;}
}
After:
public class MappingsList {
private List<Mapping> mappings;
#JsonProperty
public List<Mapping> getMappings() {return mappings;}
}
And it worked. The cause is that the ObjectMapper was seeing two (2) properties in the MappingsList class and therefore doing serialization on both. First it would create json for the mappings field and then again for the getMappings() method.

Categories

Resources