jackson Include.NOT_NULL on field not working - java

Using JsonInclude(content=Include.NON_NULL) (or actually any other JsonInclude options) seems to have no effect on serialization of my objects. Consider this test:
public class JsonTest {
#Data
#JsonInclude(content=Include.ALWAYS)
static class TestObj {
String fieldVisible = "a";
String fieldVisibleNull = null;
#JsonInclude(content=Include.NON_NULL)
String fieldInvisible = null;
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writerFor(TestObj.class).writeValueAsString(new TestObj());
if (json.contains("fieldInvisible")) {
System.err.println(json);
throw new RuntimeException("null field written even though it shouldn't be");
}
}
}
The result is:
{"fieldVisible":"a","fieldVisibleNull":null,"fieldInvisible":null}
Exception in thread "main" java.lang.RuntimeException: null field written even though it shouldn't be
at my.package.JsonTest.main(JsonTest.java:57)
What am I missing? What don't I understand?

Using
#JsonInclude(Include.NON_NULL)
Instead of
#JsonInclude(content=Include.NON_NULL)
Works. Thanks #Aaron.

Related

xmlMapper allow to use any root element during deserialization

I have such code
public class Xml {
public static void main(String[] args) throws JsonProcessingException {
String xmlString = "<password><plainPassword>12345</plainPassword></password>";
XmlMapper xmlMapper = new XmlMapper();
PlainPassword plainPassword = xmlMapper.readValue(xmlString, PlainPassword.class);
System.out.println(plainPassword.getPlainPassword());
}
#JacksonXmlRootElement(localName = "password")
public static class PlainPassword {
public String getPlainPassword() {
return this.plainPassword;
}
public void setPlainPassword(String plainPassword) {
this.plainPassword = plainPassword;
}
private String plainPassword;
}
}
It works fine, but in xmlString I can use any root tag name and my code still will work.
For example String xmlString = "<x><plainPassword>12345</plainPassword></x>"; where I use x as root element also works.
But is it possible to say xmlMapper that it could correctly deserialize only strings with "password" root element?
Unfortunately, the behavior you described is the one supported by Jackson as indicated in this Github open issue.
With JSON content and ObjectMapper you can enable the UNWRAP_ROOT_VALUE deserialization feature, and maybe it could be of help for this purpose, although I am not quite sure if this feature is or not correctly supported by XmlMapper.
One possible solution could be the implementation of a custom deserializer.
Given your PlainPassword class:
#JacksonXmlRootElement(localName = "password")
public class PlainPassword {
public String getPlainPassword() {
return this.plainPassword;
}
public void setPlainPassword(String plainPassword) {
this.plainPassword = plainPassword;
}
private String plainPassword;
}
Consider the following main method:
public static void main(String[] args) throws JsonProcessingException {
String xmlString = "<x><plainPassword>12345</plainPassword></x>";
XmlMapper xmlMapper = new XmlMapper();
xmlMapper.registerModule(new SimpleModule().setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
Class<?> beanClass = beanDesc.getBeanClass();
JacksonXmlRootElement annotation = beanClass.getAnnotation(JacksonXmlRootElement.class);
String requiredLocalName = null;
if (annotation != null) {
requiredLocalName = annotation.localName();
}
if (requiredLocalName != null) {
return new EnforceXmlElementNameDeserializer<>(deserializer, beanDesc.getBeanClass(), requiredLocalName);
}
return deserializer;
}
}));
PlainPassword plainPassword = xmlMapper.readValue(xmlString, PlainPassword.class);
System.out.println(plainPassword.getPlainPassword());
}
Where the custom deserializer looks like:
public class EnforceXmlElementNameDeserializer<T> extends StdDeserializer<T> implements ResolvableDeserializer {
private final JsonDeserializer<?> defaultDeserializer;
private final String requiredLocalName;
public EnforceXmlElementNameDeserializer(JsonDeserializer<?> defaultDeserializer, Class<?> beanClass, String requiredLocalName) {
super(beanClass);
this.defaultDeserializer = defaultDeserializer;
this.requiredLocalName = requiredLocalName;
}
#Override
public T deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
String rootName = ((FromXmlParser)p).getStaxReader().getLocalName();
if (!this.requiredLocalName.equals(rootName)) {
throw new IllegalArgumentException(
String.format("Root name '%s' does not match required element name '%s'", rootName, this.requiredLocalName)
);
}
#SuppressWarnings("unchecked")
T itemObj = (T) defaultDeserializer.deserialize(p, ctxt);
return itemObj;
}
#Override public void resolve(DeserializationContext ctxt) throws JsonMappingException {
((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
}
}
You have to implement ResolvableDeserializer when modifying BeanDeserializer, otherwise deserializing throws exception.
The code is based in this excellent SO answer.
The test should raise IllegalArgumentException with the corresponding message:
Root name 'x' does not match required element name 'password'
Please, modify the exception type as appropriate.
If, instead, you use:
String xmlString = "<password><plainPassword>12345</plainPassword></password>";
in your main method, it should run without problem.
You can change your name of root class to everything, for example : #JacksonXmlRootElement(localName = "xyz") and it works.
Based on Java documentation JacksonXmlRootElement is used to define name of root element used for the root-level object when serialized (not for deserialized mapping), which normally uses name of the type (class).
I'd approach this differently. Grab an XPath implementation, select all nodes that match //plainPassword, then get a list of contents of each node.
If you need to, you can also get the name of the parent node; when in context of a found node use .. to get the parent node.
Check XPath examples and try it out for yourself. Note that your code may differ depending on language and XPath implementation.

