Jackson serialize simple one attribute ValueObject like Enum - without nesting - java

Is it possible to configure jackson to serialize simple value objects, wrapping only one attribute, to be serialized like enums?
public final class ErrorCode {
private final String value;
public ErrorCode(#JsonProperty("value") final String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
Now it is serialized as ..., "errorCode":{"value":"invalid.login"}, ...
but I would like to have, as if it were enum, ..., "errorCode":"invalid.login", ....
It is possible to but only via #JsonUnwrapped() in each surrounding class
SorroundingClass {
#JsonUnwrapped()
private ErrorCode errorCode;
...
}
I would like to configure It only in one place, best in ErrorCode itself.
Looking at flattening-nested-attributes-in-jackson it seems to me impossible, but I want to make sure.

You can achieve this by implementing custom com.fasterxml.jackson.databind.JsonSerializer serializer:
class SingleValueJsonSerializer extends JsonSerializer<Object> {
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value == null) {
gen.writeNull();
return;
}
final Field[] fields = value.getClass().getDeclaredFields();
if (fields.length != 1) {
throw new IOException("Do not use this serialiser for the class " + value.getClass().getSimpleName() + " which has" + fields.length + " fields!");
}
final Field first = fields[0];
try {
final Object fieldValue = first.get(value);
gen.writeObject(fieldValue);
} catch (IllegalAccessException e) {
throw new IOException(e);
}
}
}
You can define it in required class:
class Wrapper {
#JsonSerialize(using = SingleValueJsonSerializer.class)
private ErrorCode error;
// other fields
}
or register it globally by defining on the class level:
#JsonSerialize(using = SingleValueJsonSerializer.class)
class ErrorCode {

Related

Change the value of a parameter annotation [duplicate]

Imagine there is a class:
#Something(someProperty = "some value")
public class Foobar {
//...
}
Which is already compiled (I cannot control the source), and is part of the classpath when the jvm starts up. I would like to be able to change "some value" to something else at runtime, such that any reflection thereafter would have my new value instead of the default "some value".
Is this possible? If so, how?
Warning: Not tested on OSX - see comment from #Marcel
Tested on OSX. Works fine.
Since I also had the need to change annotation values at runtime, I revisited this question.
Here is a modified version of #assylias approach (many thanks for the inspiration).
/**
* Changes the annotation value for the given key of the given annotation to newValue and returns
* the previous value.
*/
#SuppressWarnings("unchecked")
public static Object changeAnnotationValue(Annotation annotation, String key, Object newValue){
Object handler = Proxy.getInvocationHandler(annotation);
Field f;
try {
f = handler.getClass().getDeclaredField("memberValues");
} catch (NoSuchFieldException | SecurityException e) {
throw new IllegalStateException(e);
}
f.setAccessible(true);
Map<String, Object> memberValues;
try {
memberValues = (Map<String, Object>) f.get(handler);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
Object oldValue = memberValues.get(key);
if (oldValue == null || oldValue.getClass() != newValue.getClass()) {
throw new IllegalArgumentException();
}
memberValues.put(key,newValue);
return oldValue;
}
Usage example:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface ClassAnnotation {
String value() default "";
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface FieldAnnotation {
String value() default "";
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MethodAnnotation {
String value() default "";
}
#ClassAnnotation("class test")
public static class TestClass{
#FieldAnnotation("field test")
public Object field;
#MethodAnnotation("method test")
public void method(){
}
}
public static void main(String[] args) throws Exception {
final ClassAnnotation classAnnotation = TestClass.class.getAnnotation(ClassAnnotation.class);
System.out.println("old ClassAnnotation = " + classAnnotation.value());
changeAnnotationValue(classAnnotation, "value", "another class annotation value");
System.out.println("modified ClassAnnotation = " + classAnnotation.value());
Field field = TestClass.class.getField("field");
final FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class);
System.out.println("old FieldAnnotation = " + fieldAnnotation.value());
changeAnnotationValue(fieldAnnotation, "value", "another field annotation value");
System.out.println("modified FieldAnnotation = " + fieldAnnotation.value());
Method method = TestClass.class.getMethod("method");
final MethodAnnotation methodAnnotation = method.getAnnotation(MethodAnnotation.class);
System.out.println("old MethodAnnotation = " + methodAnnotation.value());
changeAnnotationValue(methodAnnotation, "value", "another method annotation value");
System.out.println("modified MethodAnnotation = " + methodAnnotation.value());
}
The advantage of this approach is, that one does not need to create a new annotation instance. Therefore one doesn't need to know the concrete annotation class in advance. Also the side effects should be minimal since the original annotation instance stays untouched.
Tested with Java 8.
This code does more or less what you ask for - it is a simple proof of concept:
a proper implementation needs to also deal with the declaredAnnotations
if the implementation of annotations in Class.java changes, the code will break (i.e. it can break at any time in the future)
I have no idea if there are side effects...
Output:
oldAnnotation = some value
modifiedAnnotation = another value
public static void main(String[] args) throws Exception {
final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("oldAnnotation = " + oldAnnotation.someProperty());
Annotation newAnnotation = new Something() {
#Override
public String someProperty() {
return "another value";
}
#Override
public Class<? extends Annotation> annotationType() {
return oldAnnotation.annotationType();
}
};
Field field = Class.class.getDeclaredField("annotations");
field.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) field.get(Foobar.class);
annotations.put(Something.class, newAnnotation);
Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty());
}
#Something(someProperty = "some value")
public static class Foobar {
}
#Retention(RetentionPolicy.RUNTIME)
#interface Something {
String someProperty();
}
This one works on my machine with Java 8. It changes the value of ignoreUnknown in the annotation #JsonIgnoreProperties(ignoreUnknown = true) from true to false.
final List<Annotation> matchedAnnotation = Arrays.stream(SomeClass.class.getAnnotations()).filter(annotation -> annotation.annotationType().equals(JsonIgnoreProperties.class)).collect(Collectors.toList());
final Annotation modifiedAnnotation = new JsonIgnoreProperties() {
#Override public Class<? extends Annotation> annotationType() {
return matchedAnnotation.get(0).annotationType();
} #Override public String[] value() {
return new String[0];
} #Override public boolean ignoreUnknown() {
return false;
} #Override public boolean allowGetters() {
return false;
} #Override public boolean allowSetters() {
return false;
}
};
final Method method = Class.class.getDeclaredMethod("getDeclaredAnnotationMap", null);
method.setAccessible(true);
final Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) method.invoke(SomeClass.class, null);
annotations.put(JsonIgnoreProperties.class, modifiedAnnotation);
SPRING can do this job very easily , might be useful for spring developer .
follow these steps :-
First Solution :-
1)create a Bean returning a value for someProperty . Here I injected the somePropertyValue with #Value annotation from DB or property file :-
#Value("${config.somePropertyValue}")
private String somePropertyValue;
#Bean
public String somePropertyValue(){
return somePropertyValue;
}
2)After this , it is possible to inject the somePropertyValue into the #Something annotation like this :-
#Something(someProperty = "#{#somePropertyValue}")
public class Foobar {
//...
}
Second solution :-
1) create getter setter in bean :-
#Component
public class config{
#Value("${config.somePropertyValue}")
private String somePropertyValue;
public String getSomePropertyValue() {
return somePropertyValue;
}
public void setSomePropertyValue(String somePropertyValue) {
this.somePropertyValue = somePropertyValue;
}
}
2)After this , it is possible to inject the somePropertyValue into the #Something annotation like this :-
#Something(someProperty = "#{config.somePropertyValue}")
public class Foobar {
//...
}
Try this solution for Java 8
public static void main(String[] args) throws Exception {
final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("oldAnnotation = " + oldAnnotation.someProperty());
Annotation newAnnotation = new Something() {
#Override
public String someProperty() {
return "another value";
}
#Override
public Class<? extends Annotation> annotationType() {
return oldAnnotation.annotationType();
}
};
Method method = Class.class.getDeclaredMethod("annotationData", null);
method.setAccessible(true);
Object annotationData = method.invoke(getClass(), null);
Field declaredAnnotations = annotationData.getClass().getDeclaredField("declaredAnnotations");
declaredAnnotations.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) declaredAnnotations.get(annotationData);
annotations.put(Something.class, newAnnotation);
Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty());
}
#Something(someProperty = "some value")
public static class Foobar {
}
#Retention(RetentionPolicy.RUNTIME)
#interface Something {
String someProperty();
}
i am able to access and modify annotaions in this way in jdk1.8,but not sure why has no effect,
try {
Field annotationDataField = myObject.getClass().getClass().getDeclaredField("annotationData");
annotationDataField.setAccessible(true);
Field annotationsField = annotationDataField.get(myObject.getClass()).getClass().getDeclaredField("annotations");
annotationsField.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) annotationsField.get(annotationDataField.get(myObject.getClass()));
annotations.put(Something.class, newSomethingValue);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
Annotation attribute values have to be constants - so unless you want to do some serious byte code manipulation it won't be possible. Is there a cleaner way, such as creating a wrapper class with the annotation you desire?

