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;
}
}
Related
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}
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.
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);
}
I wanted to test my serializer which parses my java object to a json object. This is my Serializer class:
public class CountryCodeSerializer extends JsonSerializer<CountryCode> {
#Override
public void serialize(CountryCode value, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
if (value == null) {
generator.writeString("{}");
} else {
generator.writeString(value.toString());
}
}
}
My test looks like this:
#Before
public void setUp() throws Exception {
stringJson = new StringWriter();
generator = new JsonFactory().createGenerator(stringJson);
provider = new ObjectMapper().getSerializerProvider();
countryCode = CountryCode.parse("us");
}
#Test
public void parsingNullReturnsNull() throws Exception {
assertThat(countryCodeSerializer.serialize(countryCode, generator, provider)).isEqualTo("{'countrycode':'us'}); //this doesn't work, since serialize() is void
//countryCodeSerializer.serialize(countryCode, generator, provider); //this throws an java.lang.NullPointerException
}
So how can I test my serializer? I tried other answers to similar questions, but nothing worked for me.
I use the serializer like this in my other clases:
#JsonSerialize(using = CountryCodeSerializer.class)
private CountryCode countryCode;
Ok thank you for your answers. I got it now this way and it works fine:
I changed my serializer a little bit:
public class CountryCodeSerializer extends JsonSerializer<CountryCode> {
#Override
public void serialize(CountryCode value, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
if (null == value) {
throw new IllegalArgumentException("CountryCode is null");
} else {
generator.writeString(value.toString());
}
}
}
And here are my two tests:
public class CountryCodeSerializerTest {
private CountryCodeSerializer countryCodeSerializer;
private JsonGenerator jsonGenerator;
#Before
public void setUp() throws Exception {
countryCodeSerializer = new CountryCodeSerializer();
jsonGenerator = mock(JsonGenerator.class);
}
#Test
public void testNullCountryCodeThrowsIllegalArgumentException() throws Exception {
try {
countryCodeSerializer.serialize(null, jsonGenerator, null);
fail("An IllegalArgumentException should have been thrown.");
} catch (IllegalArgumentException e) {
//ok
}
}
#Test
public void testCountryCodeConvertedToJsonString() throws Exception {
countryCodeSerializer.serialize(CountryCode.parse("us"), jsonGenerator, null);
verify(jsonGenerator).writeString("us");
}
}
Something like this:
#Mock
private JsonGenerator generator;
#Test
public void testInstanceWithValue() {
//SETUP
String expectedValue = "test value";
CountryCode value = mock(CountryCode.class);
when(value.toString()).thenReturn(expectedValue);
// CALL
CountryCodeSerializer instance = new CountryCodeSerializer(value, generator, null);
// VERIFY
verify(generator).writeString(expectedValue);
}
#Test
public void testInstanceWithNull() {
//SETUP
CountryCode value = null;
// CALL
CountryCodeSerializer instance = new CountryCodeSerializer(value, generator, null);
// VERIFY
verify(generator).writeString("{}");
}
This can be achieved by creating a custom JsonGenerator that stores what is written to it.
class TestJsonGenerator extends JsonGenerator {
private StringBuilder stringBuilder = new StringBuilder();
...
#Override
public void writeString(String text) {
stringBuilder.append(text);
}
public String getText() {
return stringBuilder.toString();
}
}
Then you verify the generated content, without needing to check all the calls to writeString that were made:
TestJsonGenerator testGenerator = new TestJsonGenerator();
serializer.serialize(countryCode, testGenerator, provider);
assertThat(testGenerator.getText()).isEqualsTo("{ \"foo\": \"bar\" }");
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 "";
}
}