Java binary serializing fails because of jackson - java

I use jackson 2 to convert json into a java object. So far so good. But I also use hazelcast to distribute the objects in a cluster. Therefore all beans have to be java.io.Serializable. When I read the Object from json like so:
ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(AbstractBean.class, MongoIdMixIn.class);
// this is to prevent from failing on missing type class property: #JsonProperty("#class")
Object tgtObject = targetClass.newInstance();
mapper.readerForUpdating(tgtObject).readValue(dbo.toString());
// put into hazelcast map
target.put(dbo.get(keyColumn), tgtObject);
I will get an exception from hazelcast:
java.io.NotSerializableException: com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer
I am wondering where the com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer is coming from since the Object is a plain java bean (but using inheritance).
My Abstract class is:
#JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="#javaClass")
public abstract class AbstractBean implements Serializable {
#JsonIgnore public static final transient IMarkupParser MARKUP_PARSER = new WikiMarkupParser();
#JsonProperty("id")
private String id;
#JsonProperty("#class")
private String clazz;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getClazz() {
return this.getClass().getSimpleName();
}
}
And my child is:
public class Posting extends AbstractBean {
private String postingSource;
private String languageCode;
public String getPostingSource() {
return postingSource;
}
public void setPostingSource(String postingSource) {
this.postingSource = postingSource;
}
public String getLanguageCode() {
return languageCode;
}
public void setLanguageCode(String languageCode) {
this.languageCode = languageCode;
}
}
I have no Idea why the serailizer would even try to serialize the mixins since the are not part of the bean but here they are (yes I have tried to make them serializable too, just as a test, no luck):
public interface IdMixins extends Serializable {
}
public interface MongoIdMixIn extends IdMixins {
#JsonProperty("_id")
#JsonSerialize(using = MongoIdSerializer.class)
public String getId();
#JsonProperty("_id")
#JsonDeserialize(using = MongoIdDeserializer.class)
public void setId(String id);
}
public class MongoIdDeserializer extends JsonDeserializer<String> implements Serializable {
private static final long serialVersionUID = -5404276857799190647L;
#Override
public String deserialize(JsonParser jp, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String value = null;
String tmp = jp.getText(); // {
validate(jp, tmp,"{");
int curly = 1;
while (jp.nextToken() != null) {
String v = jp.getText();
if (v.equals("{")) curly++;
if (v.equals("$oid")) {
jp.nextToken();
value = jp.getText();
}
if (v.equals("}")) curly--;
if (curly<=0) return value;
}
return null;
}
private void validate(JsonParser jsonParser, String input, String expected) throws JsonProcessingException {
if (!input.equals(expected)) {
throw new JsonParseException("Unexpected token: " + input, jsonParser.getTokenLocation());
}
}
}
public class MongoIdSerializer extends JsonSerializer<String> implements Serializable {
private static final long serialVersionUID = 3435689991839324194L;
#Override
public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName("$oid");
jsonGenerator.writeString(s);
jsonGenerator.writeEndObject();
}
}

Stupid me! Somewhere in the serialization chain was a completely unnecessary ObjectMapper object. But it was hard to find because not the Posting object was the real reason, instead it was another object. But the Stacktrace and the com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer Exception were completely miss leading! ... clustered software is sometimes really painful to debug :-)

I'm 1 Rep. Point away from being able to comment. So I have to make a suggestion as an answer ;-).
Perhaps one of the Annotations do inject an instance of TypeWrappedDeserializer as a private property into the AbstractBean. Maybe as hint for the deserialization mechanism.
Could you inspect the created object with reflection to verify?
for (Field field : tgtObject.getClass().getDeclaredFields() )
{
// you can replace this by your logging method
System.out.println("Field: " + field.getName() + ":" + field.getType());
}
for (Field field : tgtObject.getClass().getSuperclass().getDeclaredFields() )
{
// you can replace this by your logging method
System.out.println("Field: " + field.getName() + ":" + field.getType());
}
If you find the apropriate type in the listing the Class was added by Byte Code Enhancement.

