You can have many problem when your system try to interpret incoming JSON content. It is a summary of problems and solutions with Jackson.
For preventing this issues you can use @JsonCreator and @JsonValue annotations.
Let's create an Enum for the possible languages, for example we have a node:
final HashMap<String, String> title = new HashMap<String, String>();
We dont like the Strings, the first String is a language code, the second is the title of the content, like:
{
title : {
"EN": "title",
"FR": "title in FR"
}
}
I created an Enum to restrict, reuse the languges.
public enum MediaLanguage {
INT, EN, FR, DE
}
INT means here it is international version. Dont care! And just for fun I recieved "INT/EN" once!
Cool, lets see the annotations in an example:
public enum MediaLanguage {
INT, EN, FR, DE;
public static List<String> names() {
List<String> retval = new ArrayList<String>();
for (MediaLanguage elem : MediaLanguage.values()) {
retval.add(elem.name());
}
return retval;
}
public static boolean exist(String code) {
return code != null && names().contains(code.toUpperCase());
}
@JsonCreator
public static MediaLanguage getByJsonValue(String value) {
if (exist(value)) {
return MediaLanguage.valueOf(value.toUpperCase());
}
return EN;
}
@JsonValue
public String toJsonValue() {
return this.name();
}
}
I created a getByJsonValue() function with @JsonCreator annotation. When Jackson tries to map it to MediaLanguage enum, he will call this function automatically to get the enum instance.
First he will check, do we have this value in the list, and then return the instance. Just a comment, here you can use HasMaps to store the key-value pairs, if the enum element name is different then the JSON value.
If we could not found, we return with a default value. I dont want to throw exception, I dont want lose data.
I created a toJsonValue method with @JsonValue annotation. It will be called automativally by Jackson when he serialise the Enum to JSON string.
This few functions are a little extensions in the Enum, but you can trust more and enjoy the type checks.
Convert to Enum
We like Enums in Java, because it is a terminated list of values. Sometimes you can find all the possible values of a property on the API, sometimes not. And of course you can get different undefined values, JSON is just a text.For preventing this issues you can use @JsonCreator and @JsonValue annotations.
Let's create an Enum for the possible languages, for example we have a node:
final HashMap<String, String> title = new HashMap<String, String>();
We dont like the Strings, the first String is a language code, the second is the title of the content, like:
{
title : {
"EN": "title",
"FR": "title in FR"
}
}
I created an Enum to restrict, reuse the languges.
public enum MediaLanguage {
INT, EN, FR, DE
}
INT means here it is international version. Dont care! And just for fun I recieved "INT/EN" once!
Cool, lets see the annotations in an example:
public enum MediaLanguage {
INT, EN, FR, DE;
public static List<String> names() {
List<String> retval = new ArrayList<String>();
for (MediaLanguage elem : MediaLanguage.values()) {
retval.add(elem.name());
}
return retval;
}
public static boolean exist(String code) {
return code != null && names().contains(code.toUpperCase());
}
@JsonCreator
public static MediaLanguage getByJsonValue(String value) {
if (exist(value)) {
return MediaLanguage.valueOf(value.toUpperCase());
}
return EN;
}
@JsonValue
public String toJsonValue() {
return this.name();
}
}
I created a getByJsonValue() function with @JsonCreator annotation. When Jackson tries to map it to MediaLanguage enum, he will call this function automatically to get the enum instance.
First he will check, do we have this value in the list, and then return the instance. Just a comment, here you can use HasMaps to store the key-value pairs, if the enum element name is different then the JSON value.
If we could not found, we return with a default value. I dont want to throw exception, I dont want lose data.
I created a toJsonValue method with @JsonValue annotation. It will be called automativally by Jackson when he serialise the Enum to JSON string.
This few functions are a little extensions in the Enum, but you can trust more and enjoy the type checks.
Different JSON stucture
Very annoying situation, if in one property we could receive different structure of JSON. For example:
"_id": 1234,
"type": "PHOTO",
"cooperator": {
"AGENCY": ["Audiovisual Service ", "Lorant"],
"PHOTOGRAPHER": ["Benedek Dankahazi"]
},
"_id": 1244,
"type": "VIDEO",
"cooperator": {
"I-12345-EN": {"PRODUCER": ["Audiovisual Service"]}
},
It is not so funny. In the first case we can wrap the content with a HashMap<String, String> or later with a HashMap<MediaRole, String>:
public class MediaDocumentJSON implements Serializable {
private static final long serialVersionUID = 1L;
private Long _id;
private MediaType type;
private Map<MediaRole, String> cooperator = new HashMap<MediaRole, String>();
}
But the second case is not a hash map, we have an ID as a string and after that some roles in the HashMap<MediaRole, String>.
The second difficulty is this key is always changing, like an id, we can not use @JsonProperty annotation.
The only starting point is that we know when we receive the first and in which case the second one. So we can separate a little bit the interpretation logic.
If you found a very dynamic node in the JSON like this, you can use the JsonNode type, and you will read this value later.
public class MediaDocumentJSON implements Serializable {
private static final long serialVersionUID = 1L;
private Long _id;
private MediaType type;
private JsonNode cooperator;
}
Then we read this value in the process:
if (PHOTO.equals(document.getType())) {
return getPhotoCooperator(cooperatorJson);
} else {
return getAudioVideoCooperator(cooperatorJson);
}
The fist case you can cover with another class, or HashMap.
protected static String getPhotoCooperator(String cooperatorJson) {
try {
CooperatorForPHOTOSJSON cooperator = mapper.readValue(nvl(cooperatorJson, ""), CooperatorForPHOTOSJSON.class);
return StringUtils.join(cooperator.getPhotographer(), ",");
} catch (Exception e) {
return "";
}
}
But for the second case we have to use the TypeFactory and the MapType:
static ObjectMapper mapper = new ObjectMapper();
static {
TypeFactory typeFactory = mapper.getTypeFactory();
MapType cooperatorVideoMapType = typeFactory.constructMapType(HashMap.class, String.class, CooperatorForVIDEOSJSON.class);
}
protected static String getAudioVideoCooperator(String cooperatorJson) {
StringBuilder result = new StringBuilder();
try {
HashMap<String, CooperatorForVIDEOSJSON> cooperatorMap = mapper.readValue(nvl(cooperatorJson, ""), cooperatorVideoMapType);
CooperatorForVIDEOSJSON cooperator = cooperatorMap.values().iterator().next();
if (!cooperator.getDirector().isEmpty()) {
result.append("Director: ").append(StringUtils.join(cooperator.getDirector(), ","));
}
} catch (Exception e) {
return "";
}
return result.toString();
}
Good luck with the JSONs!

