How to deserialize Jackson Json NULL String to Date with JsonFormat - json

I have looked a lot but still couldn't get the answer so far, any help is really appreciated!
I have a simple String to Date field mapping and try to read a JSON string to Java object.
#JsonInclude(value=Include.NON_EMPTY)
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="dd-MMM-yyyy", timezone="PST")
protected Date eolAnnounceDate;
However I am getting the following exception if the JSON string value is empty.
Can you someone tell me how to get around this? I have tried a few options but they are all for serialization.
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.setSerializationInclusion(Include.NON_EMPTY);
Exception :
java.lang.IllegalArgumentException: Failed to parse Date value 'NULL' (format: "dd-MMM-yyyy"): Unparseable date: "NULL"
com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateBasedDeserializer._parseDate(DateDeserializers.java:180)
com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer.deserialize(DateDeserializers.java:279)
com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer.deserialize(DateDeserializers.java:260)
com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:464)
com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:98)
com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:295)
com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:121)
com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:230)
com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:207)
com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:23)
com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:464)
com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:98)
com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:295)
com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:121)
com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:2888)
com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2034)
com.cisco.cre.dao.impl.ElasticsearchDAOImpl.getListByIdsFilter(ElasticsearchDAOImpl.java:94)
Thanks
- Atul

Your problem is not that a null value is passed in the JSON. The problem is that the JSON contains a string that has the value "NULL".
So, in order to fix this there are a number of available approaches. I think that the following two will work for this case.
Alternative 1: Fix the JSON
The first alternative is to fix the JSON so that it does not contain the the string value "NULL" and instead contain the value null (not quoted) or simply skip it.
Imagine the following POJO:
public class DatePojo {
#JsonInclude(value= JsonInclude.Include.NON_EMPTY)
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="dd-MMM-yyyy", timezone="PST")
#JsonProperty("date")
private Date date;
}
The following test shows that valid dates, empty values and null values work:
#Test
public void testJson() throws IOException {
String jsonWithValidDate = "{\"date\":\"12-Jun-1982\"}";
String jsonWithNoDate = "{}";
String jsonWithNullDate = "{\"date\":null}";
ObjectMapper mapper = new ObjectMapper();
final DatePojo pojoWithValidDate = mapper.readValue(jsonWithValidDate, DatePojo.class);
final DatePojo pojoWithNoDate = mapper.readValue(jsonWithNoDate, DatePojo.class);
final DatePojo pojoWithNullDate = mapper.readValue(jsonWithNullDate, DatePojo.class);
Assert.assertNotNull(pojoWithValidDate.date);
Assert.assertNull(pojoWithNoDate.date);
Assert.assertNull(pojoWithNullDate.date);
}
However, if you pass along the value "NULL" the test fails since "NULL" can not be parsed as a date:
#Test(expected = JsonMappingException.class)
public void testInvalidJson() throws IOException {
String jsonWithNullString = "{\"date\":\"NULL\"}";
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(jsonWithNullString, DatePojo.class); // Throws the exception
Assert.fail();
}
Alternative 2: provide your own converter that handles "NULL"
If it is not possible to fix the JSON (as described in alternative 1) you can provide your own converter.
Setup your pojo like this instead:
public class DatePojo {
#JsonProperty("date")
#JsonDeserialize(converter = MyDateConverter.class)
private Date date;
}
And provide a converter along the lines of:
public class MyDateConverter extends StdConverter<String, Date> {
#Override
public Date convert(final String value) {
if (value == null || value.equals("NULL")) {
return null;
}
try {
return new SimpleDateFormat("dd-MMM-yyyy").parse(value);
} catch (ParseException e) {
throw new IllegalStateException("Unable to parse date", e);
}
}
}
Then, you should be all set. The following test passes:
#Test
public void testJson() throws IOException {
String jsonWithValidDate = "{\"date\":\"12-Jun-1982\"}";
String jsonWithNoDate = "{}";
String jsonWithNullDate = "{\"date\":null}";
String jsonWithNullString = "{\"date\":\"NULL\"}"; // "NULL"
ObjectMapper mapper = new ObjectMapper();
final DatePojo pojoWithValidDate = mapper.readValue(jsonWithValidDate, DatePojo.class);
final DatePojo pojoWithNoDate = mapper.readValue(jsonWithNoDate, DatePojo.class);
final DatePojo pojoWithNullDate = mapper.readValue(jsonWithNullDate, DatePojo.class);
final DatePojo pojoWithNullStr = mapper.readValue(jsonWithNullString, DatePojo.class); // Works
Assert.assertNotNull(pojoWithValidDate.date);
Assert.assertNull(pojoWithNoDate.date);
Assert.assertNull(pojoWithNullDate.date);
Assert.assertNull(pojoWithNullStr.date); // Works
}
IMO, the best approach is to use alternative 1 where you simply change the JSON.

