JsonDeserializer Not Processing Absent Properties - java

I am not able to get JsonDeserializer to process empty values. I have a DAO and a custom deserialiser like this.
Class Example {
#JsonDeserialize(using = ResetableValueDeserializer.class)
Resetable<String> property;
}
class ResetableValueDeserializer extends StdDeserializer<ResetableValue<String>> {
ResetableValueDeserializer() {
super(ResetableValue.class);
}
protected ResetableValueDeserializer(Class<?> vc) {
super(vc);
}
#Override
public ResetableValue<String> deserialize(JsonParser jp, DeserializationContext deserializationContext) throws IOException, JacksonException {
JsonNode node = jp.readValueAsTree();
if (node.asText().isEmpty()) {
return null;
}
return new ResetableValue<>(node.asText());
}
#Override
public ResetableValue<String> getNullValue(DeserializationContext ctxt) throws JsonMappingException {
return ResetableValue.asReset();
}
#Override
public ResetableValue<String> getAbsentValue(DeserializationContext ctxt) {
return ResetableValue.asNotProvided();
}
But neither the deserialise() nor getAbsentValue() is triggered when I have an empty input JSON like this {}. While the getNull() method is handling input JSON {property: null}

Related

How to prepare or mock JsonParser to test custom StdDeserializer

I have custom StdDeserializer<Date>, how can i unit test the overridden deserialize method here?
or how can i prepare or mock JsonParser here for unit testing desterilize method?
public class StringToDateDeserializer extends StdDeserializer<Date> {
protected StdDateFormat df = new StdDateFormat();
public StringToDateDeserializer() {
this(null);
}
protected StringToDateDeserializer(Class<?> T) {
super(T);
}
#Override
public Date deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
String dateStr = jsonParser.getText();
if (StringUtils.isEmpty(dateStr)) {
return null;
}
try {
return df.parse(dateStr);
} catch (ParseException e) {
throw new MyCustomException("Invalid date passed, ISO 8601 is expected");
}
}
}
Example of test for StringToDateDeserializer with 100% coverage.
public class TestClass {
private ObjectMapper mapper;
private StringToDateDeserializer deserializer;
#Before
public void setup() {
mapper = new ObjectMapper();
deserializer = new StringToDateDeserializer();
}
#Test
public void dateTest() throws IOException {
Date date = deserializer.deserialize(prepareParser("{ \"value\":\"2020-07-10T15:00:00.000\" }"), mapper.getDeserializationContext());
Assert.assertNotNull(date);
Assert.assertEquals(1594393200000L, date.getTime());
}
#Test(expected = MyCustomException.class)
public void exceptionalTest() throws IOException {
deserializer.deserialize(prepareParser("{ \"value\":\"2020-07\" }"), mapper.getDeserializationContext());
}
#Test
public void nullTest() throws IOException {
Date date = deserializer.deserialize(prepareParser("{ \"value\":\"\" }"), mapper.getDeserializationContext());
Assert.assertNull(date);
}
private JsonParser prepareParser(String json) throws IOException {
JsonParser parser = mapper.getFactory().createParser(json);
while (parser.nextToken() != JsonToken.VALUE_STRING);
return parser;
}
}

How to throw a custom exception from a custom deserializer

I have a class deserialized by my custom deserializer and I need to throw my custom exception.
public class MyClass {
}
public static void main(String[] args) {
try {
ObjectMapper mapper = new ObjectMapped();
MyClass myClass = mapper.readValue(json, MyClass.class);
} catch(Exception e) {
System.out.println(e); // JsonMappingException
System.out.println(e.getCause()); // null, but I need to get my CustomException.class
}
}
public class MyDeserializer extends StdDeserializer<MyClass> {
#Override
public MyClass deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
throw new CustomException("TestException", 1);
}
}
public class CustomException extends IOException {
private int code;
public CustomException(String message, int code) {
super(message);
this.code = code;
}
public int getCode() {
return code;
}
}
What can I do to return my custom exception from the custom deserializer?
First you should add custom desializer to ObjectMapper or target Object like below
#JsonDeserialize(using = MyDeserializer.class)
public class MyClass {
}
getCause() return null because you already have caused Exception, check getCause implementation
public synchronized Throwable getCause() {
return (cause==this ? null : cause);
}
shorty you don't need to getCause, e is your CustomException instance. Just do first step and remove getCause, it ll be fine.