Custom BigInteger JSON serializer

I am trying to implement a custom JSON serializer class to display object BigInteger values as string in the JSON response.
I have implememented a custom serializer class
public class CustomCounterSerializer extends StdSerializer<BigInteger> {
private static final long serialVersionUID = 5440920327083673598L;
public CustomCounterSerializer() {
this(BigInteger.class);
}
public CustomCounterSerializer(Class<BigInteger> vc) {
super(vc);
}
#Override
public void serialize(BigInteger value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException {
BigInteger valueJson = value==null ? BigInteger.valueOf(0) : value;
jsonGenerator.writeString(valueJson.toString());
}
}
The problem I have is that I want to handle the null object values using the overridden method and pass 0 to the JSON string and not null.
I have written a JUnit test for this:
public class CustomCounterSerializerTest {
private ObjectMapper objectMapper = new ObjectMapper();
#After
public void tearDown() {
objectMapper = null;
}
#Test
public void testCustomSerializerWithNullValues() throws JsonParseException, JsonMappingException, IOException {
MyObject obj = new MyObject();
obj.setNonNullValue(BigInteger.valueOf(1);
String obj_ = objectMapper.writeValueAsString(obj);
assertThat(obj_).isNotNull();
assertTrue(obj_.contains("\"nonNullValue\":\"" + BigInteger.valueOf(1).toString() + "\",\"nullValue\":\""+ BigInteger.valueOf(0).toString() +"\""));
}
}
It fails as it contains null and not nullValue:"0".
But the null value of the object always goes to no args constructor and even like this(BigInteger.class) does not use my method and prints null.
You need to tell Jackson that it should use your serializer
even for null values. You do this by using #JsonSerializer
also with the nullsUsing parameter.
In your MyObject class it would look like this:
#JsonSerialize(using = CustomCounterSerializer.class,
nullsUsing = CustomCounterSerializer.class)
private BigInteger nullValue;

CustomDeserializer has no default (no arg) constructor

I am consuming a REST Api with RestTemplate. The response I'm getting from the API has lots of nested objects. Here's a little snippet as an example:
"formularios": [
{
"form_data_id": "123006",
"form_data": {
"form_data_id": "123006",
"form_id": "111",
"efs": {
"1": {},
"2": "{\"t\":\"c\",\"st\":\"m\",\"v\":[{\"id\":\"3675\",\"l\":\"a) Just an example\",\"v\":\"1\"},{\"id\":\"3676\",\"l\":\"b) Another example.\",\"v\":\"2\"}]}"
}
}
The problem I'm having is that most of the times the "1" actually has content, just like "2", and the jackson just parses it as a String on the object "efs". But sometimes, just like in the code snippet, the API sends it empty, and jackson takes it as an Object, which gives me an error that says something about START_OBJECT (can't remember the exact error, but it's not important for this question).
So I decided to make a custom deserializer so when jackson reads "1", it ignores the empty object and just parses it as a null string.
Here's my custom deserializer:
public class CustomDeserializer extends StdDeserializer<Efs> {
public CustomDeserializer(Class<Efs> t) {
super(t);
}
#Override
public Efs deserialize(JsonParser jp, DeserializationContext dc)
throws IOException, JsonProcessingException {
String string1 = null;
String string2 = null;
JsonToken currentToken = null;
while ((currentToken = jp.nextValue()) != null) {
if (currentToken.equals(JsonToken.VALUE_STRING)) {
if (jp.getCurrentName().equals("1")) {
string1 = jp.getValueAsString();
} else {
string2 = jp.getValueAsString();
}
} else {
if (jp.getCurrentName().equals("2")) {
string2 = jp.getValueAsString();
}
}
}
return new Efs(string1, string2);
}
}
And this is the way I'm using it when receiving the response from the API:
ObjectMapper mapper = new ObjectMapper();
SimpleModule mod = new SimpleModule("EfsModule");
mod.addDeserializer(Efs.class, new CustomDeserializer(Efs.class));
mapper.registerModule(mod);
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
MappingJackson2HttpMessageConverter jsonMessageConverter = new MappingJackson2HttpMessageConverter();
jsonMessageConverter.setObjectMapper(mapper);
messageConverters.add(jsonMessageConverter);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(messageConverters);
I'm getting the error:
CustomDeserializer has no default (no arg) constructor
But I don't know exactly what I'm doing wrong nor how to solve it. Thanks for the help and apologies for the long question, I wanted to give as much context as possible.
There is also one trap that users can fall into (like my self). If you declare deserializer as a inner class (not a static nested class) like:
#JsonDeserialize(using = DomainObjectDeserializer.class)
public class DomainObject {
private String key;
public class DomainObjectDeserializer extends StdDeserializer<DomainObject> {
public DomainObjectDeserializer() {
super(DomainObject.class);
}
#Override
public DomainObject deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
// code
}
}
}
Jackson uses the Class#getDeclaredConstructor() with no argument (method accepts vararg) which means: give me a default (no argument) constructor. Code above will throw exception when Jackson tries to create DomainObjectDeserializer because javac generates the constructor that accepts enclosing class reference. Technically speaking DomainObjectDeserializer does not have a default constructor.
For a curiosity sake you can execute DomainObjectDeserializer.class.getDeclaredConstructors() and ensure that method does return single element array containing constructor definition with enclosing class reference.
The DomainObjectDeserializer should be declared as a static class.
Here is a good answer to read in more details.
It is required that you have a default constructor without arguments.
What you can do is create one (or replace the other one if you don't really need it):
public class CustomDeserializer extends StdDeserializer<Efs> {
public CustomDeserializer() {
super(Efs.class);
}
...
}

How to wrap a List as top level element in JSON generated by Jackson

I am running into a problem where I am trying to include a List as the root node, but I can't seem to be able to get this. Let me explain. Let's say we have a class "TestClass"
class TestClass{
String propertyA;
}
Now, in some utility method this is what I do
String utilityMethod(){
List<TestClass> list = someService.getList();
new ObjectMapper().writeValueAsString(list);
}
The output I am trying to get in JSON is
{"ListOfTestClasses":[{"propertyA":"propertyAValue"},{"propertyA":"someOtherPropertyValue"}]}
I have tried to use
objMapper.getSerializationConfig().set(Feature.WRAP_ROOT_VALUE, true);
But, I still don't seem to get it right.
Right now, I am just creating a Map < String,TestClass > and I write that to achieve what I am trying to do, which works but clearly this is a hack. Could someone please help me with a more elegant solution? Thanks
Unfortunately, even with the WRAP_ROOT_VALUE feature enabled you still need extra logic to control the root name generated when serializing a Java collection (see this answer for details why). Which leaves you with the options of:
using a holder class to define the root name
using a map.
using a custom ObjectWriter
Here is some code illustrating the three different options:
public class TestClass {
private String propertyA;
// constructor/getters/setters
}
public class TestClassListHolder {
#JsonProperty("ListOfTestClasses")
private List<TestClass> data;
// constructor/getters/setters
}
public class TestHarness {
protected List<TestClass> getTestList() {
return Arrays.asList(new TestClass("propertyAValue"), new TestClass(
"someOtherPropertyValue"));
}
#Test
public void testSerializeTestClassListDirectly() throws Exception {
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
System.out.println(mapper.writeValueAsString(getTestList()));
}
#Test
public void testSerializeTestClassListViaMap() throws Exception {
final ObjectMapper mapper = new ObjectMapper();
final Map<String, List<TestClass>> dataMap = new HashMap<String, List<TestClass>>(
4);
dataMap.put("ListOfTestClasses", getTestList());
System.out.println(mapper.writeValueAsString(dataMap));
}
#Test
public void testSerializeTestClassListViaHolder() throws Exception {
final ObjectMapper mapper = new ObjectMapper();
final TestClassListHolder holder = new TestClassListHolder();
holder.setData(getTestList());
System.out.println(mapper.writeValueAsString(holder));
}
#Test
public void testSerializeTestClassListViaWriter() throws Exception {
final ObjectMapper mapper = new ObjectMapper();
final ObjectWriter writer = mapper.writer().withRootName(
"ListOfTestClasses");
System.out.println(writer.writeValueAsString(getTestList()));
}
}
Output:
{"ArrayList":[{"propertyA":"propertyAValue"},{"propertyA":"someOtherPropertyValue"}]}
{"ListOfTestClasses":[{"propertyA":"propertyAValue"},{"propertyA":"someOtherPropertyValue"}]}
{"ListOfTestClasses":[{"propertyA":"propertyAValue"},{"propertyA":"someOtherPropertyValue"}]}
{"ListOfTestClasses":[{"propertyA":"propertyAValue"},{"propertyA":"someOtherPropertyValue"}]}
Using an ObjectWriter is very convenient - just bare in mind that all top level objects serialized with it will have the same root name. If thats not desirable then use a map or holder class instead.
I'd expect the basic idea to be something like:
class UtilityClass {
List listOfTestClasses;
UtilityClass(List tests) {
this.listOfTestClasses = tests;
}
}
String utilityMethod(){
List<TestClass> list = someService.getList();
UtilityClass wrapper = new UtilityClass(list);
new ObjectMapper().writeValueAsString(wrapper);
}

How can I include raw JSON in an object using Jackson?

I am trying to include raw JSON inside a Java object when the object is (de)serialized using Jackson. In order to test this functionality, I wrote the following test:
public static class Pojo {
public String foo;
#JsonRawValue
public String bar;
}
#Test
public void test() throws JsonGenerationException, JsonMappingException, IOException {
String foo = "one";
String bar = "{\"A\":false}";
Pojo pojo = new Pojo();
pojo.foo = foo;
pojo.bar = bar;
String json = "{\"foo\":\"" + foo + "\",\"bar\":" + bar + "}";
ObjectMapper objectMapper = new ObjectMapper();
String output = objectMapper.writeValueAsString(pojo);
System.out.println(output);
assertEquals(json, output);
Pojo deserialized = objectMapper.readValue(output, Pojo.class);
assertEquals(foo, deserialized.foo);
assertEquals(bar, deserialized.bar);
}
The code outputs the following line:
{"foo":"one","bar":{"A":false}}
The JSON is exactly how I want things to look. Unfortunately, the code fails with an exception when attempting to read the JSON back in to the object. Here is the exception:
org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: java.io.StringReader#d70d7a; line: 1, column: 13] (through reference chain: com.tnal.prism.cobalt.gather.testing.Pojo["bar"])
Why does Jackson function just fine in one direction but fail when going the other direction? It seems like it should be able to take its own output as input again. I know what I'm trying to do is unorthodox (the general advice is to create an inner object for bar that has a property named A), but I don't want to interact with this JSON at all. My code is acting as a pass-through for this code -- I want to take in this JSON and send it back out again without touching a thing, because when the JSON changes I don't want my code to need modifications.
Thanks for the advice.
EDIT: Made Pojo a static class, which was causing a different error.
#JsonRawValue is intended for serialization-side only, since the reverse direction is a bit trickier to handle. In effect it was added to allow injecting pre-encoded content.
I guess it would be possible to add support for reverse, although that would be quite awkward: content will have to be parsed, and then re-written back to "raw" form, which may or may not be the same (since character quoting may differ).
This for general case. But perhaps it would make sense for some subset of problems.
But I think a work-around for your specific case would be to specify type as 'java.lang.Object', since this should work ok: for serialization, String will be output as is, and for deserialization, it will be deserialized as a Map. Actually you might want to have separate getter/setter if so; getter would return String for serialization (and needs #JsonRawValue); and setter would take either Map or Object. You could re-encode it to a String if that makes sense.
Following #StaxMan answer, I've made the following works like a charm:
public class Pojo {
Object json;
#JsonRawValue
public String getJson() {
// default raw value: null or "[]"
return json == null ? null : json.toString();
}
public void setJson(JsonNode node) {
this.json = node;
}
}
And, to be faithful to the initial question, here is the working test:
public class PojoTest {
ObjectMapper mapper = new ObjectMapper();
#Test
public void test() throws IOException {
Pojo pojo = new Pojo("{\"foo\":18}");
String output = mapper.writeValueAsString(pojo);
assertThat(output).isEqualTo("{\"json\":{\"foo\":18}}");
Pojo deserialized = mapper.readValue(output, Pojo.class);
assertThat(deserialized.json.toString()).isEqualTo("{\"foo\":18}");
// deserialized.json == {"foo":18}
}
}
I was able to do this with a custom deserializer (cut and pasted from here)
package etc;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
/**
* Keeps json value as json, does not try to deserialize it
* #author roytruelove
*
*/
public class KeepAsJsonDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
TreeNode tree = jp.getCodec().readTree(jp);
return tree.toString();
}
}
Use it by annotating the desired member like this:
#JsonDeserialize(using = KeepAsJsonDeserializer.class)
private String value;
#JsonSetter may help. See my sample ('data' is supposed to contain unparsed JSON):
class Purchase
{
String data;
#JsonProperty("signature")
String signature;
#JsonSetter("data")
void setData(JsonNode data)
{
this.data = data.toString();
}
}
This is a problem with your inner classes. The Pojo class is a non-static inner class of your test class, and Jackson cannot instantiate that class. So it can serialize, but not deserialize.
Redefine your class like this:
public static class Pojo {
public String foo;
#JsonRawValue
public String bar;
}
Note the addition of static
Adding to Roy Truelove's great answer, this is how to inject the custom deserialiser in response to appearance of #JsonRawValue:
import com.fasterxml.jackson.databind.Module;
#Component
public class ModuleImpl extends Module {
#Override
public void setupModule(SetupContext context) {
context.addBeanDeserializerModifier(new BeanDeserializerModifierImpl());
}
}
import java.util.Iterator;
import com.fasterxml.jackson.annotation.JsonRawValue;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
public class BeanDeserializerModifierImpl extends BeanDeserializerModifier {
#Override
public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
Iterator<SettableBeanProperty> it = builder.getProperties();
while (it.hasNext()) {
SettableBeanProperty p = it.next();
if (p.getAnnotation(JsonRawValue.class) != null) {
builder.addOrReplaceProperty(p.withValueDeserializer(KeepAsJsonDeserialzier.INSTANCE), true);
}
}
return builder;
}
}
This easy solution worked for me:
public class MyObject {
private Object rawJsonValue;
public Object getRawJsonValue() {
return rawJsonValue;
}
public void setRawJsonValue(Object rawJsonValue) {
this.rawJsonValue = rawJsonValue;
}
}
So I was able to store raw value of JSON in rawJsonValue variable and then it was no problem to deserialize it (as object) with other fields back to JSON and send via my REST. Using #JsonRawValue didnt helped me because stored JSON was deserialized as String, not as object, and that was not what I wanted.
This even works in a JPA entity:
private String json;
#JsonRawValue
public String getJson() {
return json;
}
public void setJson(final String json) {
this.json = json;
}
#JsonProperty(value = "json")
public void setJsonRaw(JsonNode jsonNode) {
// this leads to non-standard json, see discussion:
// setJson(jsonNode.toString());
StringWriter stringWriter = new StringWriter();
ObjectMapper objectMapper = new ObjectMapper();
JsonGenerator generator =
new JsonFactory(objectMapper).createGenerator(stringWriter);
generator.writeTree(n);
setJson(stringWriter.toString());
}
Ideally the ObjectMapper and even JsonFactory are from the context and are configured so as to handle your JSON correctly (standard or with non-standard values like 'Infinity' floats for example).
Here is a full working example of how to use Jackson modules to make #JsonRawValue work both ways (serialization and deserialization):
public class JsonRawValueDeserializerModule extends SimpleModule {
public JsonRawValueDeserializerModule() {
setDeserializerModifier(new JsonRawValueDeserializerModifier());
}
private static class JsonRawValueDeserializerModifier extends BeanDeserializerModifier {
#Override
public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
builder.getProperties().forEachRemaining(property -> {
if (property.getAnnotation(JsonRawValue.class) != null) {
builder.addOrReplaceProperty(property.withValueDeserializer(JsonRawValueDeserializer.INSTANCE), true);
}
});
return builder;
}
}
private static class JsonRawValueDeserializer extends JsonDeserializer<String> {
private static final JsonDeserializer<String> INSTANCE = new JsonRawValueDeserializer();
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return p.readValueAsTree().toString();
}
}
}
Then you can register the module after creating the ObjectMapper:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JsonRawValueDeserializerModule());
String json = "{\"foo\":\"one\",\"bar\":{\"A\":false}}";
Pojo deserialized = objectMapper.readValue(json, Pojo.class);
I had the exact same issue.
I found the solution in this post :
Parse JSON tree to plain class using Jackson or its alternatives
Check out the last answer.
By defining a custom setter for the property that takes a JsonNode as parameter and calls the toString method on the jsonNode to set the String property, it all works out.
Using an object works fine both ways... This method has a bit of overhead deserializing the raw value in two times.
ObjectMapper mapper = new ObjectMapper();
RawJsonValue value = new RawJsonValue();
value.setRawValue(new RawHello(){{this.data = "universe...";}});
String json = mapper.writeValueAsString(value);
System.out.println(json);
RawJsonValue result = mapper.readValue(json, RawJsonValue.class);
json = mapper.writeValueAsString(result.getRawValue());
System.out.println(json);
RawHello hello = mapper.readValue(json, RawHello.class);
System.out.println(hello.data);
RawHello.java
public class RawHello {
public String data;
}
RawJsonValue.java
public class RawJsonValue {
private Object rawValue;
public Object getRawValue() {
return rawValue;
}
public void setRawValue(Object value) {
this.rawValue = value;
}
}
I had a similar problem, but using a list with a lot of JSON itens (List<String>).
public class Errors {
private Integer status;
private List<String> jsons;
}
I managed the serialization using the #JsonRawValue annotation. But for deserialization I had to create a custom deserializer based on Roy's suggestion.
public class Errors {
private Integer status;
#JsonRawValue
#JsonDeserialize(using = JsonListPassThroughDeserialzier.class)
private List<String> jsons;
}
Below you can see my "List" deserializer.
public class JsonListPassThroughDeserializer extends JsonDeserializer<List<String>> {
#Override
public List<String> deserialize(JsonParser jp, DeserializationContext cxt) throws IOException, JsonProcessingException {
if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
final List<String> list = new ArrayList<>();
while (jp.nextToken() != JsonToken.END_ARRAY) {
list.add(jp.getCodec().readTree(jp).toString());
}
return list;
}
throw cxt.instantiationException(List.class, "Expected Json list");
}
}

Categories

Resources