Deserialising an Enum with a static factory method using Jackson - enums

How can I deserialize an Enum using a static factory method if I can't change the Enum's source code?
For example, how can Jackson be configured to deserialize using MyEnum.fromKey()?
public enum MyEnum {
FOO("key1"),
BAR("key2");
private final String key;
MyEnum(String key) {
this.key = key;
}
public static final MyEnum fromKey(String key) {
// lookup by MyEnum.key
}

Use a Jackson mixin to provide a #JsonCreator that delegates to MyEnum.fromKey():
public enum MyEnumMixIn {
/* no values */;
#JsonCreator
public static MyEnum fromKey(String key) {
return MyEnum.fromKey(key);
}
}
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(MyEnum.class, MyEnumMixIn.class);

Related

How to properly implement a Spring Converter?

I have a Money class with factory methods for numeric and String values. I would like to use it as a property of my input Pojos.
I created some Converters for it, this is the String one:
#Component
public class StringMoneyConverter implements Converter<String, Money> {
#Override
public Money convert(String source) {
return Money.from(source);
}
}
My testing Pojo is very simple:
public class MoneyTestPojo {
private Money value;
//getter and setter ommited
}
I have an endpoint which expects a Pojo:
#PostMapping("/pojo")
public String savePojo(#RequestBody MoneyTestPojo pojo) {
//...
}
Finally, this is the request body:
{
value: "100"
}
I have the following error when I try this request:
JSON parse error: Cannot construct instance of
br.marcellorvalle.Money (although at least one Creator
exists): no String-argument constructor/factory method to deserialize
from String value ('100'); nested exception is
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
construct instance of br.marcellorvalle.Money (although at
least one Creator exists): no String-argument constructor/factory
method to deserialize from String value ('100')\n at [Source:
(PushbackInputStream); line: 8, column: 19] (through reference chain:
br.marcellorvalle.MoneytestPojo[\"value\"])",
If I change Money and add a constructor which receives a String this request works but I really need a factory method as I have to deliver special instances of Money on specific cases (zeros, nulls and empty strings).
Am I missing something?
Edit: As asked, here goes the Money class:
public class Money {
public static final Money ZERO = new Money(BigDecimal.ZERO);
private static final int PRECISION = 2;
private static final int EXTENDED_PRECISION = 16;
private static final RoundingMode ROUNDING = RoundingMode.HALF_EVEN;
private final BigDecimal amount;
private Money(BigDecimal amount) {
this.amount = amount;
}
public static Money from(float value) {
return Money.from(BigDecimal.valueOf(value));
}
public static Money from(double value) {
return Money.from(BigDecimal.valueOf(value));
}
public static Money from(String value) {
if (Objects.isNull(value) || "".equals(value)) {
return null;
}
return Money.from(new BigDecimal(value));
}
public static Money from(BigDecimal value) {
if (Objects.requireNonNull(value).equals(BigDecimal.ZERO)) {
return Money.ZERO;
}
return new Money(value);
}
//(...)
}
Annotating your factory method with #JsonCreator (from the com.fasterxml.jackson.annotation package) will resolve the issue:
#JsonCreator
public static Money from(String value) {
if (Objects.isNull(value) || "".equals(value)) {
return null;
}
return Money.from(new BigDecimal(value));
}
I just tested it, and it worked for me. Rest of your code looks fine except for the sample request (value should be in quotes), but I guess that's just a typo.
Update 1:
If you're unable to make changes to the Money class, I can think of another option - a custom Jackson deserializer:
public class MoneyDeserializer extends StdDeserializer<Money> {
private static final long serialVersionUID = 0L;
public MoneyDeserializer() {
this(null);
}
public MoneyDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Money deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
String value = node.textValue();
return Money.from(value);
}
}
Just register it with your ObjectMapper.
It seems that using the org.springframework.core.convert.converter.Converter only works if the Money class is a "#PathVariable" in the controller.
I finally solved it using the com.fasterxml.jackson.databind.util.StdConverter class:
I created the following Converter classes:
public class MoneyJsonConverters {
public static class FromString extends StdConverter<String, Money> {
#Override
public Money convert(String value) {
return Money.from(value);
}
}
public static class ToString extends StdConverter<Money, String> {
#Override
public String convert(Money value) {
return value.toString();
}
}
}
Then I annotated the Pojo with #JsonDeserialize #JsonSerialize accordingly:
public class MoneyTestPojo {
#JsonSerialize(converter = MoneyJsonConverters.ToString.class)
#JsonDeserialize(converter = MoneyJsonConverters.FromString.class)
private Money value;
//getter and setter ommited
}

How to use Java 8 Optional with Moxy and Jersey

Is it possible to use Jersey with Moxy to/from Json and Java 8 Optionals?
How to configure it?
You can declare following class:
public class OptionalAdapter<T> extends XmlAdapter<T, Optional<T>> {
#Override
public Optional<T> unmarshal(T value) throws Exception {
return Optional.ofNullable(value);
}
#Override
public T marshal(Optional<T> value) throws Exception {
return value.orElse(null);
}
}
And use like this:
#XmlRootElement
public class SampleRequest {
#XmlElement(type = Integer.class)
#XmlJavaTypeAdapter(value = OptionalAdapter.class)
private Optional<Integer> id;
#XmlElement(type = String.class)
#XmlJavaTypeAdapter(value = OptionalAdapter.class)
private Optional<String> text;
/* ... */
}
Or declare in package-info.java and remove #XmlJavaTypeAdapter from POJOs:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(type = Optional.class, value = OptionalAdapter.class)
})
But here are some drawbacks:
Adapter above can only work with simple types like Integer, String, etc. that can be parsed by MOXY by default.
You have to specify #XmlElement(type = Integer.class) explicitly to tell the parser type are working with, otherwise null values would be passed to adapter's unmarshal method.
You miss the opportunity of using adapters for custom types, e.g. custom adapter for java.util.Date class based on some date format string. To overcome this you'll need to create adapter something like class OptionalDateAdapter<String> extends XmlAdapter<String, Optional<Date>>.
Also using Optional on field is not recommended, see this discussion for details.
Taking into account all the above, I would suggest just using Optional as return type for your POJOs:
#XmlRootElement
public class SampleRequest {
#XmlElement
private Integer id;
public Optional<Integer> getId() {
return Optional.ofNullable(id);
}
public void setId(Integer id) {
this.id = id;
}
}