Related

How deserialize plain String to Json using Jackson in Java?

I have a simple class as property of mage:
// getter/setter omitted for brevity
public class Magic() {
String Spell;
int strength;
}
public class Mage() {
String name;
Magic magic;
}
I need to deserialize JSON from 2 different source strings:
{
"name" : "Sauron",
"magic" : {
"spell" : "Tamador",
"strenght" : 10
}
}
and
{
"name" : "Gandalf",
"magic" : "You shall not pass"
}
or even "You shall not pass" -> Magic object
I thought going with #JsonDeserialize(using = MagicDeserializer.class) would be the way to go with Jackson, but the Parser barfs with "Unrecognized token". Is there a way I can intercept the loading to do my own parsing?
The idea of a custom deserializer is correct, you can extends the StdDeserializer class and in its deserialize method convert the json to a JsonNode separating the two Stringand Object distinct values associated to the magic key in the json:
public class MagicDeserializer extends StdDeserializer<Magic> {
public MagicDeserializer() {
super(Magic.class);
}
#Override
public Magic deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
final ObjectCodec codec = jp.getCodec();
JsonNode root = codec.readTree(jp);
Magic magic = new Magic();
if (root.isTextual()) { //<- magic is a string
magic.setSpell(root.textValue());
return magic;
}
//ok, so magic is an Magic object
return codec.treeToValue(root, Magic.class);
}
}
Then if you annotate your Magic field you can deserialize both the jsons:
#Data
public class Mage {
private String name;
#JsonDeserialize(using = MagicDeserializer.class)
private Magic magic;
}
#Data
public class Magic {
private String Spell;
private int strength;
}
Mage sauron = mapper.readValue(json1, Mage.class);
System.out.println(mapper.writeValueAsString(sauron));
Mage gandalf = mapper.readValue(json2, Mage.class);
System.out.println(mapper.writeValueAsString(gandalf));

Jackson serializes list of objects with empty objects

