I'm using Jackson + Spring in a REST API. One of the API calls has JSON that looks like this:
{
"status": "Suspended"
}
where the value of "status" is mapped to a Java enum, like so:
public enum FeatureStatus {
Activated(0),
Inactivated(1),
Suspended(2),
Deleted(3);
private FeatureStatus(int id) {
this.id = id;
}
private int id;
public int getId() {
return id;
}
public FeatureStatus valueOf(int id) {
switch(id) {
case 1: return Inactivated;
case 2: return Suspended;
case 3: return Deleted;
default: return Activated;
}
}
#JsonCreator
public static FeatureStatus fromValue(String status) {
if(status != null) {
for(FeatureStatus featureStatus : FeatureStatus.values()) {
if(featureStatus.toString().equals(status)) {
return featureStatus;
}
}
throw new IllegalArgumentException(status + " is an invalid value.");
}
throw new IllegalArgumentException("A value was not provided.");
}
}
However, this exception gets thrown: java.lang.IllegalArgumentException: { is an invalid value. It looks like it's not deserializing the JSON at all. My controller method has this definition:
public #ResponseBody void updateFeatureStatusById(#PathVariable long featureId, #RequestBody FeatureStatus updated) {
Any other controller automatically deserializes the JSON fine as expected using this format. How can I deserialize this JSON into my enum?
Put this class next to your controller:
final class FeatureStatusJsonObject {
public FeatureStatus status;
}
And then in the controller method use this:
#RequestBody FeatureStatusJsonObject updated
and get the real FeatureStatus by
updated.status
This makes the conversion from a JSON object to an Enum literal explicit in your code, and allows you to use the regular Enum <-> String (de)serialization elsewhere (if necessary).
On an unrelated note, this looks a bit funky:
#ResponseBody void
I would just make it void.
Is the exact JSON you pass an Object? Enums except either JSON String or number, not Object.
If you need to give an object, you should map it to something like:
public class EnumWrapper {
public FeatureStatus status;
}
and it'll work.
Related
Initial Question
Is it possible to have multiple #JsonCreator methods, and for jackson to detect which one it should use depending on the method definiton?
#JsonCreator
public static StateOfComm factory(String id) {
return StateOfComm.valueOf(id);
}
#JsonCreator
public static StateOfComm factory(CustomType value) {
return StateOfComm.valueOf(value.getId());
}
Update
The JSON that fails (because id=null), is the following:
{"comment":null, "processes":[{"stateOfComm":{"id":"CA"}}]}
The following works:
{"comment":null, "processes":[{"stateOfComm":"CA"}]}
I was able to parse both JSON examples in your question by:
using jackson-modules-java8 version 2.9.1 dependency
invoking the java 8 compiler with -parameters argument
introducing all argument constructors for all classes involved
avoiding #JsonProperty on static creation methods and constructors
defining a class:
class CustomType {
private final String id;
}
My understanding is that Jackson couldn't discern between multiple creators in older version. E.g. see answer here and github issue here. It seems that the option to have parameter names in compiled code in java 8 helps in this case.
I solved this problem by getting rid of the #JsonCreator annotations, and using a custom StdDeserializer that did the trick.
Here is an example:
#JsonIgnoreProperties(
ignoreUnknown = true
)
#JsonDeserialize(
using = IdTextEntry.Deserializer.class
)
#Data
public class IdTextEntry implements IdAndText {
String id;
String text;
public IdTextEntry(Enum<?> val) {
if (val != null) {
this.id = val.name();
this.text = val.toString();
}
}
public static class Deserializer extends StdDeserializer<IdTextEntry> {
public Deserializer() {
this((Class)null);
}
Deserializer(Class<?> vc) {
super(vc);
}
public IdTextEntry deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
JsonNode node = (JsonNode)jp.getCodec().readTree(jp);
String id;
if (node.has("id") && node.has("text")) {
id = node.get("id").asText();
String text = node.get("text").asText();
return new IdTextEntry(id, text);
} else if (node.has("id")) {
id = node.get("id").asText();
return new IdTextEntry(id, id);
} else {
id = node.asText();
return new IdTextEntry(id, id);
}
}
}
}
You can have multiple #JsonCreator methods but it requires to use #JsonProperty for specifying which property you are initializing.
#JsonCreator
public static StateOfComm factory(#JsonProperty("id") String id) {
return StateOfComm.valueOf(id);
}
#JsonCreator
public static StateOfComm factory(#JsonProperty("value") CustomType value) {
return StateOfComm.valueOf(value.getId());
}
I have following JSON
{
id: 1234,
code: "N/A"
}
I would like to transform it to object of such classes:
class MySweetObject {
int id;
SomeStringWrapper code;
}
class SomeStringWrapper {
String code;
Boolean isNotAvailable() {
return "N/A".equals(code);
}
}
In short - I want to wrap simple property value from JSON in object to add some business functionality. Null value is not an option as code is a required property.
Is it possible to do it in Jackson?
You could use JsonSetter annotation in setCode method:
From the documentation:
Setter means that when a property with matching name is encountered in
JSON content, this method will be used to set value of the property
This way you could check if the value is null before setting it, and if it is null then set a default value (or do whatever you want).
You need to modify your JSON string, its wrong as per your classes. As the value "N/A" jackson will assume that to be a string, but that is the instance of SomeStringWrapper class.
Your JSON should look like as below in order to deserialize to your class structure:
{
id: 1234,
code: {
code : "N/A"
}
}
Try the following code:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.javafx.fxml.PropertyNotFoundException;
class Main {
public static void main(String[] args) throws Exception {
String jsonString = "{\"id\":\"1234\"}";
ObjectMapper mapper = new ObjectMapper();
MySweetObject obj = mapper.readValue(jsonString, MySweetObject.class);
System.out.println(mapper.writeValueAsString(obj));
}
}
class MySweetObject {
private String id;
private String code;
public String getId() {
return id;
}
public String getCode() {
if (code == null) {
throw new PropertyNotFoundException("Property code cannot be null");
} else {
return code;
}
}
}
Normally #JsonProperty(required = true) would throw an exception, but It is not working, instead you can manually throw an exception in the getter if property is null or empty. check my code above, it will throw JsonMappingException if null.
This would be an extension to the following question:
Spring web service request and response for mapping having list of other objects
What if the Question POJO had an enum type. How do I represent that in the ajax call when making a request and using #RequestBody.
eg:
public class Question {
private String questionText;
List<Options> options;
private QuestionTye questionType
public String getQuestionText() {
return questionText;
}
public void setQuestionText(String questionText) {
this.questionText = questionText;
}
//getters and setters for options
}
Enum looks as follows:
public enum QuestionType {
MCQ, FILL_IN_THE_BLANK, QUESTION_AND_ANSWER, MATCH_THE_FOLLOWING
}
How should I then format my JSON when sending it to the server?
Assuming you have the jackson API on the classpatch, add an annotated "Create" method. I also like to add a "None" option to my enums to avoid null pointer:
public enum QuestionType {
MCQ, FILL_IN_THE_BLANK, QUESTION_AND_ANSWER, MATCH_THE_FOLLOWING, NONE;
#JsonCreator
public QuestionType create(String input){
QuestionType result;
if("MCQ".equals(input)){
result = MCQ;
}
else if(/*snip etc*/){
}//etc
else{
result = NONE;//Or null or throw an exception .. whatever
}
return result;
}
}
I have the following Enum:
public enum MyState {
Open("opened"),
Close("closed"),
Indeterminate("unknown");
private String desc;
private MyState(String desc) {
setDesc(desc);
}
public String getDesc() {
return this.desc;
}
private void setDesc(String desc) {
this.desc = desc;
}
}
I am trying to write an XStream Converter that will know to map back a JSON element "mystate" to a MyState instance.
"someJson": {
"object1": {
"mystate": closed
}
}
This should produce, amongst other objects (someJson and object1) a MyState.Close instance. I've started the Converter, but haven't gotten very far:
public class MyStateEnumConverter implement Converter {
#Override
public boolean canConvert(Class clazz) {
return clazz.equals(MyState.class);
}
#Override
public void marshal(Object value, HierarchialStreamWriter writer, MarshallingContext context) {
??? - no clue here
}
#Override
public Object unmarshal(HierarchialStreamReader reader, UnmarshallingContext context) {
??? - no clue here
}
}
Then, to create the mapper and use it:
XStream mapper = new XStream(new JettisonMappedXmlDriver());
mapper.registerConverter(new MyStateEnumConverter);
SomeJson jsonObj = mapper.fromXML(jsonString);
// Should print "closed"
System.out.println(jsonObject.getObject1().getMyState().getDesc());
How can I implement marshal and unmarshal so thatI get the desired mapping? Thanks in advance!
You can accomplish this by doing 2 things:
Adding a lookup method as well as a toString() override to your enum (MyStateEnum); and
Extending XStream's AbstractSingleValueConverter instead of implementing Converter
MyStateEnum:
public enum MyStateEnum {
// Everything you had is fine
// But now, add:
public static MyStateEnum getMyStateByDesc(String desc) {
for(MyStateEnum myState : MyStateEnum.values())
if(myState.getDesc().equals(desc))
return myState;
return null;
}
#Override
public String toString() {
return getDesc();
}
}
MyStateEnumConverter:
public class MyStateEnumConverter extends AbstractSingleValueConverter {
#Override
public boolean canConvert(Class clazz) {
return clazz.equals(MyStateEnum.class);
}
#Override
public Object fromString(String parsedText) {
return MyStateEnum.getMyStateByDesc(parsedText);
}
}
By adding getMyStateByDesc(String) to your enum, you now have a way to look up all the various enumerated values from the outside, by providing a desc string. The MyStateEnumConverter (which extends AbstractSingleValueConverter) uses your toString() override under the hood to associate aMyStateEnum instance with a text string.
So when XStream is parsing the JSON, it sees a JSON object of, say, "opened", and this new converter knows to pass "opened" into the converter's fromString(String) method, which in turn uses getMyStateByDesc(String) to lookup the appropriate enum instance.
Don't forget to register your converter with your XStream instance as you already showed in your original question.
You can use the EnumToStringConverter
Documentation
Example
#XStreamConverter(EnumToStringConverter.class)
public enum MyStateEnum {
enter code here
...
Use xstream.autodetectAnnotations(true)
Why are you using xstream for json support? You have a couple of other libraries specialized in json and that do it well. Also closed without quotes is not valid json.
Try for example Genson, it will work out of the box.
The values in the json stream would be "Close", "Indeterminate", etc and when deserializing it will produce the correct enum.
class SomeObject {
private MyState state;
...
}
Genson genson = new Genson();
// json = {"state" : "Indeterminate"}
String json = genson.serialize(new SomeObject(MyState.Indeterminate));
// deserialize back
SomeObject someObject = genson.deserialize(json, SomeObject.class);
// will print unknown
System.out.println(someObject.getDesc());
I receive a json encoded string and then I decode it into a pojo, like this:
String json = ...
final ObjectMapper mapper = new ObjectMapper();
MyPojo obj = mapper.readValue(json, MyPojo.class);
I want to be able to validate this input, but I'm not sure what's the "right way" of doing it.
Let's say MyPojo is defined like this:
#JsonIgnoreProperties(ignoreUnknown=true)
class MyPojo {
public static enum Type {
One,
Two,
Three;
#JsonValue
public String value() {
return this.name().toLowerCase();
}
}
private String id;
private Type type;
private String name;
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public Type getType() {
return this.type;
}
public void setType(Type type) {
this.type = type;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
There are three things I want to validate:
That all members have values
That the enum value is part of the enumeration
Test some or all values against some criteria (i.e.: min or max length, min or man number value, regular expression, etc)
If the validation fails I want to return a meaningful and readable message.
For the first and third issues I can simply check all the members of the object and see if any are null, and if not test them against the criteria, but this approach can get long and complicated when there are many fields.
As for the 2nd issue, if the value in the input does not match one of the enumeration values then a JsonMappingException is thrown, and so I managed to do this:
try {
MyPojo obj = mapper.readValue(json, MyPojo.class);
}
catch (JsonMappingException e) {
return "invalid value for property: " + e.getPath().get(0).getFieldName();
}
But how do I get the value in the input so that I can return: invalid value: VALUE for property: PROPERTY?
Thanks.
I would recommend using an implementation of JSR-303 Bean Validation. This specification defines a set of annotations and XML config files for specifying constraints that you wish to validation on your domain obects. The reference implementation is available here:
http://www.hibernate.org/subprojects/validator.html