Spring #Requestbody not mapping to inner class

I am doing Spring Rest Api project with Spring 4.x
This Works:
Controller.java
#PostMapping("newTransaction")
TransactionRequestModel insertNewTransaction(#RequestBody TransactionRequestModel model){
//do something
}
TransactionRequestModel.java
public class TransactionRequestModel {
private int id;
private List<KeyValue> keyValueList;
public TransactionRequestModel(){}
//default constructor
//getter-setter
}
KeyValue.java
public class KeyValue {
String key;
String value;
//default constructor
//setter-getter
}
Request Body Json
{
"id": 1
"keyValueList": [
{
"key": "dummy",
"value": "dummy"
}
]
}
Spring message converter using jackson is working fine.
This Won't:
When i change TransactionRequestModel.java to following (and delete KeyValue.java)
public class TransactionRequestModel {
public class KeyValue {
String key;
String value;
//default constructor
//setter-getter
}
private int id;
private List<KeyValue> keyValueList;
public TransactionRequestModel(){}
//default constructor
//getter-setter
}
means, making KeyValue an inner class, got following error.
org.springframework.http.converter.HttpMessageNotReadableException:
Could not read document: No suitable constructor found for type
[simple type, class
com.example.model.TransactionRequestModel$KeyValue]: can not
instantiate from JSON object (missing default constructor or creator,
or perhaps need to add/enable type information?)
Why?
All the related post in SO mentions the first scenario. I would like to know why this wont work. Please help.
You have to make your inner class static.
public class TransactionRequestModel {
public static class KeyValue {
String key;
String value;
//default constructor
//setter-getter
}
private int id;
private List<KeyValue> keyValueList;
public TransactionRequestModel(){}
//default constructor
//getter-setter
}

How to set different type for variable in POJO than expected while deserializing json using gson in android(see example)

