I have a POJO that contains the following attributes
public class Example {
#JsonProperty("inputFoo")
private String foo
#JsonProperty("inputBar")
private String bar
#JsonProperty("inputBaz")
#JsonDeserialize(using = MyDeserializer.class)
private Set<String> baz
}
The JSON that I am working with to represent this data currently represents the baz attribute as a single string:
{"inputFoo":"a", "inputBar":"b", "inputBaz":"c"}
I am using the Jackson ObjectMapper to attempt to convert the JSON to my POJO. I know that the input baz String from the JSON wont map cleanly to the Set that I am trying to represent it as, so I defined a custom Deserializer:
public class MyDeserializer extends StdDeserializer<Set<String>> {
public MyDeserializer(){}
public MyDeserializer(Class<?> vc) {
super(vc);
}
public Set<String> deserialize(JsonParser p, DeserializationContext cxt) throws IOException, JsonProcessingException {
String input = p.readValueAs(String.class);
Set<String> output = new HashSet<>();
if(input != null) {
output.add(input);
}
return output;
}
}
I am getting an IllegalArgumentException referencing the "inputBaz" attribute, which I can provide details on. Does anyone see any obvious issue with my deserializer implementation? Thanks
You do not need to implement custom deserialiser, use ACCEPT_SINGLE_VALUE_AS_ARRAY feature. It works for sets as well:
Feature that determines whether it is acceptable to coerce non-array
(in JSON) values to work with Java collection (arrays,
java.util.Collection) types. If enabled, collection deserializers will
try to handle non-array values as if they had "implicit" surrounding
JSON array. This feature is meant to be used for
compatibility/interoperability reasons, to work with packages (such as
XML-to-JSON converters) that leave out JSON array in cases where there
is just a single element in array. Feature is disabled by default.
See also:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of java.util.ArrayList out of START_OBJECT token
Replace the 2 constructors with this no-arg constructor:
public MyDeserializer() {
super(TypeFactory.defaultInstance().constructCollectionType(Set.class, String.class));
}
ACCEPT_SINGLE_VALUE_AS_ARRAY as suggested is a good option.
Maybe your actual problem is more complicated but if not you could also try #JsonCreator instead of custom deserializer. Like:
public class Example {
#JsonCreator
public Example(#JsonProperty("inputFoo") String foo,
#JsonProperty("inputBar") String bar,
#JsonProperty("inputBaz") String strBaz) {
this.foo = foo;
this.bar = bar;
this.baz = new HashSet<>();
baz.add(strBaz);
}
private String foo;
private String bar;
private Set<String> baz;
}
Just to show that in more general case you might avoid implementing custom deserializer with #JsonCreator also but still make some simple conversions.
Related
Say I have classes Foo
public class Foo {
private Bar bar;
}
and Bar
public class Bar {
private String fizz;
private String bang;
}
EDIT: For clarification I do not own Foo and Bar and cannot alter these classes.
If I want to serialize an empty object of type Foo, it's member, which is of type Bar, will be returned as null.
String json = objectMapper.writeValueAsString(new Foo()); // "{"bar" : null}"
Is there any way I can get the object mapper to serialize an empty Bar object without having to instantiate a new instance of Bar and then adding it to a new instance of Foo?
String json = objectMapper.writeValueAsString(new Foo()) // "{bar": {"fizz" : null, "bang" : null } }"
I was also required to produce such a structure for legacy client compatibility, here is my solution (depends on Spring Boot since uses #JsonComponent annotation)
Create "special object" that will be treated as empty
public class EmptyObject {
}
Create property in your model
#JsonProperty("data")
private EmptyObject data = new EmptyObject();
public EmptyObject getData() {
return data;
}
Create serializer that will process empty object above
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.sevensenders.datahub.api.service.response.model.EmptyObject;
import org.springframework.boot.jackson.JsonComponent;
import java.io.IOException;
#JsonComponent
public class EmptyObjectSerializer extends StdSerializer<EmptyObject> {
public EmptyObjectSerializer() {
this(null);
}
public EmptyObjectSerializer(Class<EmptyObject> t) {
super(t);
}
#Override
public void serialize(EmptyObject value, JsonGenerator gen, SerializerProvider provider) throws IOException {
// to maintain AF compatible format it is required to write {} instead of null
gen.writeStartObject();
gen.writeEndObject();
}
}
Output:
{
...
"data": {}
}
You could create a custom serializer for serializing Foo objects. Then in your custom FooSerializer implementation, you could check for a null bar value and serialize it as a default Bar instance. See https://spin.atomicobject.com/2016/07/01/custom-serializer-jackson/ or http://www.baeldung.com/jackson-custom-serialization for some examples of how to create custom serializers.
It's a bit unrelated to this, but if you define members as private on data class in Kotlin, then, Jackson serializer will produce empty json such as {}.
If you don't want to write your own serializer you can use this approach of declaring type of field as ObjectNode:
private ObjectNode data;
You can set/initialize it like this:
data = new ObjectNode(JsonNodeFactory.instance)
No. I don't see any way doing this. If you don't initialize your Bar, it'll be null inside the JSON.
Since you can't alter these classes, you can just check if the Bar inside the Foo is null and if it is, just initialize it and you'll get what you want.
Bar bar = foo.getBar();
if (bar == null) {
foo.setBar(new Bar());
}
String json = objectMapper.writeValueAsString(foo);
The json will be the following:
{
"bar" : {
"fizz" : null,
"bang" : null
}
}
Hope this helps.
I have a relatively complex object which contains a number of fields. I need to serialize one of the fields using a custom serializer, but need to emulate the #JsonUnwrapped functionality.
For simplicity's sake I'll cut this down to two fields:
public class MyClass
{
#JsonProperty("subject")
private final String subject;
#JsonSerialize(using=MySenderSerializer.class)
private final MailActor sender;
}
and my custom serializer class is as follows:
public class MySenderSerializer extends StdSerializer<MailActor>
{
public MySenderSerializer()
{
super(MailActor.class, true);
}
#Override
public void serialize(final MailActor value, final JsonGenerator gen, final SerializerProvider provider) throws IOException
{
gen.writeStringField("from_name", value.getName());
gen.writeStringField("from_email", value.getAddress());
}
}
All of this is fine, except that the output JSON looks like this:
{
...
"subject": "test subject",
"sender": {
"from_name": "test from",
"from_email": "test#test.com"
},
...
}
and I need to unwrap the sender field so that the JSON looks like this:
{
...
"subject": "test subject",
"from_name": "test from",
"from_email": "test#test.com",
...
}
If I was using standard serializers I could use the #JsonUnwrapped annotation to do this, but it doesn't appear to be compatible with custom serializers. How can I obtain the required JSON output without writing a custom serializer for the MyClass object?
I have had to look for an alternative to #JsonUnwrapped too, as it was causing me some unrelated issues with this question.
The solution I have implemented would apply to your case similarly, using #JsonAnyGetter. In your MyClass ignore the attribute that needs the special serialization and instead add it with the JsonAnyGetter:
public class MyClass
{
#JsonProperty("subject")
private final String subject;
#JsonIgnore
private final MailActor sender;
#JsonAnyGetter
public Map<String, Object> serializeActor() {
return sender.serializeToMap();
// Of course, here you could create an empty map
// and add the properties of many different classes.
}
}
Then in the MailActor class you implement that method which will return a map with the properties that you may want.
public Map<String, Object> serializeToMap() {
final Map<String, Object> properties = new ArrayMap<>();
properties.put("from_name", this.getName());
properties.put("from_email", this.getAddress());
return properties;
}
The problem of going this way is deserializing the object, since the #JsonAnySetter doesn't get the map with the JSON in a map the same way you give it with the example above. In your case it's even more difficult, as your class' attributes are final. You'd need to use a #JsonCreator which would have to create all your attributes. Something along the lines of:
#JsonCreator
public MyClass( Map< String, String > json ) {
if (json.containsKey("subject")) {
subject = json.get("subject");
} else {
subject = "";
}
String name, address;
if (json.containsKey("from_name")) {
name = json.get("from_name");
} else {
name = "";
}
if (json.containsKey("from_email")) {
address = json.get("from_email");
} else {
address = "";
}
sender = new MailActor(name, address);
}
It's been a while since you posted the question, hopefully this is helpful in some way to someone coming to look for alternatives.
Well, that is because you have designed it to be that way, when jackson maps an object it will map the inner properties as sub-properties of that object, if you want it to serialize those two fields as if they were members of MyClass instead of MailActor, then declare them as such.
This may point out that your object design may have some small flaws.
I would write a custome serializer for the MyClass object but still, in the long run is not a viable solution.
I have a web API where the user may (or may not) transfer an URL parameter like for example bird, dog etc.
I want this parameter to be mapped to an enum on the server side, something like:
#POST
#Path("/zoo")
public Response createNewAnimal(
#QueryParam("animal")
#DefaultValue("CAT") AnimalType type
) throws Exception
...
public enum AnimalType {
BIG_BIRD,
SMALL_CAT;
}
But it doesn't work!
While processing the web request, Enum.valueOf() is being called. And of course it fails, because the bird that user uses as URL parameter doesn't match the identifier in the Enum (AnimalType.BIG_BIRD).
There is no way to override to valueOf() method (it's static...) and setting constructor doesn't help (it's the opposite logical direction).
So maybe you know of a nice solution to this, instead of just using if...else...?
If you have an enum like:
public enum AnimalType {
BIG_BIRD,
SMALL_CAT,
MEDIUM_DOG;
}
then in order for JAX-RS to know what instance to return, your query parameter must be ?animal=BIG_BIRD, ?animal=SMALL_CAT or ?animal=MEDIUM_DOG.
The value of the query parameter is fed to the valueOf static method of the enum to get an instance. Off course, if you send something else like bird it won't match anything and it won't work because #QueryParam expects this:
The type T of the annotated parameter, field or property must either:
- Be a primitive type
- Have a constructor that accepts a single String argument
- Have a static method named valueOf that accepts a single String argument (see, for example, Integer.valueOf(String))
- Be List, Set or SortedSet, where T satisfies 2 or 3 above. The resulting collection is read-only.
The same applies for the #DefaultValue also. You have to specify #DefaultValue("BIG_BIRD"), #DefaultValue("SMALL_CAT") or #DefaultValue("MEDIUM_DOG"):
#POST
#Path("/zoo")
public Response createNewAnimal(
#QueryParam("animal")
#DefaultValue("SMALL_CAT") AnimalType type) {
// ...
return Response.ok().entity(type.toString()).build();
}
If you don't want to expose the names on your Java types to the client, you can transform the proper query string value into an enum instance. An if ... else ... if is a very simple way to achieve this but if you want something fancier you could create a wrapper like this:
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class AnimalTypeWrapper {
private static final Map<String, AnimalType> MAPPER = Collections
.unmodifiableMap(new HashMap<String, AnimalType>() {
{
put("bird", AnimalType.BIG_BIRD);
put("dog", AnimalType.MEDIUM_DOG);
put("cat", AnimalType.SMALL_CAT);
}
});
private AnimalType type;
public static AnimalTypeWrapper valueOf(String value) {
AnimalType type = AnimalTypeWrapper.MAPPER.get(value.toLowerCase());
if (type == null) {
// if nothing found just set the desired default value
type = AnimalType.SMALL_CAT;
}
return new AnimalTypeWrapper(type);
}
private AnimalTypeWrapper(AnimalType type) {
this.type = type;
}
public AnimalType getType() {
return this.type;
}
}
and in your resource method have:
#POST
#Path("/zoo")
public Response createNewAnimal(
#QueryParam("animal")
AnimalTypeWrapper typeWrapper) {
// ...
AnimalType type = typeWrapper.getType();
return Response.ok().entity(type.toString()).build();
}
The behavior of enum (de)serialization with JAX-RS and Jackson 2.5.0 tripped me up for a while, so I'm going to try and elaborate on #Bogdan's answer, and show what worked for me.
The thing that wasn't clear to me was that #QueryParam and #FormParam don't follow standard procedure to deserialize enums - so if you're trying to accept an enum as a query param, like so:
#GET
public Response getAnimals(#QueryParam("animalType") AnimalType animalType) {}
...then the only way your animalType argument will be deserialized properly is if your type T (in our case, AnimalType) satisfies one of the following properties:
Be a primitive type.
Have a constructor that accepts a single String argument.
Have a static method named valueOf or fromString that accepts a
single String argument (see, for example, Integer.valueOf(String)).
Have a registered implementation of ParamConverterProvider JAX-RS
extension SPI that returns a ParamConverter instance capable of a
"from string" conversion for the type.
Be List<T>, Set<T> or SortedSet<T>, where T satisfies 2, 3 or 4
above. The resulting collection is read-only.
...per the Java EE 7 #QueryParam docs.
This means that, in addition to implementing custom (de)serialization for your normal use cases, you will also need to satisfy one of the five conditions listed above. Then, and only then!, you'll be able to handle the #QueryParam deserialization case.
The solution...
A simple way that I found to handle both the normal (de)serialization cases and the #QueryParam case is to a) satisfy condition #3 by implementing fromString(), and b) implement a mapper class that contains both a serializer and a deserializer, the latter of which will rely on fromString(), so we have consistent deserialization:
// Our example enum class...
#JsonSerialize(using = AnimalTypeMapper.Serializer.class)
#JsonDeserialize(using = AnimalTypeMapper.Deserializer.class)
public enum AnimalType {
CAT("cat"),
BIRD("bird"),
DOG("doggy");
private final String name;
AnimalType(String name) {
this.name = name;
}
private static Map<String, AnimalType> VALUES_BY_NAME = Arrays.stream(values())
.collect(Collectors.toMap(AnimalType::getName, Function.identity()));
public String getName() {
return name;
}
// Implementing this method allows us to accept AnimalType's as #QueryParam
// and #FormParam arguments. It's also used in our custom deserializer.
public static AnimalType fromString(String name) {
return VALUES_BY_NAME.getOrDefault(name, DOG);
}
}
// Our custom (de)serialization class...
public class AnimalTypeMapper {
public static class Serializer extends JsonSerializer<AnimalType> {
#Override
public void serialize(AnimalType animalType, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString(animalType.getName());
}
}
public static class Deserializer extends JsonDeserializer<AnimalType> {
#Override
public AnimalType deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
return AnimalType.fromString(jsonParser.getValueAsString());
}
}
}
Hopefully someone out there will find this helpful. I spent way too much time spinning my wheels on this!
This is my firs titme dealing with Type Maps and everytime i try to map the node to my Actual Type Object which has a custom property key as FooType with a Set<Integer> values. Here is how my Object looks like
public class Foo {
private String some;
Map<FooTypes,Set<Integer>> foos;
public Map<FooTypes, Set<Integer>> getFoos() {
return foos;
}
public void setFoos(Map<FooTypes, Set<Integer>> map) {
this.foos = map;
}
public String getSome() {
return some;
}
public void setSome(String some) {
this.some = some;
}
}
public class FooTypes {
private String name;
private String id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Now everytime i try to use the mapper to read the value :-
List <Foo> response = mapper.readValue("/home/foo/foo.json",List.class);
I get an error stating that :-
Can not find a (Map) Key deserializer for type [simple type, class cruft.FooTypes]
Can someone tell me on how can i fix this problem ? Thank you.
Json Output:-
{"foos":{"FooTypes [id=1, name=Test Foo]":[1,2,3]},"some":hello},{"foos":{"FooTypes [id=2, name=Another foo]":[5,6,7]}}
It's a bit hard to help you since we don't have the Json structure you want to deserialize, but the problem here is Jackson has no idea how to deserialize your class when it is used as a map key. All the information Jackson as is a simple String, and your class provide no way of creating it with only a string.
There's 3 way to achieve this:
Add a single string argument constructor to your FooType class
Add a single string argument factory method (static) and annotate it with #JsonCreator
Use a custom KeyDeserializer and annotate the foos field with #JsonDeserialize(keyUsing=YourDeserializer.class)
IMHO the static factory method is the cleaner way.
Since you have non-primitive type as a map key (FooTypes) you'll need to write your own custom deserializer as described in Jackson wiki, because Jackson can't simply convert string value "FooTypes [id=1, name=Test Foo]" (which seems to be a result of FooTypes.toString()) into FooTypes instance.
On my opinion, serializing map with non-string keys is not really a good practice. JSON notation doesn't support anything but strings for map keys (as specified here). A better approach, i think, would be to reorganize your data structure before serialization and after deserialization.
I'm quite new to the Jackson library (version 1.9). I'm using it only since a couple of weeks, and I find it very flexible and time-saving when it's about serializing and deserializing objects in Java.
I'm experiencing troubles, though, into deserializing "flat" JSONs to a class which is a composition of another, when both are meant to be immutable.
My situation is pretty much the following:
class Foo {
private final String var1;
Foo(String var1) {
this.var1 = var1;
}
// getters omitted
}
class A {
private final Foo foo;
private final String var2;
A(/* #JsonUnwrapped doesn't work here */ Foo foo, String var2) {
this.foo = foo;
this.var2 = var2;
}
#JsonUnwrapped
Foo getFoo() {
return foo;
}
String getVar2() {
return var2;
}
}
class B extends Foo {
private final String var2;
B(String var1, String var2) {
super(var1);
this.var2 = var2;
}
// getters omitted
}
And the JSON to deserialize is something like this:
{ "var1" : "some_value", "var2" : "some_other_value" }
The question is: is there an annotation-based way (so, without the need of using a custom deserializer) to tell Jackson to compose the given JSON to a 'A' instance?
I've tried using the #JsonUnwrapped attribute for the Foo argument in class 'A' constructor, but it's not supported in multi-argument constructor as it would need a JsonProperty to work (which doesn't make sense, because there is actually no single property for those items).
Serialization, instead, works perfectly using this pattern.
It would also work with a non-immutable class by using separate setters, but I'd like to know if there's a way to do the same by only using the constructors (or a builder, which would make sense as in reality the fields are much more than the one in the example).
The very same method obviously works with class 'B' which inherits from 'Foo'.
Thanks in advance.
Note that Jackson's deserialization processing doesn't necessarily respect the immutability of final fields. So, a simple approach would be to just provide no-argument (private) constructors for Jackson to use.
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
// {"var1":"some_value", "var2":"some_other_value"}
String jsonInput = "{\"var1\":\"some_value\", \"var2\":\"some_other_value\"}";
ObjectMapper mapper = new ObjectMapper().setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
A a = new A(new Foo("some_value"), "some_other_value");
System.out.println(mapper.writeValueAsString(a));
// output: {"var1":"some_value","var2":"some_other_value"}
A aCopy = mapper.readValue(jsonInput, A.class);
System.out.println(mapper.writeValueAsString(aCopy));
// output: {"var1":"some_value","var2":"some_other_value"}
}
}
class Foo
{
private final String var1;
Foo(String var1) {this.var1 = var1;}
private Foo() {this.var1 = null;}
}
class A
{
#JsonUnwrapped
private final Foo foo;
private final String var2;
A(Foo foo, String var2)
{
this.foo = foo;
this.var2 = var2;
}
private A()
{
this.foo = null;
this.var2 = null;
}
}
If you really don't want to provide such (extra) constructors, then it would be nice if a similar solution could be devised using #JsonCreator, but I wasn't able to get such a thing to work. So, I recommend logging an enhancement request at https://github.com/FasterXML/jackson-core/issues, maybe to better support annotating a #JsonCreator argument with both #JsonUnwrapped and #JsonProperty.
Unfortunately there are certain combinations of features that may not be possible to implement properly; and this may be one of those (I am not 100% sure: feel free to file a Bug/RFE for Jackson github issues or Jira). This is because the way #JsonUnwrapped and #JsonCreator both require potential reordering of data; and also because the order of creating actual instance complicates things.
So while conceptually this should be possible, there may be implementation difficulties.
As to Jackson 2.0: I would definitely try it over 1.9 because some parts of #JsonUnwrapped handling have been improved; and any fixes/improvements will be added there. 1.9 branch will get bugfixes backported wherever possible, but no new features will be added.