Java serialization read enum to string

There is some legacy Java pojos which was used for binary serialization. Among the fields of one pojo, I have one enum field. Now in the new Java pojo, the enum field is replaced by a string field.
// old pojo with enum
class Test {
private DataType dataType;
private String someOtherField;
}
enum DataType {
Int,
Float,
String
}
// new pojo without enum field
class NewTest {
private String dataType;
private String someOtherField;
}
While reading(de-serializing) the old data, I have used the techniques mentioned here - https://stackoverflow.com/a/14608062/314310 to read old data into the new refactored pojo, which performs successfully for the non enum fields. But reading enum data to string field is almost seems impossible. I am getting exception as
java.lang.ClassCastException: cannot assign instance of demo.DataType to field demo.NewTest.dataType of type java.lang.String in instance of demo.NewTest
Is there anyway I can achieve this?
EDIT:
Here is the code for my custom ObjectInputStream
class MyObjectInputStream extends ObjectInputStream {
private static final Map<String, Class<?>> migrationMap = new HashMap<>();
static {
migrationMap.put("demo.Test", NewTest.class);
migrationMap.put("demo.DataType", String.class);
}
public MyObjectInputStream(InputStream stream) throws IOException {
super(stream);
}
#Override
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
ObjectStreamClass resultClassDescriptor = super.readClassDescriptor();
for (final String oldName : migrationMap.keySet()) {
if (resultClassDescriptor != null && resultClassDescriptor.getName().equals(oldName)) {
Class<?> replacement = migrationMap.get(oldName);
try {
resultClassDescriptor = ObjectStreamClass.lookup(replacement);
} catch (Exception e) {
log.error("Error while replacing class name." + e.getMessage(), e);
}
}
}
return resultClassDescriptor;
}
}
Try changing your enum into this:
enum DataType {
Int,
Float,
String;
public static DataType getFromString(String stringDataType) {
for(DataType dataType in DataType.values()) {
if (dataType.toString().equals(stringDataType)) {
return dataType;
}
}
throw new IllegalArgumentException("Invalid input");
}
}
So when you want to assign the Enum to String you call:
newTest.dataType = test.dataType.toString();
And when you want to assign the String to Enum, you call:
test.dataType = DataType.getFromString(newTest.dataType);