Bear with my English. I have a simple json,
{
"Hint2": "L"
}
this is the POJO that works.
public class Hints {
#SerializedName("Hint2")
#Expose
private String Hint2;
public void setHint1(Object Hint2) {
this.Hint2 = (Hint2);
}
}
i want to change it to
public class Hints {
#SerializedName("Hint2")
#Expose
public final ObservableField<String> Hint2 = new ObservableField<>();
public void setHint2(String Hint2) {
this.Hint2.set(Hint2);
}
}
both class has same setter method, same #SerializedName annotation tag. only type of Hint2 object is changed. but the latter one throws exception shown below
Caused by: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at..
so i believe deserialization depends on what kind of variable "Hint2" is.
Is there a way to make it work with ObservableField rather than using String?
The reason i'm trying this is android binding library, which supports binding objects directly to xml files. and the ObservableField automatically updates UI when corresponding value in POJO is changed.
Update:
gson design document has this
Using fields vs getters to indicate Json elements
Some Json libraries use the getters of a type to deduce the Json elements. We chose to use all fields (up the inheritance hierarchy) that are not transient, static, or synthetic. We did this because not all classes are written with suitably named getters. Moreover, getXXX or isXXX might be semantic rather than indicating properties.
However, there are good arguments to support properties as well. We intend to enhance Gson in a latter version to support properties as an alternate mapping for indicating Json fields. For now, Gson is fields-based.
so this indicates that Gson is fields-based. this pretty much answers my question but still waiting if anyone has someway around this.
I came across the same requirements, and resolved it finally, here are the steps:
create the class GsonUtils:
public class GsonUtils {
// code following
//...
}
following code are in this class
write a customized serializer & deserializer:
private static class ObservableFieldSerializerDeserializer implements JsonSerializer>, JsonDeserializer> {
#Override
public JsonElement serialize(ObservableField src, Type typeOfSrc, JsonSerializationContext context) {
return context.serialize(src.get());
}
#Override
public ObservableField deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
final Type type = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];
return new ObservableField((T) GsonUtils.getGson().fromJson(json, type));
}
}
you need to register ObservableField types to Gson:
private static GsonBuilder createGsonBuilder() {
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(new TypeToken&ltObservableField&ltString&gt&gt(){}.getType(), new ObservableFieldSerializerDeserializer&ltString&gt());
...// register more types which are wrapped by ObservableFields
return gsonBuilder;
}
create the Gson which is used by the deserializer
private static final Gson sGson = createGson();
private static Gson createGson() {
return createGsonBuilder().create();
}
// this is used by the deserializer
public static Gson getGson() {
return sGson;
}
that's all, hope it helps
I just ran into what I think is the same issue, and here is a JUnit4 test showing how I solved it with Jackson for a POJO, but of course String would work as well.
public class ObservableDeserializationTest {
private static class ObservableDeserializer extends JsonDeserializer<ObservableField> implements ContextualDeserializer {
private Class<?> mTargetClass;
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
mTargetClass = property.getType().containedType(0).getRawClass();
return this;
}
#Override
public ObservableField deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ObservableField result = new ObservableField();
result.set(p.readValueAs(mTargetClass));
return result;
}
}
private static class SomePojo {
public String id;
public String name;
}
private static class ObservableTestClass {
#JsonDeserialize(using = ObservableDeserializer.class)
public ObservableField<SomePojo> testObj = new ObservableField<>();
}
#Test
public void DeserializingAnObservableObjectShouldSetValueCorrectly() {
ObservableTestClass tc = null;
try {
tc = new ObjectMapper().readValue("{\"testObj\":{\"name\":\"TestName\",\"id\":\"TestId\"}}", ObservableTestClass.class);
} catch (IOException e) {
e.printStackTrace();
}
Assert.assertEquals("TestName", tc.testObj.get().name);
Assert.assertEquals("TestId", tc.testObj.get().id);
}
}
The key is the ContextualDeserializer interface that allows extracting the contained class type. Jackson provides several options for registering a custom deserializer, so this is but one way of doing it. Also, it would probably be a good idea to override getNullValue as well in the deserializer if you would use this for real.

