PropertyNamingStrategy ignored when using #JsonAnyGetter - java

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/

Related

Deserialize YAML map of POJOs with Jackson where Map keys are an object field

Given class:
#Data
class Widget {
String name;
int price;
}
And the following yaml file:
Widget1:
price: 5
Widget2:
price: 6
Widget3:
price: 7
Using Jackson I want to deserialize this into a Map<String, Widget> where the widget name field is set to the corresponding map key. The following snippet works but has the downside of preventing use of immutable object types such as lombok #Value. Another inelegant solution I considered was creating a separate immutable WidgetWithName class that is constructed after jackson deserialization. If someone can propose a better approach, that would be of interest.
Map<String, Widget> getWidgets(String yaml) throws Exception {
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
TypeFactory typeFactory = mapper.getTypeFactory();
MapType mapType = typeFactory.constructMapType(HashMap.class, String.class, Widget.class);
map = mapper.readValue(yaml, mapType);
map.forEach((k, v) -> v.setName(k); // so much for immutability
return map
}
Jackson will not help you because it is a generalization over YAML and JSON (and XML or so I heard) and therefore provides less functionality than directly using SnakeYAML.
With SnakeYAML, you could theoretically implement this with custom constructors, but it would need a lot of knowledge about the deserialization process because YAML is not designed to merge nodes on different levels into one value.
Frankly, having a separate class is by far the simplest and best maintainable solution. A separate class named WidgetBuilder, with only the price field and a function Widget finish(String name) would allow you to
return map.entrySet().stream().map((k, v) -> v.finish(k)).collect(
Collectors.toMap(Widget::getName, Function.identity()));
which seems elegant enough. (You can collect to a list instead if you don't need the map anymore.)

Change naming of attributes in json without #JsonProperty

I have to convert my json from camelCase to kebab-case.
Example:
My Json:
{
"disclaimerConfirmed" : true
}
And I need:
{
"disclaimer-confirmed" : true
}
I cannot use #JsonProperty because it rename this atributes permanently. I am looking for something which will consume Json (can be as String) and returns modified json(as String).
Jackson supports naming strategies so you could read the input String to map (with camelCase strategy) and then write the map back to a String (with kebab-case which is natively supported );
Specific method you need to switch these conventions in ObjectMapper without annotations is:
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.*);
You can have different serializers for different cases or you can create pojo with #JsonProperty and use those where-ever required.
For example,
class A {
private String disclaimerConfirmed;
}
class AkebabCase {
#JsonProperty("disclaimer-confirmed")
private String disclaimerConfirmed;
}
So, if you want to serialize to kebab-case you can use converters to convert A to AkebabCase and then serialize.

Pass a parameter to Custom mapping function

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

Reading attribute and values into map when key contains another key

I've been using Jackson for a while to parse json files and load the attribute and value into a Map. This is essentially what my code looks like:
Map<String, String> map = new HashMap<String, String>();
ObjectMapper mapper = new ObjectMapper();
File file = new File(pathToSource);
map = mapper.readValue(file, new TypeReference<HashMap<String, String>>() {});
This has worked well for flat json files where the keys are flat, just containing attribute/value pairs.
{
"attr":"value"
"attr":"value"
...
}
Now one of my sources has begun putting a key inside another key and th readValue method pukes when it hits the inner key.
{ "key1":{
"attr":"value"
"attr":"value"
"key2":{
"attr":"value"
"attr":"value"
}
}
}
One caveat of my need is I want to capture the attribute name and the value both. If I parse the json more granularly, taking attributes one by one, I can't access the attribute name.
I've been looking at this for a bit now and can't find the right combination to parse the keys, while capturing the attribute name and value.
Any suggestions welcome.
One thing to note is that if you just used simpler version:
Map<String, Object> map = mapper.readValue(file, Map.class);
you would get a Map that contains Strings, Lists and Maps as values, corresponding to matching JSON Structure (String, Array, Object).
You can generally use type java.lang.Object to mean "use the matching basic Java type", so signature you are asking for is Map<String,Object>, unless you want to enforce specific value, or use a POJO type.

Creating Map or Set using GSON

I am using Google's GSON library and want to create a JSON which looks something like this:
{
"teamSet":[],
"classificationMap" : {}
}
Notice that [] and {} are empty Set and Map respectively and are not surrounded with double quotes.
I want to create this Json using the GSON add/addproperty method and not by converting a Java class into JSON using the Gson.toJson() method. The business use-case restricts me from creating specific Java classes as the JSON structure may change overtime. So I cannot use a class like this as this would rigidly tie a JSON structure with a concrete class
class Linkage{
private Set<String> teamSet;
private Map<String, String> classificationMap;
// getter, setter methods follow
...
}
When I use the GSON.addproperty(genericObject, type), it is appending double quotes around [] and {}. I am using couchbase for my DB requirements and the double quotes around [] and {} makes couchbase treat them as string and not as Set/Map. This renders my Map-Reduce views useless and buggy :(
Please let me know if its possible to create such a JSON without having to tie it up with a concrete JAVA class. Thanks !
My current code looks like this:
// create types
Type setType = new TypeToken<Set<String>>() {}.getType();
Type mapType = new TypeToken<Map<String, String>>() {}.getType();
Gson GSON = new Gson();
Set<String> teams = new HashSet<String>();
Map<String, String> classificationMap = new HashMap<String, String>();
JsonObject linkageJson = new JsonObject();
linkageJson.addProperty("teamSet", GSON.toJson(teams, setType));
linkageJson.addProperty("classificationMap", GSON.toJson(classificationMap, mapType));
In the 2.x line of the couchbase java sdk, there is the JsonObject class that could have fit your need.
It is perfect to create Json "by hand" and still have a simple generic object representation, and is the official way of putting json into the database via the sdk.
It would go like this :
JsonObject obj = JsonObject.create();
obj.put("teamSet", JsonArray.from(new ArrayList(teams)))
.put("classificationMap", JsonObject.from(classificationMap));
Granted this is a little bit contrived because arrays and sub-objects can only be constructed from respectively List<?> and Map<String, ?> factory methods. Also the class support a limited set of value types (no custom classes, only String, Number, Boolean, etc...).
i feel somewhere it is storing it toString() representation.
Please refer below link, it might help you.
click here

Categories

Resources