How deserialize based on information available in the parent class

I am using Jackson to deserialize a number of different implementations of the Product interface. These product implementations have different fields, but all have an InsuredAmount field. This InsuredAmount class has a value field and an IAType field. The IAType is a marker interface with different enums as implementations.
Now here's the problem: The enum implementations of the IAType interface correspond to a certain implementation of the Product interface. How can I make a generic implementation and tell Jackson to find the correct implementation of thee IAType? Should I use a generic parameter on the Product and the IAType interface identifying the product implementation? Should I use a Productable functional interface on the classes identifying the product implementation? How can I tell Jackson to use that implementation?
I hope the code below clarifies the problem, I chose to implement a Productable interface here, but a bettere structure to handle this problem would also be welcome.
#JsonPropertyOrder({"type", "someInfo"})
public class InsuredAmount implements Productable, Serializable {
private static final long serialVersionUID = 1L;
private IAType type;
private String someInfo;
public InsuredAmount() {
}
public InsuredAmount(IAType typeA, String someInfo) {
this.type = typeA;
this.someInfo = someInfo;
}
/* This should be on the product level, but if I can solve this problem,
the next level will just be more of the same.
*/
#JsonIgnore
#Override
public Product getProduct() {
return Product.PROD_A;
}
// Getters, setters, equals, etc. omitted.
}
--
public interface Productable {
public Product getProduct();
}
--
public enum Product {
PROD_A, PROD_B;
}
--
#JsonDeserialize(using = IATypeDeserializer.class)
public interface IAType extends Productable {
}
--
public enum IATypeA implements IAType {
FOO, BAR;
#Override
public Product getProduct() {
return Product.PROD_A;
}
}
--
public class IATypeDeserializer extends StdDeserializer<IAType> {
private static final long serialVersionUID = 1L;
public IATypeDeserializer() {
this(null);
}
public IATypeDeserializer(Class<?> vc) {
super(vc);
}
#Override
public IAType deserialize(JsonParser parser, DeserializationContext context)
throws IOException, JsonProcessingException {
JsonNode node = parser.getCodec().readTree(parser);
/* How to find out that the class calling the deserialization is InsuredAmountA, which
has getProduct() method that returns PROD_A, and matches the IATypeA that also returns
PROD_A, so I know to deserialize IATypeA, instead of other implementations of the IAType
interface?
*/
return IATypeA.valueOf(node.asText());
}
}
--
public class InsuredAmountTest {
private final ObjectMapper mapper = new ObjectMapper();
#Test
public void test01() throws IOException {
InsuredAmount iaA = new InsuredAmount(IATypeA.FOO, "test it");
String json = mapper.writeValueAsString(iaA);
assertThat(json, is("{\"type\":\"FOO\",\"someInfo\":\"test it\"}"));
InsuredAmount iaA2 = mapper.readValue(json, InsuredAmount.class);
IAType type = iaA2.getType();
assertThat(type, is(IATypeA.FOO));
assertThat(type.getProduct(), is(Product.PROD_A));
assertThat(iaA, is(iaA2));
}
#Test
public void test02() throws IOException {
InsuredAmount iaA = new InsuredAmount(IATypeA.BAR, "test it");
String json = mapper.writeValueAsString(iaA);
assertThat(json, is("{\"type\":\"BAR\",\"someInfo\":\"test it\"}"));
InsuredAmount iaA2 = mapper.readValue(json, InsuredAmount.class);
assertThat(iaA, is(iaA2));
}
}
Jackson handles the serialization of enums with minimal fuss, so all you need to do is annotate the IAType field with #JsonTypeInfo:
#JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
private IAType type;
Then a test:
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(new InsuredAmount(IATypeA.FOO, "info"));
System.out.println(json);
InsuredAmount ia = mapper.readValue(json, InsuredAmount.class);
System.out.println("Type is: " + ia.getType());
}
results in the output:
{"type":[".IATypeA","FOO"],"someInfo":"info"}
Type is: FOO
To get a more compact representation you will have to use custom serialization. Assuming that there are no overlaps in your enum namespace, you can serialize the type field as the enum name.
The deserializer will need to know which types are available for construction, either by class path discovery or, as in the following example, simply hard-coding the references:
public class IATest {
public static class IATypeSerializer extends JsonSerializer<IAType> {
#Override
public void serialize(IAType value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(((Enum) value).name());
}
}
public static class IATypeDeserializer extends JsonDeserializer<IAType> {
#Override
public IAType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String value = p.readValueAs(String.class);
try {
return IATypeA.valueOf(value);
} catch (IllegalArgumentException e) {
// fall through
}
try {
return IATypeB.valueOf(value);
} catch (IllegalArgumentException e) {
// fall through
}
throw new JsonMappingException(p, "Unknown type '" + value + "'");
}
}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
// Register a module to handle serialization of IAType implementations
SimpleModule module = new SimpleModule();
module.addSerializer(IAType.class, new IATypeSerializer());
module.addDeserializer(IAType.class, new IATypeDeserializer());
mapper.registerModule(module);
// Test
String json = mapper.writeValueAsString(new InsuredAmount(IATypeA.FOO, "info"));
System.out.println(json);
InsuredAmount ia = mapper.readValue(json, InsuredAmount.class);
System.out.println("Type is: " + ia.getType());
}
}
Which outputs:
{"type":"FOO","someInfo":"info"}
Type is: FOO
I ended up with using JsonCreator annotation on a special constructor.
#JsonCreator
public InsuredAmountA(
#JsonProperty("type") String type,
#JsonProperty("someInfo") String someInfo) throws IOException {
switch (getProduct()) {
case PROD_A:
try {
this.type = IATypeA.valueOf(type);
break;
} catch (IllegalArgumentException ex) {
// Throw IOException in the default.
}
// case PROD_B:
// this.type = (IATypeB) typeA;
// break;
default:
throw new IOException(String.format("Cannot parse value %s as type.", type));
}
this.someInfo = someInfo;
}
You may look into direction of polymorphic deserialisation:
http://wiki.fasterxml.com/JacksonPolymorphicDeserialization
unsing custom type resolver