Spring -Mongodb storing/retrieving enums as int not string

My enums are stored as int in mongodb (from C# app). Now in Java, when I try to retrieve them, it throws an exception (it seems enum can be converted from string value only). Is there any way I can do it?
Also when I save some collections into mongodb (from Java), it converts enum values to string (not their value/cardinal). Is there any override available?
This can be achieved by writing mongodb-converter on class level but I don't want to write mondodb-converter for each class as these enums are in many different classes.
So do we have something on the field level?
After a long digging in the spring-mongodb converter code,
Ok i finished and now it's working :) here it is (if there is simpler solution i will be happy see as well, this is what i've done ) :
first define :
public interface IntEnumConvertable {
public int getValue();
}
and a simple enum that implements it :
public enum tester implements IntEnumConvertable{
vali(0),secondvali(1),thirdvali(5);
private final int val;
private tester(int num)
{
val = num;
}
public int getValue(){
return val;
}
}
Ok, now you will now need 2 converters , one is simple ,
the other is more complex. the simple one (this simple baby is also handling the simple convert and returns a string when cast is not possible, that is great if you want to have enum stored as strings and for enum that are numbers to be stored as integers) :
public class IntegerEnumConverters {
#WritingConverter
public static class EnumToIntegerConverter implements Converter<Enum<?>, Object> {
#Override
public Object convert(Enum<?> source) {
if(source instanceof IntEnumConvertable)
{
return ((IntEnumConvertable)(source)).getValue();
}
else
{
return source.name();
}
}
}
}
the more complex one , is actually a converter factory :
public class IntegerToEnumConverterFactory implements ConverterFactory<Integer, Enum> {
#Override
public <T extends Enum> Converter<Integer, T> getConverter(Class<T> targetType) {
Class<?> enumType = targetType;
while (enumType != null && !enumType.isEnum()) {
enumType = enumType.getSuperclass();
}
if (enumType == null) {
throw new IllegalArgumentException(
"The target type " + targetType.getName() + " does not refer to an enum");
}
return new IntegerToEnum(enumType);
}
#ReadingConverter
public static class IntegerToEnum<T extends Enum> implements Converter<Integer, Enum> {
private final Class<T> enumType;
public IntegerToEnum(Class<T> enumType) {
this.enumType = enumType;
}
#Override
public Enum convert(Integer source) {
for(T t : enumType.getEnumConstants()) {
if(t instanceof IntEnumConvertable)
{
if(((IntEnumConvertable)t).getValue() == source.intValue()) {
return t;
}
}
}
return null;
}
}
}
and now for the hack part , i personnaly didnt find any "programmitacly" way to register a converter factory within a mongoConverter , so i digged in the code and with a little casting , here it is (put this 2 babies functions in your #Configuration class)
#Bean
public CustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<Converter<?, ?>>();
converters.add(new IntegerEnumConverters.EnumToIntegerConverter());
// this is a dummy registration , actually it's a work-around because
// spring-mongodb doesnt has the option to reg converter factory.
// so we reg the converter that our factory uses.
converters.add(new IntegerToEnumConverterFactory.IntegerToEnum(null));
return new CustomConversions(converters);
}
#Bean
public MappingMongoConverter mappingMongoConverter() throws Exception {
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.setApplicationContext(appContext);
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory());
MappingMongoConverter mongoConverter = new MappingMongoConverter(dbRefResolver, mappingContext);
mongoConverter.setCustomConversions(customConversions());
ConversionService convService = mongoConverter.getConversionService();
((GenericConversionService)convService).addConverterFactory(new IntegerToEnumConverterFactory());
mongoConverter.afterPropertiesSet();
return mongoConverter;
}
You will need to implement your custom converters and register it with spring.
http://static.springsource.org/spring-data/data-mongo/docs/current/reference/html/#mongo.custom-converters
Isn't it easier to use plain constants rather than an enum...
int SOMETHING = 33;
int OTHER_THING = 55;
or
public class Role {
public static final Stirng ROLE_USER = "ROLE_USER",
ROLE_LOOSER = "ROLE_LOOSER";
}
String yourRole = Role.ROLE_LOOSER

Resources