How to alter the value of a JsonNode while constructing it from a string in Jackson

I have a JSON string and I want to alter the value while constructing the JsonNode using Jackson library.
eg:-
input: {"name":"xyz","price":"90.00"}
output:{"name":"xyz-3","price":90.90}
I created my own JsonFactory and passed my own Parser. but I can only alter the keys, not the values associated with a key.
code:
private static ObjectMapper create() {
ObjectMapper objectMapper = new ObjectMapper(new JsonFactory() {
#Override
protected JsonParser _createParser(byte[] data, int offset, int len, IOContext ctxt) throws IOException {
return new MyParser(super._createParser(data, offset, len, ctxt));
}
#Override
protected JsonParser _createParser(InputStream in, IOContext ctxt) throws IOException {
return new MyParser(super._createParser(in, ctxt));
}
#Override
protected JsonParser _createParser(Reader r, IOContext ctxt) throws IOException {
return new MyParser(super._createParser(r, ctxt));
}
#Override
protected JsonParser _createParser(char[] data, int offset, int len, IOContext ctxt, boolean recyclable)
throws IOException {
return new MyParser(super._createParser(data, offset, len, ctxt, recyclable));
}
});
private static final class MyParser extends JsonParserDelegate {
private MyParser(JsonParser d) {
super(d);
}
#Override
public String getCurrentName() throws IOException, JsonParseException {
....
}
#Override
public String getText() throws IOException, JsonParseException {
...
}
#Override
public Object getCurrentValue() {
...
}
#Override
public String getValueAsString() throws IOException {
...
}
#Override
public String getValueAsString(String defaultValue) throws IOException {
...
}
}
Below is the code to construct the JsonNode from the string.
mapper.readTree(jsonStr);
In this case when the readTree method is called the getCurrentValue or getValueAsString methods are not called, so I am not able to alter the value while creating the JsonNode itself.
Also the json strings can be different. Basically I want to construct a JsonNode from the string. so tying to a specific schema/bean is not a good choice here.
How to address this ? TIA
Adding the updated code for version 2.7.4:-
static class MyParser extends JsonParserDelegate {
MyParser(final JsonParser delegate) {
super(delegate);
}
#Override
public String getText() throws IOException {
final String text = super.getText();
if ("name".equals(getCurrentName())) {
return text + "-3";
}
return text;
}
#Override
public JsonToken nextToken() throws IOException {
if ("price".equals(getCurrentName())) {
// Advance token anyway
super.nextToken();
return JsonToken.VALUE_NUMBER_FLOAT;
}
return super.nextToken();
}
#Override
public int getCurrentTokenId() {
try {
if ("price".equals(getCurrentName())) {
return JsonTokenId.ID_NUMBER_FLOAT;
}
} catch (final IOException e) {
//
}
return super.getCurrentTokenId();
}
#Override
public NumberType getNumberType() throws IOException {
if ("price".equals(getCurrentName())) {
return NumberType.FLOAT;
}
return super.getNumberType();
}
#Override
public float getFloatValue() throws IOException {
return Float.parseFloat(getValueAsString("0")) + 0.09F;
}
#Override
public double getDoubleValue() throws IOException {
return Double.parseDouble(getValueAsString("0")) + 0.09D;
}
}
pom.xml:-
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.8.7</version>
<!--<scope>test</scope>-->
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.8.7</version>
</dependency>
Edit: there is a subtle difference between 2.7.* and 2.9.*.
While 2.9.* is able to differentiate between double and float with
getDoubleValue()
getFloatValue()
instead 2.7.* only uses
getDoubleValue()
even for ID_NUMBER_FLOAT tokens.
So, you need to decide if you want to maintain retro-compatibility or not.
You can also override both, like I did here.
This is all what you need for your custom MyParser
static class MyParser extends JsonParserDelegate {
MyParser(final JsonParser delegate) {
super(delegate);
}
#Override
public String getText() throws IOException {
final String text = super.getText();
if ("name".equals(getCurrentName())) {
return text + "-3";
}
return text;
}
#Override
public JsonToken nextToken() throws IOException {
if ("price".equals(getCurrentName())) {
// Advance token anyway
super.nextToken();
return JsonToken.VALUE_NUMBER_FLOAT;
}
return super.nextToken();
}
#Override
public int getCurrentTokenId() {
try {
if ("price".equals(getCurrentName())) {
return JsonTokenId.ID_NUMBER_FLOAT;
}
} catch (final IOException e) {
//
}
return super.getCurrentTokenId();
}
#Override
public NumberType getNumberType() throws IOException {
if ("price".equals(getCurrentName())) {
return NumberType.FLOAT;
}
return super.getNumberType();
}
#Override
public float getFloatValue() throws IOException {
return Float.parseFloat(getValueAsString("0")) + 0.09F;
}
#Override
public double getDoubleValue() throws IOException {
return Double.parseDouble(getValueAsString("0")) + 0.09D;
}
}
Output: {"name":"xyz-3","price":90.09}
Your code seems fine, and it's tested and working ;)
Are you really sure that regarding the Separation of Concerns it is a good idea to mix parsing and changes within the parsed data?
If you still want to do this, you could use a Custom Deserializer and treat your wanted field names and types the way you want it, like:
class CustomDeserializer extends StdDeserializer<Entity> {
public CustomDeserializer(Class<Entity> t) {
super(t);
}
#Override
public Entity deserialize(JsonParser jp, DeserializationContext dc) throws IOException {
String name = null;
float price = 0;
JsonToken currentToken = null;
while ((currentToken = jp.nextValue()) != null) {
switch (currentToken) {
case VALUE_STRING:
switch (jp.getCurrentName()) {
case "name":
name = jp.getText() + "-3"; // change this text to whatever you want;
break;
case "price":
price = Float.parseFloat(jp.getText()); // parse
break;
default:
break;
}
break;
default:
break;
}
}
return new Entity(name, price);
}
}
And after registering your custom deserializer it works on any object mapper you want:
#Test
public void customDeserialization() throws IOException {
// given
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Entity.class, new CustomDeserializer(Entity.class));
mapper.registerModule(module);
// when
Entity entity = mapper.readValue("{\"name\":\"xyz\",\"price\":\"90.00\"}", Entity.class);
// then
assertThat(entity.getName()).isEqualTo("xyz-3");
assertThat(entity.getPrice()).isEqualTo(90f);
}