Mapping json string to interace with anonymous class

I have an interface, which I want to use for serialize/deserialize. I want to omit some of the fields. Code below is not working so far.
#JsonAutoDetect(fieldVisibility = Visibility.NONE)
public interface MyWrapper {
//no annotation to not serialize
String getMyField();
//annotation to deserialize
#JsonProperty("my_field")
void setMyField();
}
You can either specify #JsonIgnore annotation on the method, or #JsonIgnoreProperties(value = {"myfield"}) annotation on the class.
see examples here
EDIT:
which version of Jackson are you using? becuase in the one I am using (2.5) the use of #JsonIgnore together with #JsonProperty works perfectly.
also, notice that the setter needs to receive an argument to actually be used by Jackson
interface with fixed setter:
#JsonAutoDetect(fieldVisibility = Visibility.NONE)
public interface MyWrapper {
#JsonIgnore
String getMyField();
// annotation to deserialize
#JsonProperty("my_field")
void setMyField(String f);
}
implementation (nothing exciting here)
public class Foo implements MyWrapper {
private String myField;
public Foo() {}
public Foo(String f) {
setMyField(f);
}
#Override
public String getMyField() {
return myField;
}
#Override
public void setMyField(String f) {
myField = f;
}
}
testing :
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
// serialization - ignore field
try {
MyWrapper w = new Foo("value");
String json = mapper.writeValueAsString(w);
System.out.println("serialized MyWrapper: " + json);
} catch (Exception e) {
e.printStackTrace();
}
// de-serialization - read field
String json = "{\"my_field\":\"value\"}";
try (InputStream is = new ByteArrayInputStream(json.getBytes("UTF-8"))) {
MyWrapper w = (MyWrapper)mapper.readValue(is, Foo.class);
System.out.println("deserialized MyWrapper: input: " + json + " ; w.getMyField(): " + w.getMyField());
} catch (Exception e) {
e.printStackTrace();
}
}
output:
serialized MyWrapper: {}
deserialized MyWrapper: input: {"my_field":"value"} ; w.getMyField(): value

