Is there anyway I can pass a parameter to GSON JsonSerializer? - java

I've got a json file which has got a Date field. I'm transforming this to a required Date format(dd/MM/yyyy) using JsonDeserializer and registering the class as a TypeAdapter in GsonBuilder. Now, I want to transform this field to different date formats(say ISO8601) for different use cases. Instead of creating new JsonDeserializer's for different date formats, Is there anyway I can pass date format as one of the parameters to JsonSerializer class to make it extensible.
Deserializer class:
public class DateDeserializer implements JsonDeserializer<Date> {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd/MM/yyyy");
#Override
public Date deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) {
String dateString = jsonElement.getAsString();
if (!StringUtils.isEmpty(dateString)) {
try {
return DATE_FORMAT.parse(dateString);
} catch (ParseException e) {
}
}
}
GsonBuilder:
public static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(Date.class, new DateDeserializer())
.serializeNulls()
.create();

Related

Java GSON deserializer LocalDate

I have a problem trying to deserialize a gson LocalDate.
I have the next 2 adaptors:
public class LocalDateSerializer implements JsonSerializer < LocalDate > {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d-MMM-yyyy");
#Override
public JsonElement serialize(LocalDate localDate, Type srcType, JsonSerializationContext context) {
return new JsonPrimitive(formatter.format(localDate));
}
}
and
public class LocalDateDeserializer implements JsonDeserializer < LocalDate > {
#Override
public LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return LocalDate.parse(json.getAsString(),
DateTimeFormatter.ofPattern("d-MMM-yyyy").withLocale(Locale.ENGLISH));
}
}
I try to make a simple Serialization of an object "Task" which have a field starttime of LocalDate type.
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateSerializer());
gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateDeserializer());
Gson gson = gsonBuilder.setPrettyPrinting().create();
LocalDate now=LocalDate.now();
Task task=new Task();
task.setID(1);
task.setDuration(2);
task.setID_Proiect(1);
task.setStarttime(now);
JSONObject json=new JSONObject(task);
String aux1=gson.toJson(json);
System.out.println(aux1);
Task t2=gson.fromJson(aux1,Task.class);
I receive the error
Failed making field 'java.time.LocalDateTime#date' accessible; either
change its visibility or write a custom TypeAdapter for its declaring
type
Can you tell me what is wrong with this code? I know it is probably a noobie question but i rly need help to improve my skills. Ty

Testing an isolated custom JsonDeserializer in Java

