java/jackson - #JsonValue/#JsonCreator and null - java

I have a Value class which holds a value:
public class Value {
protected final Object value;
#JsonValue
public Object getValue() {
return value;
}
#JsonCreator
public Value(final Object value) {
this.value = value;
}
}
This Value class is embedded as a field (amongst other fields) in a data class:
class Data {
protected final Value value;
#JsonProperty("value")
public Value getValue() {
return value;
}
...
#JsonCreator
public Data(#JsonProperty("value") final Value value, ...) {
this.value = value;
....
}
}
When the input JSON has null for the value field of a data object (see below for example), Data.value is null. I would like to have Data.value set to new Value(null). In other words, the data object must hold a non-null value object, which holds the null.
{
"value" : null,
...
}
What is the easiest way to achieve this? I could ofcourse alter the constructor of Data, but I am wondering if Jackson could resolve this automatically.

You can write a custom de-serializer and override the getNullValue() method according to your requirements e.g.
public final class InstantiateOnNullDeserializer
extends JsonNodeDeserializer
{
#Override
public JsonNode getNullValue()
{
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.convertValue(new Value(null), JsonNode.class);
return node;
}
}
and register it on the value field of your Data class
class Data {
#JsonDeserialize(using = InstantiateOnNullDeserializer.class)
protected final Value value;
#JsonProperty("value")
public Value getValue() {
return value;
}
#JsonCreator
public Data(Value value) {
this.value = value;
}
}
Note that you need to remove the #JsonProperty("value") to avoid argument type mismatch. By removing JsonProperty annotation you create a so-called "delegate creator",
Jackson will than first bind JSON into type of the argument, and then call a creator

I do not believe that this is possible without creating your own deserializer or modify the constructor (or #JsonCreator).
From a good old thread in the Jackson User Group:
#JsonProperty does not support transformations, since the data binding is based on incremental parsing and does not have access to full tree representation.
So, in order to avoid a custom deserializer I would do something along the lines of:
#JsonCreator
public Data(#JsonProperty("value") final String value) {
this.value = new Value(value);
}
Which is kind of the opposite of what you asked ;)

Related

How to auto detect concrete class based on the JSON provided using Spring/Jackson

I have 2 implementations of Value interface, RangeValue, FileValue.
RangeValue looks like below:
public class RangeValue implements Value {
private int min;
private int max;
public RangeValue(int min, int max) {
this.min = min;
this.max = max;
}
public int getMin() {
return min;
}
public int getMax() {
return max;
}
}
FileValue looks like below:
public class FileValue implements Value {
private String contentType;
private String value;
public FileValue(String contentType, String value) {
this.contentType = contentType;
this.value = value;
}
public String getContentType() {
return contentType;
}
public String getValue() {
return value;
}
}
the json for RangeValue looks like :
{
"min": 200,
"max": 300
}
The json for FileValue looks:
{
"contentType": "application/octet-stream",
"value": "fileValue"
}
Now I want the RequestType parameter for these json to be of type Value only, I can't change the JSON files i.e. the json would look like the same and user should use the same JSON in request body as stated above.
I solved this by using #JsonTypeInfo & #JsonSubTypes by adding extra attributes to the above JSON i.e. type but the spec doesn't allow me to add that.
How can the appropriate concrete class could be instantiated based on the JSON above without altering?
Option 1: custom deserializer. Algorithm can be as follows:
Parse to JsonNode.
Use the properties in the node to find the correct class to deserialize into.
Convert the node to instance of the actual class.
Simplified example:
public class ValueDeserializer extends StdDeserializer<Value> {
public ValueDeserializer() {
super(Value.class);
}
#Override
public Value deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonNode root = parser.readValueAsTree();
if (root instanceof ObjectNode objectNode) {
JsonNode valueNode = objectNode.get("somePropertyName");
Class<? extends Value> clazz = valueNode == null ? RangeValue.class : FileValue.class;
return context.readTreeAsValue(objectNode, clazz);
}
throw new JsonParseException(parser, "not an object");
//handling the case, when json is json array
//or something else which can't be deserialized into object
}
}
Register the deserializer with JsonDeserialize on the interface:
#JsonDeserialize(using = ValueDeserializer.class)
Put the same annotation on RangeValue and FileValue, without specifying a deserializer, otherwise you will get StackOverflowError.
Option 2: use JsonTypeInfo.Id.DEDUCTION
#JsonSubTypes({
#JsonSubTypes.Type(FileValue.class),
#JsonSubTypes.Type(RangeValue.class)
})
#JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
public interface Value {
}
Jackson will deduce the correct class using the property names. Keep in mind exception will be thrown if it fails deduction.
Means that no serialized typing-property is used. Types are deduced based on the fields available. Deduction is limited to the names of fields (not their values or, consequently, any nested descendants). Exceptions will be thrown if not enough unique information is present to select a single subtype.
If deduction is being used annotation properties visible, property and include are ignored.

Saving Enum name instead of value in couchbase