getting the values of an Enum type in the web interface

I have implemented an enum class.This is the code:
public enum OfferType {
NO_OFFER("Pas d'offre", "N/A"),
LOCAL("Offre locale", "LOCAL"),
NATIONAL("Offre nationale", "NATIONAL"),
DEFAULT("DEFAULT", "DEFAULT");
// private static Logger logger = LoggerFactory.getLogger(OfferType.class);
public final String frontLabel;
public final String daoField;
OfferType(String frontLabel, String daoField) {
this.frontLabel = frontLabel;
this.daoField = daoField;
}
public static OfferType getEnum(String daoField) {
if (NO_OFFER.daoField.equals(daoField)){
return NO_OFFER;
}
if (LOCAL.daoField.equals(daoField)){
return LOCAL;
}
if (NATIONAL.daoField.equals(daoField)){
return NATIONAL;
}
//logger.error("Unknown enum value: " + daoField);
throw new IllegalArgumentException("No Enum specified for this string:"+daoField);
}
}
I have a class Order defined with an attribute with the type "OfferType".
I should instantiate an object "order" and set this property to the object, like the following:
order.setOfferType(OfferType.getEnum((rs.getString("offerType"))));
The problem is that with this setting I will have the value with uppercase of the enum.I want to retrieve the first value of the enum instead.How can I do it ?
Thanks in advance
Spring uses default jackson serializer. Thus in order to have custom serialization you should annotate your field:
#JsonSerialize(using = OfferTypeSerializer.class)
private OfferType offerType;
and create OfferTypeSerializer.class:
public class OfferTypeSerializer extends JsonSerializer<OfferType> {
#Override
public void serialize(OrderType value, JsonGenerator jgen,
SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeString(value.frontLabel);
}
}
Another way is to get value calling offerType.frontLabel and setting it as String or implementing toString() method which is not the best solution:
#Override
public String toString() {
return this.frontLabel;
}
If you're calling getEnum(String daoField) to get data you should write it in the next form:
public static String getEnumFrontLabel(String daoField) {
if (NO_OFFER.daoField.equals(daoField)){
return NO_OFFER.frontLabel;
}
if (LOCAL.daoField.equals(daoField)){
return LOCAL.frontLabel;
}
if (NATIONAL.daoField.equals(daoField)){
return NATIONAL.frontLabel;
}
And actually better to use next form of returning enum:
public static String getEnumFrontLabel(String daoField) {
OfferType t = getType(field);
if (t != null) {
return t.frontLabel;
}
return null;
}
public static OfferType getType(String field) {
for (OfferType type : values()) {
if (type.equals(field)) {
return type;
}
}
return null;
}

Categories

Resources