I'm trying to serialize a very large object graph that uses #JsonView (I don't know if that's relevant or not). What I'm finding is that for a list of objects within the graph, Jackson is serializing a list of empty objects, such as [{},{},{}]. None of the attributes are in the string. All scalar attributes serialize just fine. I'm only having trouble with the lists.
I've verified several times that the attributes are being set in the objects. Part of my POJO looks like so:
public class ProfessionalData implements Serializable {
#JsonProperty("collegeEducation")
#JsonView(Views.Myview.class)
#Valid
private List<CollegeEducation> collegeEducation = new ArrayList<CollegeEducation>();
#JsonProperty("managementCommittee")
#JsonView(Views.Myview.class)
#Valid
private List<ManagementCommittee> managementCommittee = new ArrayList<ManagementCommittee>();
//getters and setters
}
This is the ObjectMapper code:
#Override
public String convertToDatabaseColumn(#NotNull MyPojoItem item) {
try {
//Disable Default_view_Inclusion, so fields without a view annotation wont be included
objectMapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
objectMapper.setDateFormat(df); //Transform the Object to String
String result = objectMapper
.writerWithView(Views.MyView.class)
.writeValueAsString(item);
return result;
} catch (Exception ex) {
LOG.error("Failed to convertToDatabaseColumn: " + ex.getMessage());
return null;
}
}
Can someone tell me what this usually means?
You could do something like this for both lists. A custom serializer to write your objects there from a List. For example, for the CollegeEducation list:
public class CollegeEducationSerializer extends JsonSerializer<List<CollegeEducation>>
{
#Override
public void serialize(List<CollegeEducation> list, JsonGenerator json,
SerializerProvider provider) throws IOException
{
if (list == null || list.isEmpty())
return;
json.writeFieldName("CollegeEducationArray");
json.writeStartArray();
for (CollegeEducation ce : list)
{
json.writeStartObject();
json.writeObjectField("CollegeEducation", ce);
json.writeEndObject();
}
json.writeEndArray();
}
}
And specify it on the annotations:
#JsonSerialize(using = CollegeEducationSerializer.class)
private List<CollegeEducation> collegeEducation = new ArrayList<CollegeEducation>();
In order to read it, the same logic applies (a custom deserializer for both lists).
For something like a generic approach, you could also do something like this:
public class CustomListSerializer extends JsonSerializer<List<? extends ListElement>>
{
#Override
public void serialize(List<? extends ListElement> list, JsonGenerator json,
SerializerProvider provider) throws IOException
{
if (list == null || list.isEmpty())
return;
json.writeFieldName(list.get(0).getElementType()+"Array");
json.writeStartArray();
for (ListElement le : list)
{
json.writeStartObject();
json.writeObjectField(le.getElementType(), le);
json.writeEndObject();
}
json.writeEndArray();
}
}
Then you could use it for all your Lists:
#JsonSerialize(using = CustomListSerializer.class)
private List<CollegeEducation> collegeEducation = new ArrayList<>();
#JsonSerialize(using = CustomListSerializer.class)
private List<ManagementCommittee> managementCommittee = new ArrayList<>();
For this I added a customProperties Map, which may be prone to errors here. So the approach would be writing manually the desired fields, instead of using json.writeObjectField(le.getElementType(), le);
public abstract class ListElement
{
public abstract String getElementType();
public Map<String, String> properties = new HashMap<>();
public final String name;
public final int id;
public ListElement(String name, int id)
{
this.name = name;
this.id = id;
}
}
and, f.e:
public class CollegeEducation extends ListElement
{
protected String location;
protected String director;
public CollegeEducation(String name, int id, String location, String director)
{
super(name,id);
this.director = director;
this.location= location;
properties.put("director", director);
properties.put("location", location);
//...
}
public String getElementType()
{
return "CollegeEducation";
}
//...
}
//...
In the serializer:
json.writeFieldName(list.get(0).getElementType()+"Array");
json.writeStartArray();
for (ListElement le : list)
{
json.writeStartObject();
json.writeFieldName(le.getElementType()+"-"+le.id);
json.writeStartObject();
json.writeStringField("name", le.name);
for(Map.Entry<String,String> kv : le.properties.entrySet())
json.writeStringField(kv.getKey(), kv.getValue());
json.writeEndObject();
json.writeEndObject();
}
json.writeEndArray();
You should get something like:
{
"CollegeEducationArray":
[
{ "CollegeEducation-22" :
{
"name" : "Lauaxeta",
"director" : "AJerk",
"location": "Bilbao"
}
},
{ "CollegeEducation-55" :
{
"name" : "Harvard",
"director" : "OtherJerk",
"location": "Rwanda"
}
}
]
}

Deserializing an enum with Jackson (#JsonValue) [duplicate]

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!
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"}
#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"
}
]

Generic Enum Json Deserialization