Related

jackson reading in non-existent and null values to “” and marshalling out “” to non-existent values?

I read through this post Jackson: deserializing null Strings as empty Strings which has this cool trick
mapper.configOverride(String.class)
.setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));
THEN on the flipside, I read through this post Jackson serialization: ignore empty values (or null) which has this cool trick
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
This is VERY VERY close except I really don't want incoming data to be null in any case. I have the following code printing 4 situations with the above settings BUT want to fix the null piece so any json we unmarshal into java results in
public class MapperTest {
private static final Logger log = LoggerFactory.getLogger(MapperTest.class);
private ObjectMapper mapper = new ObjectMapper();
public MapperTest() {
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.configOverride(String.class)
.setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));
}
public static void main(String[] args) throws JsonProcessingException {
new MapperTest().start();
}
private void start() throws JsonProcessingException {
//write out java color=null resulting in NO field...
String val = mapper.writeValueAsString(new Something());
log.info("val="+val);
Something something = mapper.readValue(val, Something.class);
log.info("value='"+something.getColor()+"'");
//write out java color="" resulting in NO field...
Something s = new Something();
s.setColor("");
String val2 = mapper.writeValueAsString(new Something());
log.info("val="+val2);
String temp = "{\"color\":null,\"something\":0}";
Something something2 = mapper.readValue(temp, Something.class);
log.info("value2='"+something2.getColor()+"'");
}
}
The output is then
INFO: val={"something":0}
INFO: value='null'
INFO: val={"something":0}
INFO: value2=''
NOTE: The value = 'null' is NOT what I desire and want that to also be empty string. Notice that if customers give a color:'null', it does result in empty string. Non-existence should result in the same thing for us "".
This is a HUGE win in less mistakes in this area 'for us' I mean.
thanks,
Dean

How to display decimal in specific format in Jackson (for JSON)

Suppose I have an object with
private Double test;
// Need specific output in JSON via Jackson: test = 24.6000
When output to JSON via Jackson, I get 24.6, but I need the exact 4-decimal output as in the example. Does Jackson allow this?
For example, for Dates, we found a way to force MM/dd/yyyy:
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "MM/dd/yyyy")
Date myDate;
We need something similar for Decimal formatting.
One way of doing this is to use custom json serializer and specify in #JsonSerialize.
#JsonSerialize(using = CustomDoubleSerializer.class)
public Double getAmount()
public class CustomDoubleSerializer extends JsonSerializer<Double> {
#Override
public void serialize(Double value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
if (null == value) {
jgen.writeNull();
} else {
final String pattern = ".####";
final DecimalFormat myFormatter = new DecimalFormat(pattern);
final String output = myFormatter.format(value);
jgen.writeNumber(output);
}
}
}
You can try to use com.fasterxml.jackson.databind.util.RawValue:
BigDecimal d = new BigDecimal(new BigInteger("246000"), 4);
RawValue rv = new RawValue(d.toPlainString());
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode output = objectMapper.createObjectNode();
output.putRawValue("decimal_value", rv);
System.out.println(output.toPrettyString());
//output is:
//{
// "decimal_value" : 24.6000
//}

Spring - Return Raw JSON without double serialization