So for this little program I'm writing I'm looking to parse Twitter's tweet stream. Im using the Gson library which works nice. Gson couldn't parse Twitters created_at datetime field, so I had to write a custom JsonDserializer that needs to be registered with the parser through the GsonBuilderas follows:
new GsonBuilder().registerTypeAdatapter(DateTime.class, <myCustomDeserializerType>)
Now my deserializer works well, and I am able to parse Twitter's stream.
However, I'm trying to cover as much of my program with unit tests, so this custom deserializer should be included.
Since a good unit test is a nicely isolated test, I do not want to register it with a Gson object after which I would parse a json string. What I do want is to create an instance of my deserializer and just pass a generic string representing a datetime, so that I could test the deserializer without it being integrated with anything else.
The signature of the deserialize method of a JsonDeserializer is as follows:
deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext)
Let's say I want to parse the following data: 'Mon Mar 27 14:09:47 +0000 2017'. How would I have to transform my input data in order to correctly test my deserializer.
I'm not looking for code that actually parses this date, I already have that part covered. I'm asking how I can meet the deserialize method's signature so that I can simulate it's use in a Gson it is used in.
JsonSerializer and JsonDeserializer are tightly bound to Gson JSON tree model and a specific Gson configuration (de)serialization context that provides a set of types that can be (de)serialized. Because of this, accomplishing unit tests for JsonSerializer and JsonDeserializer are rather hard than easy.
Consider the following JSON document somewhere in your src/test/resources/.../zoned-date-time.json:
"Mon Mar 27 14:09:47 +0000 2017"
This is a perfectly valid JSON document, and it has nothing except of a single string for simplicity. A date/time formatter for the format above can be implemented in Java 8 as follows:
final class CustomPatterns {
private CustomPatterns() {
}
private static final Map<Long, String> dayOfWeek = ImmutableMap.<Long, String>builder()
.put(1L, "Mon")
.put(2L, "Tue")
.put(3L, "Wed")
.put(4L, "Thu")
.put(5L, "Fri")
.put(6L, "Sat")
.put(7L, "Sun")
.build();
private static final Map<Long, String> monthOfYear = ImmutableMap.<Long, String>builder()
.put(1L, "Jan")
.put(2L, "Feb")
.put(3L, "Mar")
.put(4L, "Apr")
.put(5L, "May")
.put(6L, "Jun")
.put(7L, "Jul")
.put(8L, "Aug")
.put(9L, "Sep")
.put(10L, "Oct")
.put(11L, "Nov")
.put(12L, "Dec")
.build();
static final DateTimeFormatter customDateTimeFormatter = new DateTimeFormatterBuilder()
.appendText(DAY_OF_WEEK, dayOfWeek)
.appendLiteral(' ')
.appendText(MONTH_OF_YEAR, monthOfYear)
.appendLiteral(' ')
.appendValue(DAY_OF_MONTH, 1, 2, NOT_NEGATIVE)
.appendLiteral(' ')
.appendValue(HOUR_OF_DAY, 2)
.appendLiteral(':')
.appendValue(MINUTE_OF_HOUR, 2)
.appendLiteral(':')
.appendValue(SECOND_OF_MINUTE, 2)
.appendLiteral(' ')
.appendOffset("+HHMM", "+0000")
.appendLiteral(' ')
.appendValue(YEAR)
.toFormatter();
}
Now consider the following JSON deserializer for ZonedDateTime:
final class ZonedDateTimeJsonDeserializer
implements JsonDeserializer<ZonedDateTime> {
private static final JsonDeserializer<ZonedDateTime> zonedDateTimeJsonDeserializer = new ZonedDateTimeJsonDeserializer();
private ZonedDateTimeJsonDeserializer() {
}
static JsonDeserializer<ZonedDateTime> getZonedDateTimeJsonDeserializer() {
return zonedDateTimeJsonDeserializer;
}
#Override
public ZonedDateTime deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
try {
final String s = context.deserialize(jsonElement, String.class);
return ZonedDateTime.parse(s, customDateTimeFormatter);
} catch ( final DateTimeParseException ex ) {
throw new JsonParseException(ex);
}
}
}
Note that I'm deserialiazing a string via the context by intention to accent that more complex JsonDeserializer instances may depend on it heavily. Now let's make some JUnit tests to test it:
public final class ZonedDateTimeJsonDeserializerTest {
private static final TypeToken<ZonedDateTime> zonedDateTimeTypeToken = new TypeToken<ZonedDateTime>() {
};
private static final ZonedDateTime expectedZonedDateTime = ZonedDateTime.of(2017, 3, 27, 14, 9, 47, 0, UTC);
#Test
public void testDeserializeIndirectlyViaAutomaticTypeAdapterBinding()
throws IOException {
final JsonDeserializer<ZonedDateTime> unit = getZonedDateTimeJsonDeserializer();
final Gson gson = new GsonBuilder()
.registerTypeAdapter(ZonedDateTime.class, unit)
.create();
try ( final JsonReader jsonReader = getPackageResourceJsonReader(ZonedDateTimeJsonDeserializerTest.class, "zoned-date-time.json") ) {
final ZonedDateTime actualZonedDateTime = gson.fromJson(jsonReader, ZonedDateTime.class);
assertThat(actualZonedDateTime, is(expectedZonedDateTime));
}
}
#Test
public void testDeserializeIndirectlyViaManualTypeAdapterBinding()
throws IOException {
final JsonDeserializer<ZonedDateTime> unit = getZonedDateTimeJsonDeserializer();
final Gson gson = new Gson();
final TypeAdapterFactory typeAdapterFactory = newFactoryWithMatchRawType(zonedDateTimeTypeToken, unit);
final TypeAdapter<ZonedDateTime> dateTypeAdapter = typeAdapterFactory.create(gson, zonedDateTimeTypeToken);
try ( final JsonReader jsonReader = getPackageResourceJsonReader(ZonedDateTimeJsonDeserializerTest.class, "zoned-date-time.json") ) {
final ZonedDateTime actualZonedDateTime = dateTypeAdapter.read(jsonReader);
assertThat(actualZonedDateTime, is(expectedZonedDateTime));
}
}
#Test
public void testDeserializeDirectlyWithMockedContext()
throws IOException {
final JsonDeserializer<ZonedDateTime> unit = getZonedDateTimeJsonDeserializer();
final JsonDeserializationContext mockContext = mock(JsonDeserializationContext.class);
when(mockContext.deserialize(any(JsonElement.class), eq(String.class))).thenAnswer(iom -> {
final JsonElement jsonElement = (JsonElement) iom.getArguments()[0];
return jsonElement.getAsJsonPrimitive().getAsString();
});
final JsonParser parser = new JsonParser();
try ( final JsonReader jsonReader = getPackageResourceJsonReader(ZonedDateTimeJsonDeserializerTest.class, "zoned-date-time.json") ) {
final JsonElement jsonElement = parser.parse(jsonReader);
final ZonedDateTime actualZonedDateTime = unit.deserialize(jsonElement, ZonedDateTime.class, mockContext);
assertThat(actualZonedDateTime, is(expectedZonedDateTime));
}
verify(mockContext).deserialize(any(JsonPrimitive.class), eq(String.class));
verifyNoMoreInteractions(mockContext);
}
}
Note that each test here requires some Gson configuration to be built in order to let the deserialization context work, or the latter must be mocked. Pretty much to test a simple unit.
An alternative to the JSON tree model in Gson is stream-oriented type adapters that that do not require the entire JSON tree to be constructed, so you can easily read or write directly from/to JSON streams making your (de)serialization faster and less memory consuming. Especially, for simple cases like what trivial string<==>FooBar conversions are.
final class ZonedDateTimeTypeAdapter
extends TypeAdapter<ZonedDateTime> {
private static final TypeAdapter<ZonedDateTime> zonedDateTimeTypeAdapter = new ZonedDateTimeTypeAdapter().nullSafe();
private ZonedDateTimeTypeAdapter() {
}
static TypeAdapter<ZonedDateTime> getZonedDateTimeTypeAdapter() {
return zonedDateTimeTypeAdapter;
}
#Override
public void write(final JsonWriter out, final ZonedDateTime zonedDateTime) {
throw new UnsupportedOperationException();
}
#Override
public ZonedDateTime read(final JsonReader in)
throws IOException {
try {
final String s = in.nextString();
return ZonedDateTime.parse(s, customDateTimeFormatter);
} catch ( final DateTimeParseException ex ) {
throw new JsonParseException(ex);
}
}
}
And here is a simple unit test for the type adapter above:
public final class ZonedDateTimeTypeAdapterTest {
private static final ZonedDateTime expectedZonedDateTime = ZonedDateTime.of(2017, 3, 27, 14, 9, 47, 0, UTC);
#Test(expected = UnsupportedOperationException.class)
public void testWrite() {
final TypeAdapter<ZonedDateTime> unit = getZonedDateTimeTypeAdapter();
unit.toJsonTree(expectedZonedDateTime);
}
#Test
public void testRead()
throws IOException {
final TypeAdapter<ZonedDateTime> unit = getZonedDateTimeTypeAdapter();
try ( final Reader reader = getPackageResourceReader(ZonedDateTimeTypeAdapterTest.class, "zoned-date-time.json") ) {
final ZonedDateTime actualZonedDateTime = unit.fromJson(reader);
assertThat(actualZonedDateTime, is(expectedZonedDateTime));
}
}
}
For simple cases I would definitely go with type adapters however they may be somewhat harder to implement. You could also refer the Gson unit tests for more information.