I needed a better hibernate enum mapping and this page served me well (Except I used char type instead of int).
Next question is how can I serialize/deserialize an enum in a generic way?
Think of a Gender enum:
#JsonSerialize(using = PersistentEnumSerializer.class)
#JsonDeserialize(using = PersistentEnumDeserializer.class)
public enum Gender implements PersistentEnum {
MALE("M", "Male"), FEMALE("F", "Female");
private String code;
private String display;
Gender(String code, String display) {
this.code = code;
this.display = display;
}
public String getName() {
return name();
}
public String getCode() {
return code;
}
public String getDisplay() {
return display;
}
public String toString() {
return display;
}
}
which implements getName(), getCode() and getDisplay() methods of PersistentEnum interface. Serializing is easy:
public class PersistentEnumSerializer extends JsonSerializer<PersistentEnum> {
#Override
public void serialize(PersistentEnum object, JsonGenerator generator, SerializerProvider provider) throws IOException, JsonProcessingException {
generator.writeStartObject();
generator.writeFieldName("name");
generator.writeString(object.getName());
generator.writeFieldName("code");
generator.writeString(object.getCode());
generator.writeFieldName("display");
generator.writeString(object.getDisplay());
generator.writeEndObject();
}
}
but how can I deserialize in java 6? In java 8, I would add a static method to PersistentEnum interface.
public class PersistentEnumDeserializer extends JsonDeserializer<PersistentEnum> {
#Override
public PersistentEnum deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
//String value = node.get("name").asText();
//TODO Somehow I need to get Gender.MALE if the json is {"name":"MALE","code":"M","display":"Male"}
return null;
}
}
One possible solution is to add a new method getType() to PersistentEnum which will identify the type of Enum.
#JsonSerialize(using = PersistentEnumSerializer.class)
#JsonDeserialize(using = PersistentEnumDeserializer.class)
public enum Gender implements PersistentEnum {
#Override
public String getType() {
return "gender";
}
}
Serializer should also be modified to include type while serialization.
public class PersistentEnumSerializer extends JsonSerializer<PersistentEnum> {
#Override
public void serialize(PersistentEnum object, JsonGenerator generator, SerializerProvider provider) throws IOException, JsonProcessingException {
generator.writeStartObject();
generator.writeFieldName("name");
generator.writeString(object.getName());
generator.writeFieldName("code");
generator.writeString(object.getCode());
generator.writeFieldName("display");
generator.writeString(object.getDisplay());
generator.writeFieldName("type");
generator.writeString(object.getType());
generator.writeEndObject();
}
}
Deserializer can be written as shown below.
public class PersistentEnumDeserializer extends JsonDeserializer<PersistentEnum> {
#Override
public PersistentEnum deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
return findEnum(node.get("type").asText(), node.get("name").asText());
}
private PersistentEnum findEnum(String type, String name) {
switch (type) {
case "gender":
return Gender.valueOf(name);
// handle other types here.
default:
return null;
}
}
}
While #Justin Jose's solution is not the one I'm looking for (because for each enum we need to add to findEnum method), it gave me a good hint.
If getType is implemented like this:
#Override
public String getType() {
return getClass().getSimpleName();
}
and findEnum like this
private PersistentEnum findEnum(String type, String name) {
Class<?> c = null;
try {
c = Class.forName("enums." + type); //Assuming all PersistentEnum's are in "enums" package
if (PersistentEnum.class.isAssignableFrom(c)) {
Method method = c.getMethod("name");
for (Object object : c.getEnumConstants()) {
Object enumName = method.invoke(object);
if (name.equals(enumName))
return (PersistentEnum) object;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
it may work. Not tested and possibly vulnerable.

Getting JsonMappingException while sending data to view

I am trying to show DB data to my webpage.
I have made following code when GET request to the #RequestMapping(value = "/api/binder").
but when get request came to this method it will fetch data (I have print on console and display well) but it doesn't map to my Java Script Ajax call, it's showing me an error.
Following is my code for to fetch data :
#Autowired
IBinderViewRepository repository;
#RequestMapping(method= RequestMethod.GET)
public #ResponseBody
List<BinderResponse> getBinders(){
List<BinderView> binders = repository.getBinders();
List<BinderResponse> responses = new ArrayList<>();
ModelMapper mapper = Mapper.getInstance();
for(int i = 0; i < binders.size(); i++){
System.out.println("In Loop");
BinderResponse response = mapper.map(binders.get(i),BinderResponse.class);
System.out.println("Data :: " + response.getBinderName());
responses.add(response);
}
return responses;
}
but it shows me following error :
HTTP Status 500 - Could not write JSON: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.ngl.dto.outgoing.BinderResponse["valid"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.ngl.dto.outgoing.BinderResponse["valid"])
Here is ajax call from knockout js :
ajax.get('api/binder').done(function(response){ ... }
Here BinderView and BinderResponse have same fields :
private String binderName;
private String binderAddress1;
and getter setter as well in both.
and repository.genBinders() method bring data from DB.
Here is insert method and works fine for me :
#RequestMapping(method= RequestMethod.POST,consumes = "application/json")
public #ResponseBody
IWebApiResponse addBinder(#RequestBody AddBinderForm binder){
.....
}
Shall I have to put any json annotation on my BinderResponse class ?
I don't understand where am i wrong ?Anyone pleas guide me.
UPDATE :
public class BinderResponse extends WebApiResponseBase {
private String binderName;
private String binderAddress1;
public String getBinderName() {
return binderName;
}
public void setBinderName(String binderName) {
this.binderName = binderName;
}
public String getBinderAddress1() {
return binderAddress1;
}
public void setBinderAddress1(String binderAddress1) {
this.binderAddress1 = binderAddress1;
}
}
BinderView :
public class BinderView extends BaseView {
private String binderName;
private String binderAddress1;
public String getBinderName() {
return binderName;
}
public void setBinderName(String binderName) {
this.binderName = binderName;
}
public String getBinderAddress1() {
return binderAddress1;
}
public void setBinderAddress1(String binderAddress1) {
this.binderAddress1 = binderAddress1;
}
}
In console it prints data / BinderName :
In Loop
Data :: ada
In Loop
Data :: tya
New Update :
Here is BaseView :
#MappedSuperclass
public abstract class BaseView implements IEntity {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Column(name="id")
private long id;
public long getId() {
return id;
}
public void setId(long id) {
if (this.id != 0 && this.id != id) {
throw new IllegalStateException(
"The ID must not be changed after it is set.");
}
this.id = id;
}
}
and In IEntity :
public interface IEntity extends Serializable {
long getId();
void setId(long id);
}
WebApiResponseBase :
public class WebApiResponseBase implements IWebApiResponse {
private String _uri;
#Override
public String getUri() {
return _uri == null ? "" : _uri;
}
#Override
public void setUri(String uri) {
_uri = uri;
}
}
Jackson, by default, serializes an object's whole inheritance hierarchy, ie. the parent class fields as well. In the case of
public class BinderResponse extends WebApiResponseBase {
it seems like
Could not write JSON: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.ngl.dto.outgoing.BinderResponse["valid"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.ngl.dto.outgoing.BinderResponse["valid"])
Jackson tries to serialize a field called valid from a getter called isValid (which is a conventional bean property name). The getter method, however, seems to throw a NullPointerException for whatever reason.
If you want Jackson to ignore it, you can annotate the getter with #JsonIgnore or your class with #JsonIgnoreProperties and specify the property name, ie. valid.
In my case when I used #JsonIgnore the exception has been gone but the problem was it couldn't receive that value from API Request anymore and Spring ignored it (obviously because of #JsonIgnore) So I investigated about the issue and figured out that the problem was the getter and setter.
I had the Integer property while my getter was int. So when I changed the getter to Integer my problem solved and error's gone.
private Integer purchaseId;
#JsonIgnore
public int getPurchaseId() {
return purchaseId;
}
public void setPurchaseId(int purchaseId) {
this.purchaseId = purchaseId;
}
Changed to :
private Integer purchaseId;
public Integer getPurchaseId() {
return purchaseId;
}
public void setPurchaseId(Integer purchaseId) {
this.purchaseId = purchaseId;
}
#Column(name="createddate")
private Date createdDate;
#Transient
private String formatedCreatedDate;
public String getFormatedCreatedDate() {
DateFormat dateFormat = new SimpleDateFormat("dd/mm/yyyy");
return dateFormat.format(this.getCreatedDate());
}
It throws the same exception because here may be null by calling getCreatedDate() value come so it can't format null date so keep null check here like:
Solution
public String getFormatedCreatedDate() {
DateFormat dateFormat = new SimpleDateFormat("dd/mm/yyyy");
Date createDdate=this.getCreatedDate();
if(createDdate!=null){
return dateFormat.format(createDdate);
}
return "-";
}

Categories