How to get enum class in generic method?

I am trying to create a standard way to serialize and deserialize for Enum on Jackson.
My serialize is easy:
public class EnumSerializer extends JsonSerializer<Enum<?>> {
#Override
public void serialize(Enum<?> data, JsonGenerator gen, SerializerProvider provider)
throws IOException, JsonProcessingException {
if (data == null) {
gen.writeString("");
} else {
gen.writeString(data.name());
}
}
}
Now I am working on Deserializer:
public class EnumDeserializer extends JsonDeserializer<Enum<?>> {
#Override
public Enum<?> deserialize(JsonParser jsonparser, DeserializationContext deserializationcontext)
throws IOException, JsonProcessingException {
String dataStr = jsonparser.getText();
if (dataStr == null || dataStr.isEmpty()) {
return null;
} else {
Class<Enum<?>> enumClass = null; // How can I get enumClass?
for(Enum<?> one: enumClass.getEnumConstants()){
if(one.name().equals(dataStr)){
return one;
}
}
return null;
}
}
}
But you can see I have trouble to get enumClass.
Could you please help me?
Thanks!
If you really want to create the custom EnumDeserializer you can see the implementation of Jackson:
com.fasterxml.jackson.databind.deser.std.EnumDeserializer
But as I can see you try to implement the standard behavior of Jackson.

JacksonMapper to deserialize null value

I am going to deserialize Json null value to Java Object empty string
I am able to make my custom deserializer, but when the Json value is null, it did not go into the deserializer.
How should I deserialize it?
Thanks in advance!
public class CustomStringDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonparser, DeserializationContext deserializationcontext) throws IOException,
JsonProcessingException {
String str = jsonparser.getText();
try {
return (str == null) ? "" : str;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public CustomObjectMapper() {
SimpleModule _module = new SimpleModule("Module", new Version(1, 9, 10, "FINAL"));
_module.addDeserializer(String.class, new CustomStringDeserializer());
}
Thanks #nutlike
I do this by
#Override
public String getNullValue() {
return "";
}
Maybe it would be sufficient to overwrite the method getNullValue()?
public class CustomStringDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonparser,
DeserializationContext deserializationcontext) throws IOException,
JsonProcessingException {
return jsonparser.getText();
}
#Override
public String getNullValue() {
return "";
}
}

Categories

Resources