Why is my DateTime deserializer is truncating DateTime's minute/second/millisecond?

I have a class that deserializes a JSON element.
public class DateTimeConverter implements JsonSerializer<DateTime>, JsonDeserializer<DateTime>
{
private static final DateTimeFormatter DATE_FORMAT = ISODateTimeFormat.dateHourMinuteSecondMillis();
#Override
public JsonElement serialize(DateTime src, Type typeOfSrc, JsonSerializationContext context)
{
final DateTimeFormatter fmt = ISODateTimeFormat.dateHourMinuteSecondMillis();
return new JsonPrimitive(fmt.print(src));
}
#Override
public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException
{
final String dateAsString = json.getAsString();
System.out.println(dateAsString);
if (json.isJsonNull() || dateAsString.length()==0)
{
return null;
}
else
{
return DATE_FORMAT.parseDateTime(json.getAsString());
}
}
}
However, my Deserialize method when I input:
2015-07-29T11:00:00.000Z
I receive:
2015-07-29T11
from the System.out.println(dateAsString); Why is it truncating my input?
I think my issue is within my test class:
I constructed a DateTime object to be used with Google's Gson. However, I think the default constructor for DateTimeType doesn't support minute/second/millisecond. Is there a way I can extend the DateTimeType to support it?
Here is my test class:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import org.joda.time.DateTime;
import org.junit.Test;
import java.lang.reflect.Type;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* Tests {#link DateTimeConverter}.
*/
public class DateTimeConverterTest {
String testTime = "2015-07-29T11:00:00.001Z";
#Test
public void testDateTimeConverter() throws Exception {
final Gson gson = initCustomGSON();
Type DateTimeType = new TypeToken<DateTime>() {
}.getType();
System.out.println(testTime);
DateTimeConverter timeConverter = new DateTimeConverter();
DateTime m = (gson.fromJson(testTime, DateTimeType));
assertThat("11", is(m.hourOfDay().getAsText()));
}
public Gson initCustomGSON() {
final GsonBuilder builder = new GsonBuilder();
JodaTimeConverters converter = new JodaTimeConverters();
converter.registerAll(builder);
return builder.create();
}
}
You have a few issues with this code.
Your first problem is that : is an operator in Json. You are interpreting an unescaped String with a : in it, so Gson is interpreting it as key : value. Your test string needs to surround the entire text date with quotes to prevent this from happening, e.g.
String testTime = "\"2015-07-29T11:00:00.001Z\"";
You were using ISODateTimeFormat.dateHourMinuteSecondMillis() in your code. However, the format pattern for this is yyyy-MM-dd'T'HH:mm:ss.SSS, which as you can see does not include a time zone. You want to be using ISODateTimeFormat.dateTime(), whose pattern is yyyy-MM-dd'T'HH:mm:ss.SSSZZ, which does have a time zone.
private static final DateTimeFormatter DATE_FORMAT = ISODateTimeFormat.dateTime();
Once these two changes are made, the DateTime object is finally properly created... but it will be created in your local time zone, not in UTC (it will correctly adjust the time to your zone. You can easily switch this back to UTC by doing:
DateTime m = ((DateTime) (gson.fromJson(testTime, DateTimeType))).withZone(DateTimeZone.UTC);
Once you make these three changes, your tests will pass. However: I strongly advise against using JsonSerializer and JsonDeserializer, they have been deprecated in favor of TypeAdapter, whose streaming API is significantly more performant:
New applications should prefer TypeAdapter, whose streaming API is more efficient than this interface's tree API.
I am aware the user guide provides code for how to do it with the JsonSerializer / JsonDeserializer API, but that's just because they haven't yet updated it.
It would simply be something like this:
public class DateTimeAdapter extends TypeAdapter<DateTime> {
private static final DateTimeFormatter FORMAT = ISODateTimeFormat.dateTime();
public DateTime read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return null;
}
String dateString = reader.nextString();
if(dateString.length() == 0) return null;
return FORMAT.parseDateTime(dateString);
}
public void write(JsonWriter writer, DateTime value) throws IOException {
if (value == null) {
writer.nullValue();
return;
}
writer.value(FORMAT.print(value));
}
}

