I have a requirement to strip off all the special characters and control characters from the fields of type String in any of the Objects. Deserializer was registered but never executes during the runtime for Strings.
I tried adding the same as an annotation #JsonDeserialize(using = StringProcessorComponent.class), but the same issue.
It works for any other type like Date/Long. Please let me know if I am missing any.
Here is my Deserializer.
#JsonComponent
public class StringProcessorComponent extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonToken currentToken = p.getCurrentToken();
if (currentToken.equals(JsonToken.VALUE_STRING)) {
String text = MyStringProcessor.clean(p.getValueAsString());
return text;
}
return null;
}
}
To override default deserialisers you could use SimpleModule. Also, when you want to extend default implementation if possible you can extend default deserialisers. In your case you can extend com.fasterxml.jackson.databind.deser.std.StringDeserializer class. See below example:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
import java.util.StringJoiner;
public class JsonApp {
public static void main(String[] args) throws Exception {
SimpleModule stringModule = new SimpleModule("String Module");
stringModule.addDeserializer(String.class, new CustomStringDeserializer());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(stringModule);
String json = "{\"firstName\":\" Tom \",\"lastName\":\" Long \"}";
CustomStringPojo customStringPojo = mapper.readValue(json, CustomStringPojo.class);
System.out.println(customStringPojo);
}
}
class CustomStringDeserializer extends StringDeserializer {
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String text = super.deserialize(p, ctxt);
//clean up value
return text.trim();
}
}
class CustomStringPojo {
private String firstName;
private String lastName;
// getters, setters, toString
}
Above code prints:
CustomStringPojo{firstName='Tom', lastName='Long'}
I need to write a method that takes some object, some field name fieldName that exists in the given object's class, and some field value value. The value is the JSON-serialized form of the field. That method shall take the value and deserialize it accordingly, something like this:
static void setField(Object obj, String fieldName, String value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName)
Object valObj = objectMapper.readValue(value, field.getType());
field.set(obj, valObj);
}
(I actually only need to retrieve the deserialized value, and not set it again, but this makes it a better example.)
This works, as long as jackson's default deserialization is sufficient. Now let's assume I have a class with a custom (de)serializer:
class SomeDTO {
String foo;
#JsonSerialize(using = CustomInstantSerializer.class)
#JsonDeserialize(using = CustomInstantDeserializer.class)
Instant bar;
}
One possible solution would be to manually check for JsonDeserialize annotations. However, I really do not want to try to replicate whatever policies Jackson follows to decide what serializer to use, as that seems brittle (for example globally registered serializers).
Is there a good way to deserialize the value using the field's deserialization configuration defined in the DTO class? Maybe deserializing the value into the field's type while passing the field's annotations along to Jackson, so they get honored?
I managed to get a hold of an AnnotatedMember instance, which holds all the required information (JSON-annotations and reflective field- or setter/getter-access), but couldn't figure out how I would use it to deserialize a standalone value due to lack of documentation:
final JavaType dtoType = objectMapper.getTypeFactory().constructType(SomeDTO.class);
final BeanDescription description = objectMapper.getDeserializationConfig().introspect(dtoType);
for (BeanPropertyDefinition propDef: beanDescription.findProperties()) {
final AnnotatedMember mutator = propertyDefinition.getNonConstructorMutator();
// now what? Also: How do I filter for the correct property?
}
One possibility would be to serialize the object, replace the given field, and then deserialize it again. This can be easily done when serializing from/to JsonNode instead of JSON-String, like this:
static Object setField(Object obj, String fieldName, String value) throws Exception {
// note: produces a new object instead of modifying the existing one
JsonNode node = objectMapper.valueToTree(obj);
((ObjectNode) node).put(fieldName, value);
return objectMapper.readValue(node.traverse(), obj.getClass());
}
However, serializing and deserializing a whole object just to deserialize a single field seems like a lot of overhead, and might be brittle because other aspects of the DTO class affect the deserialization process of the single field
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
import java.util.Map;
public final class Jackson {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
public static void main(String[] args) throws IOException {
Dto source = makeDto("Master", 31337);
Dto dst = makeDto("Slave", 0xDEADBEEF);
//1. read value of field "fieldName" from json source
//2. clones destination object, sets up field "fieldName" and returns it
//3. in case of no field either on "src" or "dst" - throws an exception
Object result = restoreValue(dst, "details", OBJECT_MAPPER.writeValueAsString(source));
System.out.println(result);
}
private static Object restoreValue(Object targetObject, String fieldName, String sourceObjectAsJson) throws IOException {
String targetObjectAsJson = OBJECT_MAPPER.writeValueAsString(targetObject);
Map sourceAsMap = OBJECT_MAPPER.readValue(sourceObjectAsJson, Map.class);
Map targetAsMap = OBJECT_MAPPER.readValue(targetObjectAsJson, Map.class);
targetAsMap.put(fieldName, sourceAsMap.get(fieldName));
String updatedTargetAsJson = OBJECT_MAPPER.writeValueAsString(targetAsMap);
return OBJECT_MAPPER.readValue(updatedTargetAsJson, targetObject.getClass());
}
private static Dto makeDto(String name, int magic) {
Dto dto = new Dto();
dto.setName(name);
CustomDetails details = new CustomDetails();
details.setMagic(magic);
dto.setDetails(details);
return dto;
}
private static final class Dto {
private String name;
#JsonSerialize(using = CustomDetails.CustomDetailsSerializer.class)
#JsonDeserialize(using = CustomDetails.CustomDetailsDeserializer.class)
private CustomDetails details;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public CustomDetails getDetails() {
return details;
}
public void setDetails(CustomDetails details) {
this.details = details;
}
#Override
public String toString() {
return "Dto{" +
"name='" + name + '\'' +
", details=" + details +
'}';
}
}
private static final class CustomDetails {
private int magic;
public int getMagic() {
return magic;
}
public void setMagic(int magic) {
this.magic = magic;
}
#Override
public String toString() {
return "CustomDetails{" +
"magic=" + magic +
'}';
}
public static final class CustomDetailsSerializer extends StdSerializer<CustomDetails> {
public CustomDetailsSerializer() {
this(null);
}
public CustomDetailsSerializer(Class<CustomDetails> t) {
super(t);
}
#Override
public void serialize(CustomDetails details, JsonGenerator jg, SerializerProvider serializerProvider) throws IOException {
jg.writeStartObject();
jg.writeNumberField("_custom_property_magic", details.magic);
jg.writeEndObject();
}
}
private static final class CustomDetailsDeserializer extends StdDeserializer<CustomDetails> {
public CustomDetailsDeserializer() {
this(null);
}
public CustomDetailsDeserializer(Class<CustomDetails> t) {
super(t);
}
#Override
public CustomDetails deserialize(JsonParser jp, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
int magic = (Integer) node.get("_custom_property_magic").numberValue();
CustomDetails
customDetails = new CustomDetails();
customDetails.setMagic(magic);
return customDetails;
}
}
}
}
so the output is:
Dto{name='Slave', details=CustomDetails{magic=31337}}
Let's say my POJO is limited to the following:
public class TestPojo {
StringProperty myField;
public TestPojo(){}
public TestPojo(String myField) {
this.myField = new SimpleStringProperty(myField);
}
public String getMyField() {
return this.myField.get();
}
public StringProperty myFieldProperty() {
return this.myField;
}
public void setMyField(String myField) {
this.myField.set(myField);
}
}
I want to deserialize with Jackson. Jackson doesn't seem to like StringProperty, because as a normal String it works as expected. So I write a custom deserializer...
public class StringPropertyDeserializer extends JsonDeserializer<StringProperty> {
public StringPropertyDeserializer() {}
#Override
public StringProperty deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException, JsonProcessingException {
String value = jsonParser.readValueAs(String.class);
return new SimpleStringProperty(value);
}
}
And I add this annotation to my StringProperty field...
#JsonDeserialize(using = StringPropertyDeserializer.class)
I get the following error:
com.fasterxml.jackson.databind.JsonMappingException: Class com.test.example.TestPojo$StringPropertyDeserializer has no default (no arg) constructor
I added a default constructor into the deserializer (as you can see above) and I get the same error regardless of it being there. Any solutions? This seems like it should be simple.
Edit here is the actual code where I'm making the call if that's helpful...
#GET("/api/test")
Call<List<TestPojo>> testPojoCall();
And it's configured in Gradle like so:
compile group: 'com.squareup.retrofit2', name: 'converter-jackson', version: '2.1.0'
That is everything related to this code.
Your POJO implementation isn't correct: the no-arg constructor doesn't initialize the StringProperty. Consequently, if you use the no-arg constructor, then call setMyField(...) you'd get a null pointer exception. I'm guessing that trying to work around that was what led you to try creating the custom deserializer.
I'm not entirely sure why the custom deserializer is giving the error it's giving (maybe it doesn't like the fact that it's an inner class: that's just a guess).
The following works fine for me without any custom deserialization:
TestPojo.java:
package jxtest;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class TestPojo {
private final StringProperty myField1 = new SimpleStringProperty();
private final StringProperty myField2 = new SimpleStringProperty();
private int myField3 ;
public final StringProperty myField1Property() {
return this.myField1;
}
public final String getMyField1() {
return this.myField1Property().get();
}
public final void setMyField1(final String myField1) {
this.myField1Property().set(myField1);
}
public final StringProperty myField2Property() {
return this.myField2;
}
public final String getMyField2() {
return this.myField2Property().get();
}
public final void setMyField2(final String myField2) {
this.myField2Property().set(myField2);
}
public int getMyField3() {
return myField3;
}
public void setMyField3(int myField3) {
this.myField3 = myField3;
}
}
Test.java:
package jxtest;
import java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Test {
public static void main(String[] args) throws IOException {
TestPojo item = new TestPojo();
item.setMyField1("Test1");
item.setMyField2("Test2");
item.setMyField3(42);
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(item);
System.out.println(json);
TestPojo readBackIn = mapper.readValue(json, TestPojo.class);
System.out.println(readBackIn.getMyField1());
System.out.println(readBackIn.getMyField2());
System.out.println(readBackIn.getMyField3());
}
}
Output:
{"myField1":"Test1","myField2":"Test2","myField3":42}
Test1
Test2
42
Using jackson-databind 2.8.7.
I'm using JAVA 1.6 and Jackson 1.9.9 I've got an enum
public enum Event {
FORGOT_PASSWORD("forgot password");
private final String value;
private Event(final String description) {
this.value = description;
}
#JsonValue
final String value() {
return this.value;
}
}
I've added a #JsonValue, this seems to do the job it serializes the object into:
{"event":"forgot password"}
but when I try to deserialize I get a
Caused by: org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.globalrelay.gas.appsjson.authportal.Event from String value 'forgot password': value not one of declared Enum instance names
What am I missing here?
The serializer / deserializer solution pointed out by #xbakesx is an excellent one if you wish to completely decouple your enum class from its JSON representation.
Alternatively, if you prefer a self-contained solution, an implementation based on #JsonCreator and #JsonValue annotations would be more convenient.
So leveraging on the example by #Stanley the following is a complete self-contained solution (Java 6, Jackson 1.9):
public enum DeviceScheduleFormat {
Weekday,
EvenOdd,
Interval;
private static Map<String, DeviceScheduleFormat> namesMap = new HashMap<String, DeviceScheduleFormat>(3);
static {
namesMap.put("weekday", Weekday);
namesMap.put("even-odd", EvenOdd);
namesMap.put("interval", Interval);
}
#JsonCreator
public static DeviceScheduleFormat forValue(String value) {
return namesMap.get(StringUtils.lowerCase(value));
}
#JsonValue
public String toValue() {
for (Entry<String, DeviceScheduleFormat> entry : namesMap.entrySet()) {
if (entry.getValue() == this)
return entry.getKey();
}
return null; // or fail
}
}
Note that as of this commit in June 2015 (Jackson 2.6.2 and above) you can now simply write:
public enum Event {
#JsonProperty("forgot password")
FORGOT_PASSWORD;
}
The behavior is documented here: https://fasterxml.github.io/jackson-annotations/javadoc/2.11/com/fasterxml/jackson/annotation/JsonProperty.html
Starting with Jackson 2.6 this annotation may also be used to change serialization of Enum like so:
public enum MyEnum {
#JsonProperty("theFirstValue") THE_FIRST_VALUE,
#JsonProperty("another_value") ANOTHER_VALUE;
}
as an alternative to using JsonValue annotation.
You should create a static factory method which takes single argument and annotate it with #JsonCreator (available since Jackson 1.2)
#JsonCreator
public static Event forValue(String value) { ... }
Read more about JsonCreator annotation here.
Actual Answer:
The default deserializer for enums uses .name() to deserialize, so it's not using the #JsonValue. So as #OldCurmudgeon pointed out, you'd need to pass in {"event": "FORGOT_PASSWORD"} to match the .name() value.
An other option (assuming you want the write and read json values to be the same)...
More Info:
There is (yet) another way to manage the serialization and deserialization process with Jackson. You can specify these annotations to use your own custom serializer and deserializer:
#JsonSerialize(using = MySerializer.class)
#JsonDeserialize(using = MyDeserializer.class)
public final class MyClass {
...
}
Then you have to write MySerializer and MyDeserializer which look like this:
MySerializer
public final class MySerializer extends JsonSerializer<MyClass>
{
#Override
public void serialize(final MyClass yourClassHere, final JsonGenerator gen, final SerializerProvider serializer) throws IOException, JsonProcessingException
{
// here you'd write data to the stream with gen.write...() methods
}
}
MyDeserializer
public final class MyDeserializer extends org.codehaus.jackson.map.JsonDeserializer<MyClass>
{
#Override
public MyClass deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException
{
// then you'd do something like parser.getInt() or whatever to pull data off the parser
return null;
}
}
Last little bit, particularly for doing this to an enum JsonEnum that serializes with the method getYourValue(), your serializer and deserializer might look like this:
public void serialize(final JsonEnum enumValue, final JsonGenerator gen, final SerializerProvider serializer) throws IOException, JsonProcessingException
{
gen.writeString(enumValue.getYourValue());
}
public JsonEnum deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException
{
final String jsonValue = parser.getText();
for (final JsonEnum enumValue : JsonEnum.values())
{
if (enumValue.getYourValue().equals(jsonValue))
{
return enumValue;
}
}
return null;
}
I've found a very nice and concise solution, especially useful when you cannot modify enum classes as it was in my case. Then you should provide a custom ObjectMapper with a certain feature enabled. Those features are available since Jackson 1.6. So you only need to write toString() method in your enum.
public class CustomObjectMapper extends ObjectMapper {
#PostConstruct
public void customConfiguration() {
// Uses Enum.toString() for serialization of an Enum
this.enable(WRITE_ENUMS_USING_TO_STRING);
// Uses Enum.toString() for deserialization of an Enum
this.enable(READ_ENUMS_USING_TO_STRING);
}
}
There are more enum-related features available, see here:
https://github.com/FasterXML/jackson-databind/wiki/Serialization-Features
https://github.com/FasterXML/jackson-databind/wiki/Deserialization-Features
Try this.
public enum Event {
FORGOT_PASSWORD("forgot password");
private final String value;
private Event(final String description) {
this.value = description;
}
private Event() {
this.value = this.name();
}
#JsonValue
final String value() {
return this.value;
}
}
I like the accepted answer. However, I would improve it a little (considering that there is now Java higher than version 6 available).
Example:
public enum Operation {
EQUAL("eq"),
NOT_EQUAL("ne"),
LESS_THAN("lt"),
GREATER_THAN("gt");
private final String value;
Operation(String value) {
this.value = value;
}
#JsonValue
public String getValue() {
return value;
}
#JsonCreator
public static Operation forValue(String value) {
return Arrays.stream(Operation.values())
.filter(op -> op.getValue().equals(value))
.findFirst()
.orElseThrow(); // depending on requirements: can be .orElse(null);
}
}
You can customize the deserialization for any attribute.
Declare your deserialize class using the annotationJsonDeserialize (import com.fasterxml.jackson.databind.annotation.JsonDeserialize) for the attribute that will be processed. If this is an Enum:
#JsonDeserialize(using = MyEnumDeserialize.class)
private MyEnum myEnum;
This way your class will be used to deserialize the attribute. This is a full example:
public class MyEnumDeserialize extends JsonDeserializer<MyEnum> {
#Override
public MyEnum deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
MyEnum type = null;
try{
if(node.get("attr") != null){
type = MyEnum.get(Long.parseLong(node.get("attr").asText()));
if (type != null) {
return type;
}
}
}catch(Exception e){
type = null;
}
return type;
}
}
Here is another example that uses string values instead of a map.
public enum Operator {
EQUAL(new String[]{"=","==","==="}),
NOT_EQUAL(new String[]{"!=","<>"}),
LESS_THAN(new String[]{"<"}),
LESS_THAN_EQUAL(new String[]{"<="}),
GREATER_THAN(new String[]{">"}),
GREATER_THAN_EQUAL(new String[]{">="}),
EXISTS(new String[]{"not null", "exists"}),
NOT_EXISTS(new String[]{"is null", "not exists"}),
MATCH(new String[]{"match"});
private String[] value;
Operator(String[] value) {
this.value = value;
}
#JsonValue
public String toStringOperator(){
return value[0];
}
#JsonCreator
public static Operator fromStringOperator(String stringOperator) {
if(stringOperator != null) {
for(Operator operator : Operator.values()) {
for(String operatorString : operator.value) {
if (stringOperator.equalsIgnoreCase(operatorString)) {
return operator;
}
}
}
}
return null;
}
}
There are various approaches that you can take to accomplish deserialization of a JSON object to an enum. My favorite style is to make an inner class:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import static com.fasterxml.jackson.annotation.JsonFormat.Shape.OBJECT;
#JsonFormat(shape = OBJECT)
public enum FinancialAccountSubAccountType {
MAIN("Main"),
MAIN_DISCOUNT("Main Discount");
private final static Map<String, FinancialAccountSubAccountType> ENUM_NAME_MAP;
static {
ENUM_NAME_MAP = Arrays.stream(FinancialAccountSubAccountType.values())
.collect(Collectors.toMap(
Enum::name,
Function.identity()));
}
private final String displayName;
FinancialAccountSubAccountType(String displayName) {
this.displayName = displayName;
}
#JsonCreator
public static FinancialAccountSubAccountType fromJson(Request request) {
return ENUM_NAME_MAP.get(request.getCode());
}
#JsonProperty("name")
public String getDisplayName() {
return displayName;
}
private static class Request {
#NotEmpty(message = "Financial account sub-account type code is required")
private final String code;
private final String displayName;
#JsonCreator
private Request(#JsonProperty("code") String code,
#JsonProperty("name") String displayName) {
this.code = code;
this.displayName = displayName;
}
public String getCode() {
return code;
}
#JsonProperty("name")
public String getDisplayName() {
return displayName;
}
}
}
In the context of an enum, using #JsonValue now (since 2.0) works for serialization and deserialization.
According to the jackson-annotations javadoc for #JsonValue:
NOTE: when use for Java enums, one additional feature is that value returned by annotated method is also considered to be the value to deserialize from, not just JSON String to serialize as. This is possible since set of Enum values is constant and it is possible to define mapping, but can not be done in general for POJO types; as such, this is not used for POJO deserialization.
So having the Event enum annotated just as above works (for both serialization and deserialization) with jackson 2.0+.
Besides using #JsonSerialize #JsonDeserialize, you can also use SerializationFeature and DeserializationFeature (jackson binding) in the object mapper.
Such as DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, which give default enum type if the one provided is not defined in the enum class.
In my case, this is what resolved:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum PeriodEnum {
DAILY(1),
WEEKLY(2),
;
private final int id;
PeriodEnum(int id) {
this.id = id;
}
public int getId() {
return id;
}
public String getName() {
return this.name();
}
#JsonCreator
public static PeriodEnum fromJson(#JsonProperty("name") String name) {
return valueOf(name);
}
}
Serializes and deserializes the following json:
{
"id": 2,
"name": "WEEKLY"
}
I hope it helps!
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum LoginOptionType {
PHONE(1, "Phone"), MAIL(2, "mail"), PERSONAL_EMAIL(3, "Personal email");
private static List<LoginOptionType> all;
static {
all = new ArrayList<LoginOptionType>() {
{
add(LoginOptionType.PHONE);
add(LoginOptionType.MAIL);
add(LoginOptionType.PERSONAL_EMAIL);
}
};
}
private final Integer viewValue;
private final String name;
LoginOptionType(Integer viewValue, String name) {
this.viewValue = viewValue;
this.name = name;
}
public Integer getViewValue() {
return viewValue;
}
public String getName() {
return name;
}
public static List<LoginOptionType> getAll() {
return all;
}
}
Response
[
{
"viewValue": 1,
"name": "Phone"
},
{
"viewValue": 2,
"name": "mail"
},
{
"viewValue": 3,
"name": "Personal email"
}
]
Here, 'value' acts as a deserialiser and 'namespace' acts as a serialiser. Hence, you can pass in value "Student Absent" to API while saving, and in DB it will be saved as "STUDENT_ABSENT". On the other hand, while retrieving data in your class, your API will return "Student Absent"
import com.fasterxml.jackson.annotation.JsonProperty;
public enum AttendanceEnums {
STUDENT_PRESENT,
#JsonProperty(value = "Student Absent", namespace = "Student Absent")
STUDENT_ABSENT;
}
I had been looking for a solution to enum serialization and I finally made a solution.
https://github.com/sirgilligan/EnumerationSerialization
https://digerati-illuminatus.blogspot.com/2022/10/java-enum-generic-serializer-and.html
It uses a new annotation and two new classes, EnumerationSerializer and EnumerationDeserializer. You can subclass the EnumerationDeserializer and make a class that sets the enum Class (typical approach) or you can annotate the enum and you don't have to have a subclass of EnumerationDeserializer.
#JsonSerialize(using = EnumerationSerializer.class)
#JsonDeserialize(using = EnumerationDeserializer.class)
#EnumJson(serializeProjection = Projection.NAME, deserializationClass = RGB.class)
enum RGB {
RED,
GREEN,
BLUE
}
Notice how the implementation of ContextualDeserializer pulls the class from the annotation.
https://github.com/sirgilligan/EnumerationSerialization/blob/main/src/main/java/org/example/EnumerationDeserializer.java
There is a lot of good code in this that might give insights.
For your specific question you could do this:
#JsonSerialize(using = EnumerationSerializer.class)
#JsonDeserialize(using = EnumerationDeserializer.class)
#EnumJson(serializeProjection = Projection.NAME, deserializationClass = Event.class)
public enum Event {
FORGOT_PASSWORD("forgot password");
//This annotation is optional because the code looks for value or alias.
#EnumJson(serializeProjection = Projection.VALUE)
private final String value;
private Event(final String description) {
this.value = description;
}
}
Or you could do this:
#JsonSerialize(using = EnumerationSerializer.class)
#JsonDeserialize(using = EnumerationDeserializer.class)
#EnumJson(serializeProjection = Projection.NAME, deserializationClass = Event.class)
public enum Event {
FORGOT_PASSWORD("forgot password");
private final String value;
private Event(final String description) {
this.value = description;
}
}
That's all you have to do.
Then if you have a class that "has a" event you can annotate each occurance to serialize the way you want.
class EventHolder {
#EnumJson(serializeProjection = Projection.NAME)
Event someEvent;
#EnumJson(serializeProjection = Projection.ORDINAL)
Event someOtherEvent;
#EnumJson(serializeProjection = Projection.VALUE)
Event yetAnotherEvent;
}
The simplest way I found is using #JsonFormat.Shape.OBJECT annotation for the enum.
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum MyEnum{
....
}
I did it like this :
// Your JSON
{"event":"forgot password"}
// Your class to map
public class LoggingDto {
#JsonProperty(value = "event")
private FooEnum logType;
}
//Your enum
public enum FooEnum {
DATA_LOG ("Dummy 1"),
DATA2_LOG ("Dummy 2"),
DATA3_LOG ("forgot password"),
DATA4_LOG ("Dummy 4"),
DATA5_LOG ("Dummy 5"),
UNKNOWN ("");
private String fullName;
FooEnum(String fullName) {
this.fullName = fullName;
}
public String getFullName() {
return fullName;
}
#JsonCreator
public static FooEnum getLogTypeFromFullName(String fullName) {
for (FooEnum logType : FooEnum.values()) {
if (logType.fullName.equals(fullName)) {
return logType;
}
}
return UNKNOWN;
}
}
So the value of the property "logType" for class LoggingDto will be DATA3_LOG
This post is old, but if it can help someone, use JsonFormat.Shape.STRING
#JsonFormat(shape = JsonFormat.Shape.STRING)
public enum SomeEnum{
#JsonProperty("SOME_PROPERTY")
someProperty,
...
}
Code results is like this
{"someenum":"SOME_PROPERTY"}
I would like serialize an object such that one of the fields will be named differently based on the type of the field. For example:
public class Response {
private Status status;
private String error;
private Object data;
[ getters, setters ]
}
Here, I would like the field data to be serialized to something like data.getClass.getName() instead of always having a field called data which contains a different type depending on the situation.
How might I achieve such a trick using Jackson?
I had a simpler solution using #JsonAnyGetter annotation, and it worked like a charm.
import java.util.Collections;
import java.util.Map;
public class Response {
private Status status;
private String error;
#JsonIgnore
private Object data;
[getters, setters]
#JsonAnyGetter
public Map<String, Object> any() {
//add the custom name here
//use full HashMap if you need more than one property
return Collections.singletonMap(data.getClass().getName(), data);
}
}
No wrapper needed, no custom serializer needed.
Using a custom JsonSerializer.
public class Response {
private String status;
private String error;
#JsonProperty("p")
#JsonSerialize(using = CustomSerializer.class)
private Object data;
// ...
}
public class CustomSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeObjectField(value.getClass().getName(), value);
jgen.writeEndObject();
}
}
And then, suppose you want to serialize the following two objects:
public static void main(String... args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Response r1 = new Response("Error", "Some error", 20);
System.out.println(mapper.writeValueAsString(r1));
Response r2 = new Response("Error", "Some error", "some string");
System.out.println(mapper.writeValueAsString(r2));
}
The first one will print:
{"status":"Error","error":"Some error","p":{"java.lang.Integer":20}}
And the second one:
{"status":"Error","error":"Some error","p":{"java.lang.String":"some string"}}
I have used the name p for the wrapper object since it will merely serve as a placeholder. If you want to remove it, you'd have to write a custom serializer for the entire class, i.e., a JsonSerializer<Response>.
my own solution.
#Data
#EqualsAndHashCode
#ToString
#JsonSerialize(using = ElementsListBean.CustomSerializer.class)
public class ElementsListBean<T> {
public ElementsListBean()
{
}
public ElementsListBean(final String fieldName, final List<T> elements)
{
this.fieldName = fieldName;
this.elements = elements;
}
private String fieldName;
private List<T> elements;
public int length()
{
return (this.elements != null) ? this.elements.size() : 0;
}
private static class CustomSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
JsonProcessingException
{
if (value instanceof ElementsListBean) {
final ElementsListBean<?> o = (ElementsListBean<?>) value;
jgen.writeStartObject();
jgen.writeArrayFieldStart(o.getFieldName());
for (Object e : o.getElements()) {
jgen.writeObject(e);
}
jgen.writeEndArray();
jgen.writeNumberField("length", o.length());
jgen.writeEndObject();
}
}
}
}
You can use the annotation JsonTypeInfo, which tell Jackson exactly that and you don't need to write a custom serializer. There's various way to include this information, but for your specific question you'd use As.WRAPPER_OBJECT and Id.CLASS. For example:
public static class Response {
private Status status;
private String error;
#JsonTypeInfo(include = As.WRAPPER_OBJECT, use = Id.CLASS)
private Object data;
}
This, however, will not work on primitive type, such as a String or Integer. You don't need that information for primitives anyways, since they are natively represented in JSON and Jackson knows how to handle them. The added bonus with using the annotation is that you get deserialization for free, if you ever need it. Here's an example:
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Response r1 = new Response("Status", "An error", "some data");
Response r2 = new Response("Status", "An error", 10);
Response r3 = new Response("Status", "An error", new MyClass("data"));
System.out.println(mapper.writeValueAsString(r1));
System.out.println(mapper.writeValueAsString(r2));
System.out.println(mapper.writeValueAsString(r3));
}
#JsonAutoDetect(fieldVisibility=Visibility.ANY)
public static class MyClass{
private String data;
public MyClass(String data) {
this.data = data;
}
}
and the result:
{"status":"Status","error":"An error","data":"some data"}
{"status":"Status","error":"An error","data":10}
{"status":"Status","error":"An error","data":{"some.package.MyClass":{"data":"data"}}}
Based on #tlogbon response,
Here is my solution to wrap a List of Items with a specific/dynamic filed name
public class ListResource<T> {
#JsonIgnore
private List<T> items;
#JsonIgnore
private String fieldName;
public ListResource(String fieldName, List<T> items) {
this.items = items;
this.fieldName = fieldName;
}
#JsonAnyGetter
public Map<String, List<T>> getMap() {
return Collections.singletonMap(fieldName, items);
}