I'm migrating an existing communication layer to use REST with JSON. The previous framework did not require a default constructor of the transferring POJOs.
I can not make changes on the model classes being transferred, so annotations are not an option. Also Mixins are not an option as well.
It should be a central configuration.
This is the current configuration & test code:
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
mapper.setDefaultPropertyInclusion(Include.NON_NULL);
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
mapper.setVisibility(PropertyAccessor.CREATOR, Visibility.ANY);
mapper.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false);
PrivateObject object = new PrivateObject(1234, "test");
String jsonString = mapper.writeValueAsString(object);
PrivateObject result = mapper.readValue(jsonString, PrivateObject.class);
POJO example:
public class PrivateObject {
private int id;
private String name;
public PrivateObject(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
}
JSON example:
{"id":1234,"name":"test"}
Resulting exception:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.softmodeler.common.pojo.PrivateObject` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{"id":1234,"name":"test"}"; line: 1, column: 2]
It can be done without annotations or mixins if you use Java 8 or later:
compile passing -parameters argument to javac
include module jackson-module-parameter-names in your dependencies and register it e.g.
mapper.registerModule(new ParameterNamesModule());
more details here
Another solution: using the ParanamerModule instead of the ParameterNamesModule, does not require to change the compiler arguments.
ParanamerModule:
Module that uses Paranamer library to auto-detect names of Creator
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new ParanamerModule());
ParameterNamesModule:
Jackson module that adds support for accessing parameter names; a feature added in JDK 8.
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new ParameterNamesModule());
Preconditions:
class Person must be compiled with Java 8 compliant compiler with option to store formal parameter names turned on (-parameters option). For more information about Java 8 API for accessing parameter names at runtime see this
Related
This is my class:
#Builder
#Value
public class A {
int id;
String name;
#NonNull String lastName;
}
The Lombok #Builder will add the all args constructor.
I need to deserialise a string into a POJO object.
I created the following Jackson mixin containing all three properties:
public abstract class AMixin {
public AMixin(#JsonProperty("name") String name,
#JsonProperty("id") int id,
#JsonProperty("lastName") String lastName) {
}
#JsonProperty("name")
abstract String getName();
#JsonProperty("id")
abstract int getId();
#JsonProperty("lastName")
abstract String getLastName();
}
I deserialise like this:
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(A.class, AMixin.class);
String ss = "{\"id\":1,\"name\":\"some name\",\"lastName\":\"some name\"}\n";
A c = mapper.readValue(ss, A.class);
}
but I get this error:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.bla.test.A` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{"id":1,"name":"some name","lastName":"some name"}
"; line: 1, column: 2]
I found the answer.
Add lombok.config file with content:
lombok.anyConstructor.addConstructorProperties=true
The issue here is that Jackson expects a no-argument constructor or some other configured way of creating the object.
As of Lombok v1.18.14, the #Jacksonized annotation can be added to the class with the #Builder annotation to automatically configure the builder to be used for Jackson deserialization.
#Jacksonized
#Builder
#Value
public class A {
int id;
String name;
#NonNull String lastName;
}
The Lombok documentation for #Jacksonized describes this annotation in more detail:
The #Jacksonized annotation is an add-on annotation for #Builder and #SuperBuilder. It automatically configures the generated builder class to be used by Jackson's deserialization. It only has an effect if present at a context where there is also a #Builder or a #SuperBuilder; a warning is emitted otherwise.
[...]
In particular, the annotation does the following:
Configure Jackson to use the builder for deserialization using #JsonDeserialize(builder=_Foobar_._Foobar_Builder[Impl].class)) on the class (where Foobar is the name of the annotated class, and Impl is added for #SuperBuilder). (An error is emitted if such an annotation already exists.)
Copy Jackson-related configuration annotations (like #JsonIgnoreProperties) from the class to the builder class. This is necessary so that Jackson recognizes them when using the builder.
Insert #JsonPOJOBuilder(withPrefix="") on the generated builder class to override Jackson's default prefix "with". If you configured a different prefix in lombok using setterPrefix, this value is used. If you changed the name of the build() method using using buildMethodName, this is also made known to Jackson.
For #SuperBuilder, make the builder implementation class package-private.
Note: This issue has nothing to do with the usage of a mixin, which can be verified by moving Jackson configuration from the mixin to the class itself and observing that the issue is still present.
Is there a way to provide the Jackson Deserializer with a default value from "the outside" (e.g. DI container) that it will use when deserializing an object, in this case tagRegistry?
#JsonCreator
public ExtractionRule(#JsonProperty("id") String id,
TagRegistry tagRegistry) {
this.id = id;
this.tagRegistry = tagRegistry;
}
I couldn't find an easy way to do this.
You could try #JacksonInject. Add this member to the ExtractionRule class:
#JacksonInject("tagRegistry")
private TagRegistry tagRegistry;
And inject the tagRegistry to the ObjectMapper before deserialization:
InjectableValues.Std injectableValues = new InjectableValues.Std();
injectableValues.addValue("tagRegistry", tagRegistry);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setInjectableValues(injectableValues);
I haven't tried using it in a constructor, not sure if that works.
You can find further examples here:
https://www.logicbig.com/tutorials/misc/jackson/jackson-inject.html
https://www.concretepage.com/jackson-api/jackson-jacksoninject-example#JacksonInject
public void serialize(IPerson person, OutputStream output) throws Exception {}
public void deserialize(InputStream input) throws Exception {}
I have an interface named IPerson, it has basic functionality.
I want to serialize the person object and be able to deserialize it from the deserialize method.
However, the scenario is this I cannot use Java's serializable interface as I can't be sure what implementation of IPerson will be used.
I have chosen to use Jackson's FasterXML, using ObjectMapper m = new ObjectMapper();
The problem I am having is that since IPerson is an interface I cannot serialize it directly using mapper.writerValue(output, person), I figured I must convert this object into something else, say a ByteArray then serialize it?
Also, this would be converting this something else into an object when deserializing? I have minimal experience with what exactly I should convert this object to and how to do so? Any ideas?
When using the default ObjectMapper you will have to make sure the objects you serialize are Java Beans. For non-bean classes you can set field visibility using m.setVisibility(PropertyAccessor.FIELD, Visibility.ANY); or annotate your class using #JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY).
For deserializing you will have to tell the ObjectMapper the target type. This can be done by providing a concrete implementation type to readValue or by storing the classname within the exported JSON. For this you can set m.enableDefaultTypingAsProperty(DefaultTyping.OBJECT_AND_NON_CONCRETE, "__class"); and annotate your objects with #JsonTypeInfo
ObjectMapper om = new ObjectMapper();
om.enableDefaultTypingAsProperty(DefaultTyping.OBJECT_AND_NON_CONCRETE, "__class");
IPerson value = new MyPerson();
String s = om.writeValueAsString(value);
IPerson d = om.readValue(s, IPerson.class);
using
interface IPerson {
void doSomething();
}
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "__class")
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
class MyPerson implements IPerson {
String name;
#Override
public void doSomething() {
}
}
Note that, you will need a default constructor for this to work or work with #JsonCreator and #JsonProperty (see jackson-annotations for details)
I have been searching all day for something that answers this, but I have not had a lot of luck thus far.
My question is straightforward: how do I deserialize an anonymous object correctly using Jackson.
private interface Interface1
{
int getValue();
}
public static void testAnonymousObject() throws IOException
{
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
Interface1 testObject = new Interface1()
{
private final int value = 5;
#Override
public int getValue()
{
return value;
}
};
String json = mapper.writeValueAsString(testObject);
System.out.println("JSON = " + json);
Interface1 received = (Interface1) mapper.readValue(json, Object.class);
System.out.println(received);
}
The output of this is: JSON = ["com.foo.test.JacksonTest$1",{"value":5}] before I get an exception:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize Class com.foo.test.JacksonTest$1 (of type local/anonymous) as a Bean.
EDIT Just to clarify, both Jackson and XStream are able to serialize the object. But only XStream seems to be able to deserialize the object back. So this scenario can be made to work.
As of the time I am writing this, it seems that Jackson does not serialize inner classes or anonymous classes correctly. Other packages such as XStream and Kryo, do however.
Because inner classes do not have a default zero argument constructor (they have a hidden reference to the outer/parent class) Jackson cannot instantiate them.
you can check this link
Problem is not just about it being an inner class (which may or may not be problematic, depending on whether implementation is static or non-static), but also in that no type information is included -- all Jackson sees is type Interface1. To enable reading it back it is necessary to either include type information ("polymorphic type handling"), or to specify mapping between abstract type and implementation class.
Given that you are using an anonymous inner class, you would be able to support this usage by enabled so-called "default typing" (see ObjectMapper javadocs for enableDefaultTyping() or such).
But you may also need to implement specific strategy, if you do not want to enable type inclusion for all non-final types.
To see whether type id is included you can enable default typing with one of default options and have a look at JSON being produced: there should be an additional type id ("#class" property when class name is used as id).
A ready-to-use code-snippet for a generic JSON-deserialization to a Java POJO with Jackson using nested classes:
static class MyJSON {
private Map<String, Object> content = new HashMap<>();
#JsonAnySetter
public void setContent(String key, Object value) {
content.put(key, value);
}
}
String json = "{\"City\":\"Prague\"}";
try {
MyPOJO myPOJO = objectMapper.readValue(json, MyPOJO.class);
String jsonAttVal = myPOJO.content.get("City").toString();
System.out.println(jsonAttVal);
} catch (IOException e) {
e.printStackTrace();
}
#JsonAnySetter ensures a generic JSON-parsing and population.
I have custom Entity that i want to put as Json to my view page
But when i serialize it in map using ObjectMapper from Jackson i receive String created from toString() method
#Test
public void test() throws JsonProcessingException {
Map<ProductEntity, Integer> map = new HashMap<ProductEntity, Integer>();
ProductEntity prod = new ProductEntity();
prod.setIdProduct(1);
map.put(prod, 1);
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(map));
}
Received: {"com.onlineshop.entity.ProductEntity#2":1}
where "com.onlineshop.entity.ProductEntity#2" is a String, not an object.
So how can i make it to be an Object?
I need exactly Map, not another type of Collection
You either need to annotate your ProductEntity object so Jackson knows how to serialize it or use a Mix In annotation if you are not able to modify the ProductEntity class. IIRC there are also global Jackson options you can set that tell it how to handle POJOs.
Since you didn't specify which version of Jackson you're using I can't link to the correct documents but there is a ton of information available on the Jackson sites on how to use annotations and mix ins.
Thanks to all for your answers.
I solved it by creating new DTO which contains :
private ProductEntity
private Integer
fields.