I am doing a simple conversion using Jackson:
response = mapper.readValue(responseStr, PrinterStatus.class);
The code is throwing this exception:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "OutputParameters" (class com.xerox.PrinterStatus),
not marked as ignorable (one known property: "outputParameters"]) at ....
at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:61)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:823)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1153)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1589)
The Json I would like to convert is very simple:
{
"OutputParameters": {
"#xmlns": "http://xmlns.xerox.com/apps/rest/",
"#xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
"GETPRINTERSTATUS": {
"GETPRINTERSTATUS_ITEM": [{
"STATUS": "True",
"MESSAGE": " "
}]
}
}
}
This is the PrinterStatus class, it has the field "OutputParameters"
So I am not sure what is Jackson yelling about.
public class PrinterStatus {
private OutputParameters outputParameters;
public OutputParameters getOutputParameters() {
return outputParameters;
}
public void setOutputParameters(OutputParameters outputParameters) {
this.outputParameters = outputParameters;
}
...
Basically JSON keys are case sensitive. Accordingly OutputParameters doesn't equal to outputParameters.
So you have to choose:
rename the field in Java class (and getters / setters too) to OutputParameters
rename JSON property key to outputParameters
If you using Jackson 2.9 or above just simply annotate field like this:
public class PrinterStatus {
#JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
private OutputParameters outputParameters;
public OutputParameters getOutputParameters() {
return outputParameters;
}
public void setOutputParameters(OutputParameters outputParameters) {
this.outputParameters = outputParameters;
}
...
}
Set property name explicitly
public class PrinterStatus {
#JsonProperty("OutputParameters")
private OutputParameters outputParameters;
...
}
Enable case insesitive feature globally
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
Related
I have an ASP.NET Core Web API.
I an endpoint which accepts a model called Search. It has property called Query of type Expression. This Expression object has sub classes.
public class Search {
public Expression Query{get;set;}
}
Public class Expression {
}
public class AndExpression {
public IList<Expression> Expressions {get;set;}
}
public class MatchesExpression {
public string FieldId {get;set;}
public string Value {get;set;}
public string Operator {get;set;}
}
I post the following JSON to my endpoint (content-type of application/json)
{
"query":
{
"fieldId": "body",
"value": "cake",
"operator": "matches"
}
}
Firstly, the query parameter is just the base Expression - A polymorphic issue!
So... I thought bespoke Model Binder.
I can set up a model binder against the Search object, but you'll note that the AndExpression can contain other Expression objects, so instead I'd like to write a binder that can be bound to "Query" on the Search Model and to Expressions on the AndExpression Model etc etc
I attempted this:
public class Search
{
[ModelBinder(BinderType = typeof(ExpressionBinder))]
public Expression Query { get; set; }
}
public class ExpressionBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
throw new NotImplementedException();
}
}
public class ExpressionBinderProvider : IModelBinderProvider {
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(Expression))
{
return new BinderTypeModelBinder(typeof(ExpressionBinder));
}
return null;
}
}
Ive wired this binder up in the configureServices method of my Startup Class.
I have a break point in the ExpressionBinder and it doesn't hit!
What am I doing wrong?
Also, can I use the [ModelBinder(BinderType = typeof(ExpressionBinder))] attribute against a list of Expressions?
So this is very explicit https://github.com/aspnet/Mvc/issues/4553
If a FromBody attribute is applied the ModelBinder attribute wont work!
Idea is simple. I have a object and I would like to hide some fields based on the some specific roles.
I have roles in the system "dog", "cat" etc.
class Food{
String name;
#HideInfoForTheRoles({"dog", "cat"})
String age;
}
So I think to create something like that:
public String hideForRole(T object, String role){
// return new json
}
Or maybe I can override some denationalization method to force Jackson to hide field based on my annotation?
You could use #JsonView. That's probably the easiest solution, as #JsonView works out-of-the-box with JAX-RS.
Alternativerly, it could be achieved with a BeanPropertyFilter, similar to another solution I put together a while ago.
Start defining your annotation:
#Documented
#Retention(RUNTIME)
#Target({FIELD})
public #interface HiddenForRoles {
String[] value();
}
Then define your BeanPropertyFilter, which can extend SimpleBeanPropertyFilter:
public class HiddenForRolesPropertyFilter extends SimpleBeanPropertyFilter {
private String allowedRole;
public HiddenForRolesPropertyFilter(String allowedRole) {
this.allowedRole = allowedRole;
}
#Override
public void serializeAsField(Object pojo, JsonGenerator jgen,
SerializerProvider provider,
PropertyWriter writer) throws Exception {
HiddenForRoles hiddenForRoles = writer.getAnnotation(HiddenForRoles.class);
if (hiddenForRoles != null) {
if (Arrays.asList(hiddenForRoles.value()).contains(allowedRole)) {
writer.serializeAsOmittedField(pojo, jgen, provider);
return;
}
}
// If no annotation is provided, the property will be serialized
writer.serializeAsField(pojo, jgen, provider);
}
}
Place the #HiddenForRoles annotation in your fields, according to your needs and ensure the class is annotated with #JsonFilter:
#Data
#JsonFilter("hiddenForRolesPropertyFilter")
public class Foo {
private String bar;
#HiddenForRoles({"cat"})
private String biz;
}
Finally, register the filter in a ContextResolver for ObjectMapper:
String currentUserRole = // Get role from the current user
FilterProvider filterProvider = new SimpleFilterProvider()
.addFilter("hiddenForRolesPropertyFilter",
new HiddenForRolesPropertyFilter(currentUserRole));
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:
#JsonFilter("hiddenForRolesPropertyFilter")
public class HiddenForRolesPropertyFilterMixIn {
}
Then bind the mix-in class to Object:
mapper.addMixIn(Object.class, HiddenForRolesPropertyFilterMixIn.class);
Create annotation that supports on FIELD and METHOD
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.METHOD})
public #interface HideFor{
String[] roles() default{};
}
and logic that supports annotation for both field and methods
public class AccessRestrictionFilter extends SimpleBeanPropertyFilter {
#Override
public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer)
throws Exception {
if(writer.getAnnotation(HideFor.class)!=null && isHidable( Arrays.asList(writer.getAnnotation(HideFor.class).roles()))){
logger.debug("Found restriction on the getter method of the field: " + pojo + " Restriction For" + Arrays.toString(writer.getAnnotation(HideFor.class).roles()) );
return;
}
Field[] fields = jgen.getCurrentValue().getClass().getDeclaredFields();
Optional<Field> field = Arrays.stream(fields)
.filter(f-> f.getName().equalsIgnoreCase(writer.getName())).findAny();
if(field.isPresent() && field.get().getAnnotation(HideFor.class)!=null){
if(isHidable( Arrays.asList(writer.getAnnotation(HideFor.class).roles()))){
System.out.println("Found restriction on the field " + field.get().getName() + " Restriction For " + Arrays.toString(writer.getAnnotation(HideFor.class).roles()));
return;
}
}
writer.serializeAsField(pojo, jgen, provider);
}
private boolean isHidable(List<String> rolesToHide){ // imlement the logic // }
}
Usage:
FilterProvider filterProvider = new SimpleFilterProvider().addFilter("AccessRestrictionFilter", new AccessRestrictionFilter());
new ObjectMapper().writer(filterProvider ).writeValueAsString(myObjToFilter);
I use Jersey/Spring and my configuration looks like this:
#Provider
#Produces({MediaType.APPLICATION_JSON})
public class JacksonJsonProvider extends JacksonJaxbJsonProvider {
public JacksonJsonProvider(AccessRestrictionFilter filter) {
ObjectMapper objectMapper = new ObjectMapper()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.setFilterProvider(new SimpleFilterProvider().addFilter("AccessRestriction", filter));
setMapper(objectMapper);
}
}
And Filter:
#Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
#Bean("accessRestrictionFilter")
public AccessRestrictionFilter accessRestrictionFilter(){
return new AccessRestrictionFilter();
}
Note: in the filter I use the Security Context, because of this scope of the filter is Session (Not to share the state but create new object for each user)
and that's my POJO:
#JsonFilter("AccessRestrictionFilter")
public class MyClass {
#HideFor(roles = {"ROLE_USER", "ROLE_EDITOR"})
private int val;
Stuck with the problem of MyEnum enum deserialization from JSON to POJO and cannot figure out what I do wrong. So basically I try to retrieve some data calling particular microservice endpoint that returns the following json:
{
"id": "9cabf3e9-965d-4407-b62b-c57dd6006419",
"myEnums": [
{
"context": "SOME_FOO_CONTEXT_1",
"feature": "SOME_BAR_FEATURE_1",
"name": "SOME_FOO_BAR_1"
},
{
"context": "SOME_FOO_CONTEXT_2",
"feature": "SOME_BAR_FEATURE_2",
"name": "SOME_FOO_BAR_2"
}
],
"name": "Some name",
"updatedBy": null,
"updated": "2019-05-16T00:11:19.279Z"
}
This is the method that calls another microservice endpoint, deserialize response body to POJO and return result as Set:
private Mono<Set<MyEnum>> fetchMyEnums(UUID someId) {
return webClient.get().uri("/v1/something/{id}", someId)
.retrieve()
.bodyToMono(MyClass.class)
.flatMapIterable(MyClass::getMyEnums)
.collect(toSet());
}
The class that used for JSON deserialization:
#lombok.Value
static class MyClass {
List<MyEnum> myEnums;
}
Enum that I actually cannot deserialize:
#Getter
#RequiredArgsConstructor
#AllArgsConstructor
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum MyEnum {
SOME_FOO_BAR_1(SOME_FOO_CONTEXT_1, SOME_BAR_FEATURE_1),
SOME_FOO_BAR_2(SOME_FOO_CONTEXT_2, SOME_BAR_FEATURE_2);
private final FooEnum context;
private final BarEnum feature;
private String name;
#JsonProperty
public String getName() {
return super.name();
}
}
During deserialization I receive the following exception:
org.springframework.core.codec.DecodingException: JSON decoding error: Cannot deserialize instance of `com.learn.common.security.model.MyEnum` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `com.learn.common.security.model.MyEnum` out of START_OBJECT token
at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: com.learn.common.security.service.MyEnumService$MyClass["myEnums"]->java.util.ArrayList[0])
Where I did mistake?
So spending few more hours to clarify what's the problem with deserialization i figure out that there is no automatic deserialization for Enum whose Shape.Object.
But I found workaround how to deserialize MyEnum object from json(you need define static method marked it as JsonCreator and define what input parameter you expect to catch from object defining JsonProperty with fieldName):
#Getter
#RequiredArgsConstructor
#AllArgsConstructor
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum MyEnum {
SOME_FOO_BAR_1(SOME_FOO_CONTEXT_1, SOME_BAR_FEATURE_1),
SOME_FOO_BAR_2(SOME_FOO_CONTEXT_2, SOME_BAR_FEATURE_2);
private final FooEnum context;
private final BarEnum feature;
private String name;
#JsonProperty
public String getName() {
return super.name();
}
#JsonCreator
public static MyEnum fromJson(#JsonProperty("name") String name) {
return valueOf(name);
}
}
For sake of completeness: You can add the #JsonCreator annotation either to a constructor or to a factory method.
Constructor:
#JsonCreator
public MyEnum(#JsonProperty("name") String name) {
this.name = name;
}
Factory method:
#JsonCreator
public static MyEnum fromJson(#JsonProperty("name") String name) {
return valueOf(name);
}
Multiple parameters:
If your enum type contains multiple properties, add them to the signature with #JsonProperty annotation.
#JsonCreator
public MyEnum(#JsonProperty("id") String id, #JsonProperty("name") String name) {
this.id = id;
this.name = name;
}
When using a factory method, creation from JSON might fail, if you have defined multiple properties. You may get this error message:
Unsuitable method [...] decorated with #JsonCreator (for Enum type [...])
Some versions of Jackson cannot handle this case. Use constructor method as a workaround, if your enum type contains more than one property.
Assuming I have this objects:
class Person {
String name;
Household getHousehold();
}
class Household {
Set<Address> getAddresses();
String householdId;
}
which would normally be serialized as follows
{
"name": "XXX",
"household": {
"addresses": [...]
}
}
Is there a way to configure Jackson with annotations / mix-ins to obtain this (ie. without using DTO) ?
{
"name": "XXX",
"addresses": [...],
"household": {
"householdId": 123
}
}
You can configure the unwrapping of a specific property by both using mixins and annotations:
1. Mixins
Assuming you define the following mixin:
public abstract class UnwrappedAddresses {
#JsonUnwrapped
public abstract Household getHouseHold();
}
And then add a custom module to your objectMapper which applies the mixin to the Person class as follows:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper .registerModule(new SimpleModule() {
#Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(Person.class, UnwrappedAddresses.class);
}
});
This approach does not change the Household serialization as a single item, but just unwraps a household item when it's encapsulated in a Person object.
2. Annotations
Just add #JsonUnwrapped to your getHouseHold() method.
EDIT: After post changes.
What you want is basically to change the output of the json, which can be done by using the #JsonAnyGetter annotation(which can dynamically add new properties to your pojo).
Your expected result can be achieved by ignoring the household property and unwrapping it with the help of the #JsonAnyGetter.
#JsonIgnoreProperties("houseHold")
public static class Person {
String name;
Household houseHold;
#JsonAnyGetter
public Map<String,Object> properties(){
Map<String,Object> additionalProps=new HashMap<>();
additionalProps.put("addresses", new ArrayList<>(houseHold.getAddresses()));
Map<String,Object> houseHolProps=new HashMap<>();
houseHolProps.put("houseHoldId", houseHold.id);
additionalProps.put("houseHold", houseHolProps);
return additionalProps;
}
..getters&setters omitted
}
Which would after serialization return
{"name":"name",
"houseHold":{"houseHoldId":0},
"addresses":[
{"houseNo":2,"street":"abc"},
{"houseNo":1,"street":"str"}
]
}
I have the following interface
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "className")
public interface InfoChartInformation {
public String name();
}
And the following implementation (enum):
public class InfoChartSummary {
public static enum Immobilien implements InfoChartInformation {
CITY, CONSTRUCTION_DATE;
}
public static enum Cars implements InfoChartInformation {
POWER, MILEAGE;
}
}
Then I use all of It in the following entity:
#Entity(noClassnameStored = true)
#Converters(InfoChartInformationMorphiaConverter.class)
public class TestEntity{
#Id
public ObjectId id;
#Embedded
public List<InfoChartInformation> order;
}
Jackson, in order to detect the type on the unmarshalling time, will add to every enum on the list the className.
I thought morphia would do the same, but there's no field className in the List of enum and the unmarshalling cannot be done correctly: java.lang.RuntimeException: java.lang.RuntimeException: java.lang.RuntimeException: java.lang.ClassCastException: java.lang.String cannot be cast to com.mongodb
.DBObject
I guess the correct behavior should be to save all the enum route (package+name), not only the enum name. At least in that way the unmarshalling could be performed. There's a way morphia supports that by default or I need to create my own converter (similar to this) ?
I tried creating a Custom Converter:
public class InfoChartInformationMorphiaConverter extends TypeConverter{
public InfoChartInformationMorphiaConverter() {
super(InfoChartInformation.class);
}
#Override
public Object decode(Class targetClass, Object fromDBObject, MappedField optionalExtraInfo) {
if (fromDBObject == null) {
return null;
}
String clazz = fromDBObject.toString().substring(0, fromDBObject.toString().lastIndexOf("."));
String value = fromDBObject.toString().substring(fromDBObject.toString().lastIndexOf(".") + 1);
try {
return Enum.valueOf((Class)Class.forName(clazz), value);
} catch (ClassNotFoundException e) {
return null;
}
}
#Override
public Object encode(final Object value, final MappedField optionalExtraInfo) {
return value.getClass().getName() + "." + ((InfoChartInformation) value).name();
}
}
Then, I added the converter information to morphia morphia.getMapper().getConverters().addConverter(new InfoChartInformationMorphiaConverter());.
However, when serializing (or marshalling) the object to save it into the database, the custom converter is ignored and the Enum is saved using the default Morphia converter (only the enum name).
If I use in the TestEntity class only an attribute InfoChartInformation; instead of the List<>InfoChartInformation>, my customer converter will work. However I need support for List
Use:
public class InfoChartInformationMorphiaConverter extends TypeConverter implements SimpleValueConverter
It is a marker interface required to make your Convertor work.