I know there are other posts similar to this, but I haven't found any that help me find a solution for this particular case.
I am trying to return a HashMap<String, Object> from my Controller.
The Object part is a JSON string, but its being double serialized and not returned as a raw JSON string, thus not ending up with extra quotations and escape characters.
Controller function:
#RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public HashMap<String, Object> heartbeat(){
String streamInfo = service.getStreamInfo();
String streamCursorInfo = service.getStreamCursorInfo();
String topicInfo = service.getTopicInfo();
String greeting = "This is a sample app for using Spring Boot with MapR Streams.";
HashMap<String, Object> results = new HashMap();
results.put("greeting", greeting);
results.put("streamInfo", streamInfo);
results.put("streamCursorInfo", streamCursorInfo);
results.put("topicInfo", topicInfo);
return results;
}
Service function:
private String performCURL(String[] command){
StringBuilder stringBuilder = new StringBuilder();
try{
ProcessBuilder processBuilder = new ProcessBuilder(command);
Process p = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = null;
while((line = reader.readLine()) != null){
stringBuilder.append(line);
}
}
catch(Exception e){
LOGGER.error(ExceptionUtils.getRootCauseMessage(e));
}
return stringBuilder.toString();
}
The cURL command I run already returns a raw JSON string. So im just trying to add it to the HashMap to be returned in the heartbeat response.
But every time I run this, my output looks like:
{
"greeting": "This is a sample app for using Spring Boot with MapR Streams.",
"streamCursorInfo": "{\"timestamp\":1538676344564,\"timeofday\":\"2018-10-04 02:05:44.564 GMT-0400 PM\",\"status\":\"OK\",\"total\":1,\"data\":[{\"consumergroup\":\"MapRDBConsumerGroup\",\"topic\":\"weightTags\",\"partitionid\":\"0\",\"produceroffset\":\"44707\",\"committedoffset\":\"10001\",\"producertimestamp\":\"2018-10-03T05:57:27.128-0400 PM\",\"consumertimestamp\":\"2018-09-21T12:35:51.654-0400 PM\",\"consumerlagmillis\":\"1056095474\"}]}",
...
}
If i return only the single string, such as streamInfo then it works fine and doesnt add the extra quotes and escape chars.
Can anyone explain what im missing or need to do to prevent this double serialization?
Instead of returning a HashMap, create an object like this:
public class HeartbeatResult {
private String greeting;
... //other fields here
#JsonRawValue
private String streamCursorInfo;
... //getters and setters here (or make the object immutable by having just a constructor and getters)
}
With #JsonRawValue Jackson will serialize the string as is. See https://www.baeldung.com/jackson-annotations for more info.
streamCursorInfo is a string, not an object => the serialization will escape the " character.
If you are able to return the object containing the data, it will work out of the box. If what you have is just a String, I suggest to serialize it to JsonNode and add it in your response
ObjectMapper objectMapper = new ObjectMapper();
JsonNode streamCursorInfo = objectMapper.readTree(service.getStreamInfo())
results.put("streamCursorInfo", streamCursorInfo);

Handle JSON which sends array of items but sometimes empty string in case of 0 elements

I have a JSON which sends array of element in normal cases but sends empty string "" tag without array [] brackets in case of 0 elements.
How to handle this with Gson? I want to ignore the error and not cause JSONParsingException.
eg.
"types": [
"Environment",
"Management",
"Computers"
],
sometimes it returns:
"types" : ""
Getting the following exception: Expected BEGIN ARRAY but was string
Since you don't have control over the input JSON string, you can test the content and decide what to do with it.
Here is an example of a working Java class:
import com.google.gson.Gson;
import java.util.ArrayList;
public class Test {
class Types {
Object types;
}
public void test(String input) {
Gson gson = new Gson();
Types types = gson.fromJson(input,Types.class);
if(types.types instanceof ArrayList) {
System.out.println("types is an ArrayList");
} else if (types.types instanceof String) {
System.out.println("types is an empty String");
}
}
public static void main(String[] args) {
String input = "{\"types\": [\n" +
" \"Environment\",\n" +
" \"Management\",\n" +
" \"Computers\"\n" +
" ]}";
String input2 = "{\"types\" : \"\"}";
Test testing = new Test();
testing.test(input2); //change input2 to input
}
}
If a bad JSON schema is not under your control, you can implement a specific type adapter that would try to determine whether the given JSON document is fine for you and, if possible, make some transformations. I would recomment to use #JsonAdapter in order to specify improperly designed types (at least I hope the entire API is not improperly designed).
For example,
final class Wrapper {
#JsonAdapter(LenientListTypeAdapterFactory.class)
final List<String> types = null;
}
where LenientListTypeAdapterFactory can be implemented as follows:
final class LenientListTypeAdapterFactory
implements TypeAdapterFactory {
// Gson can instantiate it itself, let it just do it
private LenientListTypeAdapterFactory() {
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// Obtaining the original list type adapter
#SuppressWarnings("unchecked")
final TypeAdapter<List<?>> realListTypeAdapter = (TypeAdapter<List<?>>) gson.getAdapter(typeToken);
// And wrap it up in the lenient JSON type adapter
#SuppressWarnings("unchecked")
final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) new LenientListTypeAdapter(realListTypeAdapter);
return castTypeAdapter;
}
private static final class LenientListTypeAdapter
extends TypeAdapter<List<?>> {
private final TypeAdapter<List<?>> realListTypeAdapter;
private LenientListTypeAdapter(final TypeAdapter<List<?>> realListTypeAdapter) {
this.realListTypeAdapter = realListTypeAdapter;
}
#Override
public void write(final JsonWriter out, final List<?> value)
throws IOException {
realListTypeAdapter.write(out, value);
}
#Override
public List<?> read(final JsonReader in)
throws IOException {
// Check the next (effectively current) JSON token
switch ( in.peek() ) {
// If it's either `[...` or `null` -- we're supposing it's a "normal" list
case BEGIN_ARRAY:
case NULL:
return realListTypeAdapter.read(in);
// Is it a string?
case STRING:
// Skip the value entirely
in.skipValue();
// And return a new array list.
// Note that you might return emptyList() but Gson uses mutable lists so we do either
return new ArrayList<>();
// Not anything known else?
case END_ARRAY:
case BEGIN_OBJECT:
case END_OBJECT:
case NAME:
case NUMBER:
case BOOLEAN:
case END_DOCUMENT:
// Something definitely unexpected
throw new MalformedJsonException("Cannot parse " + in);
default:
// This would never happen unless Gson adds a new type token
throw new AssertionError();
}
}
}
}
Here is it how it can be tested:
for ( final String name : ImmutableList.of("3-elements.json", "0-elements.json") ) {
try ( final Reader reader = getPackageResourceReader(Q43562427.class, name) ) {
final Wrapper wrapper = gson.fromJson(reader, Wrapper.class);
System.out.println(wrapper.types);
}
}
Output:
[Environment, Management, Computers]
[]
If the entire API uses "" for empty arrays, then you can drop the #JsonAdapter annotation and register the LenientListTypeAdapterFactory via GsonBuilder, but add the following lines to the create method in order not to break other type adapters:
if ( !List.class.isAssignableFrom(typeToken.getRawType()) ) {
// This tells Gson to try to pick up the next best-match type adapter
return null;
}
...
There are a lot of weirdly designed JSON response choices, but this one hits the top #1 issue where nulls or empties are represented with "". Good luck!
Thanks for all your answers.
The recommed way as mentioned in above answers would be to use TypeAdapters and ExclusionStrategy for GSON.
Here is a good example Custom GSON desrialization

