Using MapStruct, and would like to pass a constant/parameter to a Custom Mapper
I have destination that has a field of type Map<String, Restriction> restrictions. The source may have 2-3 String fields that need to be mapped to a single Restriction in the map. The key to the Map is just a constant. Using Map-struct Mapping annotation, I would like to pass this key into the custom mapper so that I can either create the initial value in the Map or retrieve an existing value in the Map.
#Mappings({
#Mapping(source="source.someField", target="restrictions", constant="someKey"),
#Mapping(source="source.startDate", target="restrictions", constant="someKey"),
#Mapping(source="source.EndDate", target="restrictions", constant="someKey")
})
public abstract void restrictToClassA(SomeDataEntity source, #MappingTarget ClassA destination);
Constant is an alternative to target. You cannot specify both in the same #Mapping annotation. But, from your story I gather that you have control over your 'ClassA' destination. So, you could group your mappings into quadruplets. So:
Public Quadrupel{
String prop1;
String prop2;
String prop3;
//Getters/setters
}
ClassA{
Quadrupel key1;
Quadrupel key2;
//Etc
//Getters/setters
}
Into ClassA. Properties would then be along the lines of your key name. Your custom mapper could pick those up and map them in your target map. Although, the key would then a bit dubious as well since the property would indicate the same
Related
In my current project the names of the model class fields are German. The fields are all annotated with #JsonProperty for the English translation of the names. E.g. #JsonProperty(value = "operation"). Is there a way in the configuration that the mapping of the fields is done using the JsonProperty annotation?
Example:
public class Auftrag {
#JsonProperty(value = "orderType")
private String auftragsart;
...
}
public class OrderDto {
private String orderType;
}
MapStruct uses the Java Bean convention to detect the properties. This means that it looks in the getters and setters.
Out-of-the-box you cannot use the #JsonProperty. However, you can create your own AccessorNamingStrategy that will provide the properties based on #JsonProperty. The AccessorNamingStrategy gives you access to the Abstract syntax tree, which means you can look for fields in types, check their annotations and check their values.
Keep in mind that MapStruct will only ask to get the property for a method, so you would need to get the property name, then find the field in the type, then look for the #JsonProperty annotation and its value.
You can read more about the AccessorNamingStrategy here in the documentation.
Scenario:
Using Jackson 2.4.5 I have a dynamic bean to be serialised into JSON which can store some of its state in 'optional' properties in an internal map and uses #JsonAnyGetter on an accessor method that returns this map, e.g:
public class DynamicJsonView {
private final Map<String, Object> optionalProperties = new HashMap<>();
private final String rqdProperty = "blah";
public String getRqdProperty() {
return rqdProperty;
}
public DynamicJsonView() {
optionalProperties.put("PROP_1", "value 1");
optionalProperties.put("PROP_2", "value 2");
optionalProperties.put("PROP_3", "value 3");
// etc - in reality populated from another map
}
#JsonAnyGetter
public Map<String, Object> any() {
return Collections.unmodifiableMap(optionalProperties);
}
}
Note the map keys are UPPER_CASE. When we setup our ObjectMapper we set the following naming strategy to convert properties to lower case (and replace camelCase with snake_case), e.g:
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
Problem:
This works exactly as expected with normal java properties, i.e. rqdProperty in the example above converts to rqd_property in the JSON serialized form, but the naming strategy is seemingly ignored for the map 'properties', with the upper case keys appearing unmodified. Having debugged the jackson LowerCaseWithUnderscoresStrategy#translate method and watched the input parameter values passed in as the object is serialised, it seems the keys are never passed through the naming strategy.
The obvious workaround is to pre-process the map keys and convert them all to lower case, but I wondered if there's something I'm missing with regards to the property naming strategy, or if this is simply a limitation of the library?
This is as designed since NamingStrategy is only applied to actual concrete properties, and not for Map keys, or "any" properties.
But if ability to include name mangling for any properties sounds like a good idea, you could request a new feature to do that: it could be enabled (for example) by a flag of #JsonAnySetter:
https://github.com/FasterXML/jackson-databind/issues/
Say I have the following class:
public class Parent {
public int age;
#JsonUnwrapped
public Name name;
}
Producing JSON:
{
"age" : 18,
"first" : "Joey",
"last" : "Sixpack"
}
How do I deserialize this back into the Parent class? I could use #JsonCreator
#JsonCreator
public Parent(Map<String,String> jsonMap) {
age = jsonMap.get("age");
name = new Name(jsonMap.get("first"), jsonMap.get("last"));
}
But this also effectively adds #JsonIgnoreProperties(ignoreUnknown=true) to the Parent class, as all properties map to here. So if you wanted unknown JSON fields to throw an exception, you'd have to do that yourself. In addition, if the map values could be something other than Strings, you'd have to do some manual type checking and conversion. Is there a way for Jackson to handle this case automatically?
Edit:
I might be crazy, but this actually appears to work despite never being explicitly mentioned in the documentation: http://fasterxml.github.io/jackson-annotations/javadoc/2.2.0/com/fasterxml/jackson/annotation/JsonUnwrapped.html
I was pretty sure it didn't work for me previously. Still, the proposed #JsonCreator approach might be preferred when custom logic is required to deserialize unwrapped polymorphic types.
You can use #JsonCreator with #JsonProperty for each field:
#JsonCreator
public Parent(#JsonProperty("age") Integer age, #JsonProperty("firstName") String firstName,
#JsonProperty("lastName") String lastName) {
this.age = age;
this.name = new Name(firstName, lastName);
}
Jackson does type checking and unknown field checking for you in this case.
It does work for deserialization as well, although it's not mentioned in the docs explicitly, like you said. See the unit test for deserialization of #JsonUnwrapped here for confirmation - https://github.com/FasterXML/jackson-databind/blob/d2c083a6220f2875c97c29f4823d9818972511dc/src/test/java/com/fasterxml/jackson/databind/struct/TestUnwrapped.java#L138
#JsonUnwrapped works for both serialization and deserialization, you shouldn't need to take any additional steps.
Despite not being mentioned in the Javadocs prior to Jackson 2.13 (per
jackson-annotations#184), the #JsonUnwrapped annotation does apply to deserialization as well as serialization, so no additional work is needed to support deserialization of a field using the annotation.
The Jackson 2.13 Javadocs for #JsonUnwrapped clarify that the annotation applies to deserialization as well as serialization:
Annotation used to indicate that a property should be serialized "unwrapped" -- that is, if it would be serialized as JSON Object, its properties are instead included as properties of its containing Object -- and deserialized reproducing "missing" structure.
[...]
When values are deserialized "wrapping" is applied so that serialized output can be read back in.
For those who googled here like me, trying to resolve issue when deserializing unwrapepd Map, there is a solution with #JsonAnySetter:
public class CountryList
{
Map<String, Country> countries = new HashMap<>();
#JsonAnySetter
public void setCountry(String key, Country value)
{
countries.put(key, value);
}
}
Class with Maps
public class Page {
private Map<LocaleWrapper, String> titles;
private Map<LocaleWrapper, String> texts;
(...)
}
Key class
public class LocaleWrapper implements Serializable {
private Locale locale;
//Constructor, getter, setter
}
My IDE throws an error:
Basic attributes can only be of the following types: (...), or any Serializable type.
Why does my IDE throw this error and how can I fix my mappings?
Thanks in advance.
This error, AFAIK, is displayed because the attribute is inside a JPA entity, and not just a simple class.
And Map is not serializable.
Do you really want to save the whole map as a serialized byte array, in a BLOB column? If so, choose a serializable type, like HashMap. If not, then the map needs to define some form of association between entities and/or embedded types, and I would first think about how you want to store the information in the database, and map the database schema to entities.
I'm using MappingJacksonJsonView to serialize to JSON a class, however, I'd like to be able to rename some of the fields from the default name based on the getter name.
This is because I've to output field names like "delete_url" and "delete_type" for jQuery file upload. I'm using #Jsonserialize annotation to hand pick the fields to serialize.
#JsonAutoDetect(getterVisibility = Visibility.NONE)
public interface Picture {
#JsonSerialize
String getName();
#JsonSerialize
String getDelete_url();
...
For instance, I'm forced to call a method getDelete_url(), while I'd like to call it getDeleteUrl(), but still output the key "delete_url" when serializing to JSON.
You should be able to qualify using #JsonProperty.
#JsonAutoDetect(getterVisibility = Visibility.NONE)
public interface Picture {
#JsonSerialize
#JsonProperty("name")
String getName();
#JsonSerialize
#JsonProperty("delete_url")
String getDeleteUrl();
//...
Have you tried using the #JsonProperty annotation?
"Defines name of the logical property, i.e. Json object field name to use for the property: if empty String (which is the default), will use name of the field that is annotated."