WRT to the following question:
Jersey + Jackson JSON date format serialization - how to change the format or use custom JacksonJsonProvider.
I wish to know
Is Jackson specifying that the json date format should be normalised to a unix time integer?
Follow-up questions ...
Has there been a change in its stance anytime recently?
Shouldn't a date format be normalised to the same format provided by jaxb xml output?
why/why not?
any effort put into resolving this issue?
has RestEasy provided a json provider mitigation that would output json date in a generally recognisable date format?
Sorry people for yelling out loud - I found the answers here
http://wiki.fasterxml.com/JacksonFAQDateHandling,
here
http://wiki.fasterxml.com/JacksonFAQ#Serializing_Dates,
here
http://wiki.fasterxml.com/JacksonHowToCustomSerializers
here
http://jackson.codehaus.org/1.1.2/javadoc/org/codehaus/jackson/map/util/StdDateFormat.html
Using the #JsonSerialize(using= ... ) way:
public class JsonStdDateSerializer
extends JsonSerializer<Date> {
private static final DateFormat iso8601Format =
StdDateFormat.getBlueprintISO8601Format();
#Override
public void serialize(
Date date, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
// clone because DateFormat is not thread-safe
DateFormat myformat = (DateFormat) iso8601Format.clone();
String formattedDate = myformat.format(date);
jgen.writeString(formattedDate);
}
}
This is also controlled by a feature on the ObjectMapper (at least in 1.9.11, and possibly earlier):
ObjectMapper om = new ObjectMapper();
om.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
om.writer().writeValue(System.out, objectWithDateProperty);
I don't see how to declaratively do the equivalent on the object definition itself.
Related
Have a java object with a LocalDateTime property that I want to serialize and deserialize with gson.
I am using ISO_INSTANT as the format for datetime in the json output
Have tried creating a TypeAdaptor for GSON with the help from this post
https://stackoverflow.com/a/39193077/1196875
But get "Unsupported field: InstantSeconds"
Maybe I have gotten the date conversion messed up?
class LocalDateTimeSerializer implements JsonSerializer<LocalDateTime> {
#Override
public JsonElement serialize(LocalDateTime date, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(
DateTimeFormatter.ISO_INSTANT.withZone(ZoneId.systemDefault()).withLocale(Locale.getDefault()).format(date)); // "yyyy-mm-ddThhMMssZ"
}
}
Your issue is unrelated to Gson, it also occurs when just running the date formatting code:
LocalDateTime date = LocalDateTime.now()
DateTimeFormatter.ISO_INSTANT
.withZone(ZoneId.systemDefault())
.withLocale(Locale.getDefault())
.format(date);
Exception:
Exception java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: InstantSeconds
at LocalDate.get0 (LocalDate.java:709)
at LocalDate.getLong (LocalDate.java:688)
at LocalDateTime.getLong (LocalDateTime.java:718)
at DateTimePrintContext$1.getLong (DateTimePrintContext.java:205)
at DateTimePrintContext.getValue (DateTimePrintContext.java:308)
at DateTimeFormatterBuilder$InstantPrinterParser.format (DateTimeFormatterBuilder.java:3459)
at DateTimeFormatterBuilder$CompositePrinterParser.format (DateTimeFormatterBuilder.java:2402)
at DateTimeFormatter.formatTo (DateTimeFormatter.java:1849)
at DateTimeFormatter.format (DateTimeFormatter.java:1823)
at (#5:1)
It appears (based on this comment) that you should convert the LocalDateTime to a ZonedDateTime before formatting it.
I have a problem with jackson.
I have a date in the database in the following format :
2018-01-01 13:00:00
I convert the Database entry to a "Date" object.
I now want the jackson objectmapper to print it out date in the following format like :
2018-01-01T13:00:00.000+0200
Now I have the following code where I set up my objectmapper
private static ObjectMapper init() {
ObjectMapper mapper = new ObjectMapper();
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM dd'T'HH:mm:ss.SSSZ");
dateFormat.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
mapper.setDateFormat(dateFormat);
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper;
}
Now the problem is, that the date is printed out like this :
2018-01-01T15:00:00.000+0200
While formatting the date and adding the timezone information, it calculates the new time. All my dates use the "Europe/Berlin" timezone but I don't save it with this information in the database.
Is there any way how to add the timezone information without changing the time?
Regards
The solution was to use a custom DateFormat class, where I can handle the formatting.
Very important :
You have to implement the "clone" method. If you use Eclipse it only shows an error for the missing format and parse method. The clone method was missing and i got some nullpointer exceptions at the DateFormat class.
mapper.setDateFormat(JsonDateFormat);
public class JsonDateFormat extends DateFormat implements Serializable, Cloneable {
#Override
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
// TODO DO STUFF
return toAppendTo;
}
#Override
public Date parse(String source, ParsePosition pos) {
// TODO DO STUFF
return null;
}
#Override
public Object clone() {
return new JsonDateFormat();
}
}
I've got some JSON that has timestamps in seconds (i.e. a Unix timestamp):
{"foo":"bar","timestamp":1386280997}
Asking Jackson to deserialize this into an object with a DateTime field for the timestamp results in 1970-01-17T01:11:25.983Z, a time shortly after the epoch because Jackson is assuming it to be in milliseconds. Aside from ripping apart the JSON and adding some zeros, how might I get Jackson to understand the seconds timestamp?
I wrote a custom deserializer to handle timestamps in seconds (Groovy syntax).
class UnixTimestampDeserializer extends JsonDeserializer<DateTime> {
Logger logger = LoggerFactory.getLogger(UnixTimestampDeserializer.class)
#Override
DateTime deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String timestamp = jp.getText().trim()
try {
return new DateTime(Long.valueOf(timestamp + '000'))
} catch (NumberFormatException e) {
logger.warn('Unable to deserialize timestamp: ' + timestamp, e)
return null
}
}
}
And then I annotated my POGO to use that for the timestamp:
class TimestampThing {
#JsonDeserialize(using = UnixTimestampDeserializer.class)
DateTime timestamp
#JsonCreator
public TimestampThing(#JsonProperty('timestamp') DateTime timestamp) {
this.timestamp = timestamp
}
}
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="s")
public Date timestamp;
edit: vivek-kothari suggestion
#JsonFormat(shape=JsonFormat.Shape.NUMBER, pattern="s")
public Timestamp timestamp;
A very similar approach to that of #DrewStephens's which uses the Java SE TimeUnit API (introduced in JDK1.5) instead of plain String concatenation and is thus (arguably) a little bit cleaner and more expressive:
public class UnixTimestampDeserializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser parser, DeserializationContext context)
throws IOException, JsonProcessingException {
String unixTimestamp = parser.getText().trim();
return new Date(TimeUnit.SECONDS.toMillis(Long.valueOf(unixTimestamp)));
}
}
Specifying your custom deserializer (UnixTimestampDeserializer) on the affected Date field(s):
#JsonDeserialize(using = UnixTimestampDeserializer.class)
private Date updatedAt;
I had this exact issue, except my ZonedDateTime objects got turned to unix-timestamps (seconds) and I needed them in milliseconds (to initialize JS Date objects on the browser).
Implementing a custom serializer/deserializer looked like too much work for something that should be pretty straightforward, so I looked elsewhere and found that I can just configure the object mapper for the desired result.
Because my application already overrides the default ObjectMapper provided by Jersey, I just had to disable SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS.
Here's my code
#Provider
public class RPObjectMapperProvider implements ContextResolver<ObjectMapper> {
final ObjectMapper defaultObjectMapper;
public RPObjectMapperProvider() {
defaultObjectMapper = new ObjectMapper();
// this turned ZonedDateTime objects to proper seconds timestamps
defaultObjectMapper.enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// disable to serialize dates for millis timestamps
defaultObjectMapper.disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
// using Java8 date time classes
defaultObjectMapper.registerModule(new JavaTimeModule());
}
#Override
public ObjectMapper getContext(Class<?> type) {
return defaultObjectMapper;
}
}
And that's it
I am using Mule 3.5.2 and I have a REST service that send and receives JSON messages. The service is for both Norway and Sweden. All dates are send as strings, but Sweden and Norway have different formats. I know by the URL which country calls our service. I am using custom date serializers and deserializers.
I can kind of cheat when receiving JSON message, the formats are different enough that in my custom deserializer I can try one format. If that fails I just try the other. However: How do I serialize in the right format?
It doesn't seem to exist any way to send a parameter to the serializer that this particular message goes to Norway so use this date format...and the next goes to Sweden use another format etc.
Code that I have, that may help:
#GET
#Path("{country:se|no}/{id}")
public Response webservice(#PathParam("country") String country,
#PathParam("id") String id) {
country = country.toUpperCase();
WebServiceResponse response = doWebServiceStuff(id, country)
return Response.ok(reponse).build();
}
Response has a .language() method, but that seems just to affect the headers.
#JsonAutoDetect
public class WebServiceResponse {
#JsonSerialize(using = JsonDateSerializer.class)
#JsonDeserialize(using = JsonDateDeserializer.class)
private Date date;
public void setDate(Date d) { this.date = d; }
public Date getDate() { return this.date; }
}
Serializer today. I would like it to adapt whether it is going to a Norwegian user or Swedish user.
public class JsonDateSerializer extends JsonSerializer<Date> {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
#Override
public void serialize(Date date, JsonGenerator gen, SerializerProvider provider) throws IOException, JsonProcessingException {
String formattedDate = dateFormat.format(date);
gen.writeString(formattedDate);
provider.getConfig().getDateFormat()
}
}
Deserializer. It have about the same problem but I could surround it with try/catch...if the Swedish date format is not valid, try to parse with the Norwegian number instead and throw RuntimeException if it still is a problem.
public class JsonDateDeserializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser parser, DeserializationContext deserializationContext)
throws IOException, JsonProcessingException {
String dateText = parser.getText();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
return dateFormat.parse(dateText);
} catch (ParseException e) {
// TODO Auto-generated catch block
throw new RuntimeException("Can't parse date " + dateText, e);
}
}
}
Btw...I am using codehaus version of Jackson as that seems to work with Mule. I tried FasterXML-version but that didn't use my custom serializers nor the new fancy annotation based formatters (so you don't need custom serializers). Version 1.9.11 to be exact.
Again: the question is, how can I control the date format based on conditions from for instance the URL (more exactly from the outside) for each message. I know in webservice-method (the first code block) which country I am talking with but not in the serializer...
Outcome
The solution I got provided below was really a solution that would fix my issue, but I do beleive it is not possible to get it working in Mule 3.5.2 EE. However, if using Mule 3.6.0 or 3.7.0 (that seems to be the latest version now) this will probably be the solution for you, as well as others that might use other frameworks.
Not mentioned in the comments, but I did try commenting out "String country = uriInfo.getPathParameters().getFirst("country");" and hardcoded country to "no" and I did get Norwegian date format. When recompiling it with "se" I did get Swedish format, so the solution really works even though I could not get it to work.
Update2
I did have a discussion with Mule Support. In 3.5.x of Mule and older, jersey-json and jackson-jaxrs are shipped and it is a bit random (and depends on different environments) which it loads. One can remove jersey-json from $MULE_HOME/lib/opt. 3.6.x and later will only ships which jackson-jaxrs.
As I sit on a system with many flows that works, I have not the time to test if removing jersey-json doesn't break anything (as removing the file will affect all flows and not just this one). Basicly 3.6.x and later will have the better control over Jersey (choosing Providers etc.), and will make it possible to get this working.
"...how can I control the date format based on conditions from for instance the URL (more exactly from the outside) for each message"
Though a little bit more work, one way is to create different ObjectMappers configured differently for each type of request. To determine which one will be used we can make the decision inside a ContextResolver. We could inject a UriInfo into the resolver, to get the value of the #PathParam("country"). Then make the decision from that, which mapper will be used. For example
#Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper sweMapper;
private final ObjectMapper norMapper;
private final ObjectMapper defaultMapper;
#Context
private UriInfo uriInfo;
public ObjectMapperContextResolver() {
defaultMapper = new ObjectMapper();
sweMapper = new ObjectMapper();
SimpleModule sweModule = new SimpleModule("SweModule", new Version(1,0,0,null));
sweModule.addDeserializer(Date.class, new JsonDateDeserializer(sweFormat));
sweModule.addSerializer(Date.class, new JsonDateSerializer(sweFormat));
sweMapper.registerModule(sweModule);
norMapper = new ObjectMapper();
SimpleModule norModule = new SimpleModule("NorModule", new Version(1,0,0,null));
norModule.addDeserializer(Date.class, new JsonDateDeserializer(norFormat));
norModule.addSerializer(Date.class, new JsonDateSerializer(norFormat));
norMapper.registerModule(norModule);
}
#Override
public ObjectMapper getContext(Class<?> type) {
String country = uriInfo.getPathParameters().getFirst("country");
if (country == null) {
return defaultMapper;
}
switch (country) {
case "se": return sweMapper;
case "no": return norMapper;
default: return defaultMapper;
}
}
}
The reason we are using three mapper is for one, they are expensive to create. Secondly, configuring them is not thread-safe. And since the ContextResolver will be a singleton, only one of the mappers will be used for the application. So we just create three for different cases.
If you go this route, you should also remember to remove the serialization annotations from the field.
UPDATE
So with Jersey 2.6, it seems there is a problem with the above solution. It just fails on startup. The solution I was able to find was to not use this dependency
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>${jersey-version}</version>
</dependency>
Seem loading of some part of this module causes it to fail. Instead just use the Pure Jackson dependency (which the above actually pulls in and uses itself).
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.13</version>
</dependency>
Note: jersey-json:1.6 uses 1.7.1 of the above dependency. I just switched to use the latest 1.x version. So you may or may not want to switch it back.
Get rid of any you might have for the old artifact, i.e
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
And add the Jackson package as a package to scan
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>
com.your.packages,
org.codehaus.jackson.jaxrs
</param-value>
</init-param>
Or if you are using some Mule specific configuration, just register these
org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider
org.codehaus.jackson.jaxrs.JacksonMappingExceptionMapper
org.codehaus.jackson.jaxrs.JacksonParseExceptionMapper
I am having some problem mapping my Java Data Type to standard Schema Date data type.
I have a simple class that I annotated like this. The period instance variable is of Java Date object type.
#XmlAccessorType(value = XmlAccessType.NONE)
public class Chart {
#XmlElement
private double amount;
#XmlElement
private double amountDue;
#XmlElement
private Date period;
//constructor getters and setters
}
Here is my Web Service
#WebService
public class ChartFacade {
#WebMethod
public Chart getChart() throws ParseException {
SimpleDateFormat df = new SimpleDateFormat("yyyy-mm-dd");
Chart chart = new Chart(20.0,20.5, df.parse("2001-01-01"));
return chart;
}
}
My problem is it returns the date data in a format not according to what I am expecting.
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:getChartResponse xmlns:ns2="http://ss.ugbu.oracle.com/">
<return>
<amount>20.0</amount>
<amountDue>20.5</amountDue>
**<period>2001-01-01T00:01:00+08:00</period>**
</return>
</ns2:getChartResponse>
</S:Body>
</S:Envelope>
I wanted the period element to be returned like this
<period>2001-01-01</period>
Is there any way I can achieve this?
You can do the following to control the schema type:
#XmlElement
#XmlSchemaType(name="date")
private Date period;
For More Information:
http://bdoughan.blogspot.com/2011/01/jaxb-and-datetime-properties.html
Use #XmlJavaTypeAdapter annotation and you can marshal/unmarshal your fields any way you want.
Cannot tell though if it's the simplest way.
And note also that it may harm interoperability with any code that would try to use your WSDL. The programmers for that other code would see xsd:string as the field type, and therefore will have to do formatting and parsing manually (just like you do, yes), introducing who knows how many bugs. So please consider if the xsd:date a bad choice really.
Stolen from here:
#XmlJavaTypeAdapter(value=DateAdapter.class, type=Date.class)
Date someDate;
...
public class DateAdapter extends XmlAdapter<String, Date> {
// the desired format
private String pattern = "MM/dd/yyyy";
public String marshal(Date date) throws Exception {
return new SimpleDateFormat(pattern).format(date);
}
public Date unmarshal(String dateString) throws Exception {
return new SimpleDateFormat(pattern).parse(dateString);
}
}
UPDATE: as was mentioned by #Blaise Doughan, a much shorter way is to annotate the date with
#XmlSchemaType("date")
Date someDate;
Despite it is still not clear why timezone information is not generated for the date, this code works in practice and requires much less typing.
Your Chart constructor seems to be parsing the formatted date string back into a Date, which is then being serialized using the default format to the XML response.
I guess using private String period; (and fixing the constructors) should work