GSON de/serialize Object with Calendar to json w/ Mongo Date and back

I have some entities, which contain some Calendar attributes. I want to serialize it in a way that they are stored as Dates within the GSON serialized JSON, because Mongo can store $date as new ISODate(".."). We did this usually by ignoring the calendar attributes with ExclusionStrategy and set them manually but it became pretty gruesome after a while.
I found some code snippets which should make it work via a custom TypeAdapter.
This is my CalendarDateTypeAdapter.
public class CalendarDateTypeAdapter extends TypeAdapter<Calendar> implements JsonSerializer<Calendar>, JsonDeserializer<Calendar> {
private static final Gson gson = new GsonBuilder().create();
private static final TypeAdapter<Date> dateTypeAdapter = gson.getAdapter(Date.class);
private static final String MONGO_UTC_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
public JsonElement serialize(Calendar src, Type type,
JsonSerializationContext context) {
if (src == null) {
return null;
} else {
SimpleDateFormat format = new SimpleDateFormat(MONGO_UTC_FORMAT);
JsonObject jo = new JsonObject();
jo.addProperty("$date", format.format(src.getTime()));
return jo;
}
}
#Override
public Calendar deserialize(JsonElement json, Type type,
JsonDeserializationContext context) throws JsonParseException {
Date date = null;
SimpleDateFormat format = new SimpleDateFormat(MONGO_UTC_FORMAT);
try {
date = format.parse(json.getAsJsonObject().get("$date").getAsString());
} catch (ParseException e) {
date = null;
}
GregorianCalendar gregorianCalendar = new GregorianCalendar();
gregorianCalendar.setTime(date);
return gregorianCalendar;
}
#Override
public void write(JsonWriter out, Calendar value) throws IOException {
dateTypeAdapter.write(out, value.getTime());
}
#Override
public Calendar read(JsonReader in) throws IOException {
Date read = dateTypeAdapter.read(in);
GregorianCalendar gregorianCalendar = new GregorianCalendar();
gregorianCalendar.setTime(read);
return gregorianCalendar;
}
}
When I create my gson like:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(GregorianCalendar.class, new CalendarDateTypeAdapter());
Gson create = gsonBuilder.create();
and try to seralize I get sth like:
{ "dateHarvested" : { "year" : 2015 , "month" : 0 , "dayOfMonth" : 5 , "hourOfDay" : 22 , "minute" : 45 , "second" : 37}
instead of:
"date_harvested" : ISODate("2015-01-05T13:03:56.132Z")
Afterwards when deserializing I would like it to convert it back to a gregorian calendar.
What am I doing wrong?
I had the same issue with GSON. I created a custom serializer for the Calendar object but kept getting the default serializer output when serializing Calendar objects. I solved this by using registerTypeHierarchyAdapter instead of registerTypeAdapter.
GsonBuilder = new GsonBuilder();
build.registerTypeHierarchyAdapter(Calendar.class, new CalendarSerializer());
Gson gson = builder.create();
Hope that helps!