Gson: set date formatter for timestamp and timezone

What pattern should I use for date format 1418805300000-0100 ? (Timestamp and timezone)
GsonBuilder().setDateFormat("?????????????-Z")
Solution:
create new GSON with adapters
private static Gson createGson(){
return new GsonBuilder().disableHtmlEscaping()
.registerTypeHierarchyAdapter(Date.class, new DateTimeSerializer())
.registerTypeHierarchyAdapter(Date.class, new DateTimeDeserializer())
.create();
}
public static MyClass fromJson(String json) {
return createGson().fromJson(json, MyClass.class);
}
public String toJson() {
return createGson().toJson(this);
}
JSON Serializer
private static class DateTimeSerializer implements JsonSerializer<Date> {
#Override
public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
// hodgie code
return new JsonPrimitive(src.getTime() + new SimpleDateFormat("Z").format(src));
}
}
Deserializer
private static class DateTimeDeserializer implements JsonDeserializer<Date> {
#Override
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
// hodgie code
return new Date(Long.valueOf((json).getAsString().substring(0, 13)));
}
}
GsonBuilder#setDateFormat(String) uses the String provided as an argument for creating SimpleDateFormat instances. SimpleDateFormat does not provide any patterns for generating a timestamp. You won't be able to achieve what you want with setDateFormat. A custom TypeAdapter seems appropriate.

