Cannot deserialize instance of MyEnum out of START_OBJECT token - jackson

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.

Related

Jackson fails with "Cannot construct instance of WorkpoolId (although at least one Creator exists): no int/Int-argument constructor/factory"

I have the following class
public class WorkpoolId implements Serializable {
#NotNull
private Long id = null;
#JsonCreator
public WorkpoolId(#JsonProperty("workpoolId") long id) {
this.id = Long.valueOf(id);
}
public WorkpoolId(Long id) {
this.id = id;
}
public WorkpoolId(String id) {
this.id = Long.valueOf(id);
}
private WorkpoolId() {
}
}
when mapping
"workpoolId":1
to this class I get a
javax.ws.rs.ProcessingException: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of WorkpoolId (although at least one Creator exists): no int/Int-argument constructor/factory method to deserialize from Number value (1)
Why is jackson not able to use the long constructor for the number value?
It fails because your WorkpoolId does not have access to field workpoolId it is not in its context anuymore. When your JSON is deserialized it could be deserialized either as an
independent object (having no field workpoolId, it IS the workbookId)
field object value in an object containing -say Data - it that might be named as workpoolId.
Now the use of workbookId could be usable for the JsonCreator in Data when constructing its field workpoolId.
To clarify this a bit, here is an example of possible Data class:
#Getter #Setter
public class Data {
private WorkpoolId workpoolId;
#JsonCreator // here it is a property!
public Data(#JsonProperty("workpoolId") long id) {
this.workpoolId = new WorkpoolId(id);
}
}
Json would be like {"workpoolId":1}
To have it work just remove the annotation #JsonProperty("workpoolId") from the attribute declaration. Actually the whole #JsonCreator annotation is not needed.

UnrecognizedPropertyException: Unrecognized field - jackson 2.9

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);

Using [FromQuery] to parse enum values

I'm using the [FromQuery] attribute to parse a Get requests arguments into a complex object. For example:
[HttpGet("some-get-request")]
public IActionResult DoSomething([FromQuery] SomeArguments someArgs)
{
}
One of the properties of the SomeArguments object is an enum.
public enum SomeEnum { EnumValue01, EnumValue02 }
public class SomeArguments
{
[FromQuery(Name = "enum_arg")]
public SomeEnum EnumArgument { get; set; }
}
And I call the endpoint with something like:
http://localhost:1234/api/some-controller/some-get-request?enum_arg=EnumValue01
And this all works great. However, I want to be able to use a different enum value in the URL than in my C# enum value. For example, I want to call using a URL such as
http://localhost:1234/api/some-controller/some-get-request?enum_arg=eval01
How can I do this?
I thought I could use the [FromQuery] attribute, like I can with properties, but that doesnt seem to be possible:
'FromQuery' is not valid on this declaration type. It is only valid on 'property, indexer, parameter'
You can use EnumMemberAttribute in conjunction with StringEnumConverter to achieve your goal. Define SomeEnum as following
[JsonConverter(typeof(StringEnumConverter))]
public enum SomeEnum
{
[EnumMember(Value = "eval01")]
EnumValue01,
[EnumMember(Value = "eval02")]
EnumValue02
}
At this point it will work as you need only when Newtonsoft json serializer is used. For example, when controller expects POST request and parameter is marked as [FromBody]. In your case it won't work yet because during binding of [FromQuery] parameter json serializer is not used. To solve this one create custom model binder
public class JsonModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
string rawData = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).FirstValue;
rawData = JsonConvert.SerializeObject(rawData); //turns value to valid json
try
{
SomeEnum result = JsonConvert.DeserializeObject<SomeEnum>(rawData); //manually deserializing value
bindingContext.Result = ModelBindingResult.Success(result);
}
catch (JsonSerializationException ex)
{
//do nothing since "failed" result is set by default
}
return Task.CompletedTask;
}
}
Update SomeEnum definition to use JsonModelBinder
[JsonConverter(typeof(StringEnumConverter))]
[ModelBinder(typeof(JsonModelBinder))]
public enum SomeEnum

JAXB -EclipseLink MOXy Object type not getting marshalled correctly

public class ResposneMessage {
private int status;
private String code = "";
private String message = "";
private Object data;
}
The "DictType" is not marshaled:
{
"code": "",
"data": "com.testapp.model.DictType#7c9a897 com.testapp.model.DictType#43581423 com.testapp.model.DictType#217adb02 com.testapp.model.DictType#6ff992bb com.testapp.model.DictType#253e12c3 com.testapp.model.DictType#2644f34b com.testapp.model.DictType#51919e4a com.testapp.model.DictType#72deb289 com.testapp.model.DictType#27231e1b com.testapp.model.DictType#26fc6f1f com.testapp.model.DictType#7b42c644 com.testapp.model.DictType#7c8f695f com.testapp.model.DictType#43637313",
"message": "",
"status": 200
}
Default is not marshaling the Object type.
An Object can not be marshalled to any meaningful JSON representation because the library responsible for marshalling the object uses reflection to do so. It seems to only look at the top-most class in the hierarchy you defined and marshalls whatever it finds in there.
Since there is nothing for Object, you just get its .toString() representation.
FWIW: The same happens when you try to marshall an interface or (abstract) superclass. You only see the properties the interface/superclass itself defines, in your marshalled output - regardless of what the descendant classes declare/define.
Take this example:
public class SuperClass {
}
public class OtherClass extends SuperClass {
public String someProperty = "test";
}
public class MarshallMe {
public SuperClass classTest = new OtherClass();
}
This would marshall to just "classTest":{} because SuperClass doesn't have any properties of its own.

Morphia Interface for List of enum does not work (unmarshalling)

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.