I am working on spring data couchbase application.In oracle its saving the enum with name but in couchbase its value. I want to save enum name in couchbase instead of its value. But i dont want to change this below existing code.
Is there any way to do that?
And the enum is not a direct field in document. Its a key value in map of other attribute
public enum Foo {
Moon("Night"),
Star("Night"),
Sun("Day");
private String value;
Foo(String value) {
this.value = value;
}
public String getValue() {
return value;
}
#Override
public String toString() {
return this.value;
}
public class Universe{
Map<Foo, <List<List<Cone>>> bar;
}
Couchbase entity class is using
private List<Universe> universe;

Wanting to get Enum Value instead of name in JSON

I've got the following enum:
public enum NotificationType {
Store("S"),
Employee("E"),
Department("D"),
All("A");
public String value;
NotificationType(String value) {
this.value = value;
}
#Override
public String toString() {
return this.value;
}
#JsonCreator
public static NotificationType fromValue(String value) {
for (NotificationType type : NotificationType.values()) {
if (type.value.equals(value)) {
return type;
}
}
throw new IllegalArgumentException();
}
}
I've created a converter so that when the enum is saved to the database, it persists the value (S, E, D or A) instead of the name. And I can POST json to the controller with the value and it binds to the object correctly.
However, when I render the JSON from a GET it is still displaying the name (Employee, Store, etc) and I would prefer that it still show the value.
Because your toString method returns the value you want to use to represent your enum, you can annotate it with #JsonValue to tell Jackson that the return value represents the value of the enum.

Gson Serializing the Integer object

I'm generating JSON using the Gson library. I'm attempting to generate JSON like:
{"Plaintext":{"type":"List","value":["1","2","3","4"]},"SINGLE_FUNCTION":{"value":"1"},"IN_FLOW":{"value":10}}
However, the JSON that is appearing is..
{"Plaintext":{"type":"List","value":["1","2","3","4"]},"SINGLE_FUNCTION":{"value":"1"},"IN_FLOW":{"value":{"value":10}}}
The problem, I found is that when my objects are being created, an Integer object is being stored. This is because of the type of the mapping is Object, which means the int type that I'm storing is automatically wrapped to type Integer and Gson is rendering the only non-null value, value inside the Integer class.
My question is, is there a way of having Gson render an Integer object as if it were a primitive type int?
Edit
So, The whole JSON is a Map. This Map is defined as:
private Map<String, ParameterWrapper> parameterMap;
The class ParameterWrapper looks like this:
public class ParameterWrapper<T> {
private String type;
private T value;
public ParameterWrapper(String type, T value) {
this.type = type;
this.value = value;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
I suspect that because I'm not specifying a type T for ParameterWrapper, it's defaulting to Object, which is why Gson is outputting it as an Object and not an int, despite it being an instance of Integer.
This sample code (with lombok #Data and guava Lists):
package so28235867;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import lombok.Data;
import java.util.List;
public class Foo {
public static void main(final String[] args) {
final Root root = Root.of(Plaintext.of("List", Lists.newArrayList("1", "2", "3", "4")), new Value<String>("1"), new Value<Integer>(10));
System.out.println(new Gson().toJson(root));
}
#Data(staticConstructor = "of")
// TODO: tweak JSON serialisation to respect naming convention
static class Root {
private final Plaintext Plaintext;
private final Value<String> SINGLE_FUNCTION;
private final Value<Integer> IN_FLOW;
}
#Data(staticConstructor = "of")
static class Plaintext {
private final String type;
private final List<String> value;
}
#Data
static class Value<T> {
private final T value;
}
}
outputs:
{"Plaintext":{"type":"List","value":["1","2","3","4"]},"SINGLE_FUNCTION":{"value":"1"},"IN_FLOW":{"value":10}}
And that looks like what you want. So you probably have an error in the definition of your object hierarchy.
Change this,
public T getValue() {
return value;
}
to,
public T getValue() {
if( value instanceOf Integer ) {
return value.intValue();
}
return value;
}
My question is, is there a way of having Gson render an Integer object as if it were a primitive type int?
You can manually add the IN_FLOW (or any primitive) value as Integer primitive value.
jsonObject.add("IN_FLOW",new JsonPrimitive(obj.getInFlow()));

Non-stateless JAXB XML Adapters

I have a non-static data which I need to use on conversion. How can I transfer this data into my adapter class? Probably can I use a XmlAdapter in JAXB RI without an empty constructor (and without annotation of course)?
public class VariableAdapter extends XmlAdapter<String, Variable> {
private Map<String, Variable> varMap;
public VariableAdapter(Map<String, Variable> aVarMap) {
varMap = aVarMap;
}
public Variable unmarshal(String aVarName) {
return varMap.get(aVarName);
}
public String marshal(Variable v) {
return v.getName();
}
}
Here is my class, which I need to convert from/into XML
public class Variable {
private String name;
private Object value;
public Value(String aName, Object aValue) {
name = aName;
value = aValue;
}
public String getName() {return name;}
public Object getValue() {return value;}
public void setValue(Object aValue) {value = aValue;}
}
All Variable objects are initialized before XML processing and must be serialized per its name. Variable after unmarshalling can get another value (if its value was changed between serialization/deserialization).
By default JAXB will create a new instance of the XmlAdapter. You can call the setAdapter method on Marshaller/Unmarshaller to specify a stateful one.
For More Information
http://blog.bdoughan.com/2011/09/mixing-nesting-and-references-with.html

Categories

Resources