Gson: Unable to parse date

My json input looks like:
{ user: "sample-user", date : 1225864800 }
And my DateDeserializer class is:
private class DateDeserializer implements JsonDeserializer<Date>
{
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException
{
System.out.println("Deserializer...");
return new Date(json.getAsJsonPrimitive().getAsLong());
}
}
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Date.class, new DateDeserializer());
gson.fromJson(new FileReader("input.json"), MyType.class);
Even after setting up everything, I'm getting
java.text.ParseException: Unparseable Date: "1225864800"
Am I doing anything wrong here? Please help.
You're trying to deserialize the entire Json structure as a Date, not just the date field. You'd need something like:
User user = jsonElement.getAsJsonObject().get( "user" ).getAsString();
Date date = new Date(jsonElement.getAsJsonObject().get( "date" ).getAsLong());
You could write an adapter for the whole MyType class so that you can keep standard date deserialization on other places (if any), and limit your specific deserialization only inside MyType.
To explain better what I mean, here's the code you can copy&paste&run:
package stackoverflow.questions.q14197557;
import java.lang.reflect.Type;
import java.util.Date;
import com.google.gson.*;
public class Q14197557 {
public static class MyType {
String user;
Date date;
#Override
public String toString() {
return "MySample [user=" + user + ", date=" + date + "]";
}
}
public static class MySampleDeserializer implements JsonDeserializer<MyType> {
public MyType deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
if (json == null)
return null;
else {
MyType s = new MyType();
s.user = json.getAsJsonObject().get("user").getAsString();
s.date = new Date(json.getAsJsonObject().get("date").getAsLong());
return s;
}
}
}
public static void main(String[] args) {
String json = "{ user: \"sample-user\", date : 1225864800 }";
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(MyType.class, new MySampleDeserializer());
MyType s = gsonBuilder.create().fromJson(json, MyType.class);
System.out.println("My Type: " + s);
}
}

Categories