I am using to convert JSON String to POJO Class using Gson. In Pojo the attribute are of java.util.Date type. While gson maps Json String to Pojo objects it is removing timestamp.
Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.S'Z'").create();
ClassBaseModel baseModel = gson.fromJson(request, ClassBaseModel.class);
Attribute Original Value in String Json is - "orderDate": "2021-12-01T07:16:31Z"
After It is converted into POJO - 2021-12-01
Expected is - 2021-12-01T07:16:31Z
I am not sure what wrong I am doing. Can somebody please point out.
While Deserializing you have to register deserializer type in gson using registerTypeAdapter
Here is how I have tried to do so, and also
Request class:
class OrderInfoRequest {
String orderDate;
public OrderInfoRequest(String orderDate) {
this.orderDate = orderDate;
}
}
Main Mapper class
class OrderInfo {
OffsetDateTime orderDate;
public OffsetDateTime getOrderDate() {
return orderDate;
}
public void setOrderDate(OffsetDateTime orderDate) {
this.orderDate = orderDate;
}
#Override
public String toString() {
return "OrderInfo{" +
"orderDate=" + orderDate +
'}';
}
}
Sample code
public class DateMain {
public static void main(String[] args) {
OrderInfoRequest orderInfoRequest = new OrderInfoRequest("2021-12-01T07:16:31Z");
Gson gson = new GsonBuilder()
.registerTypeAdapter(OffsetDateTime.class, (JsonDeserializer<OffsetDateTime>) (json, typeOfT, context) -> OffsetDateTime.parse(json.getAsString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME))
.create();
String requestJson = gson.toJson(orderInfoRequest);
System.out.println("Request json");
System.out.println(requestJson);
OrderInfo orderInfo = gson.fromJson(requestJson, OrderInfo.class);
System.out.println("After parsing pojo");
System.out.println(orderInfo);
System.out.println("printing full date: " + orderInfo.orderDate.format(DateTimeFormatter.ofPattern("yyyy/MM/dd, hh:mm")));
}
}
Also as told by #Jens avoid using java.util.Date class from today onwards if you haven't.
Related
I have a JSON Data Object class as follows:
public class Plugins {
private String id;
private String name;
#JsonProperty("created_at")
private long createdAt;
}
Where createdAt is the long timestamp of the creation date. I use this class to back up a Jackson ObjectMapper object parsing JSON data from an external API call. I was wondering if it is possible to have Jackson convert created_at automatically to a readable date format and store in Java as a String or Date flavour?
if it is possible to have Jackson convert created_at automatically to a readable date format and store in Java as a String or Date flavour?
Date is obsolete and discouraged to be used.
Since Java 8 (which was released about 10 years ago) we have modern Time API which includes Instant, LocalDateTime and other classes from the java.time package.
You can change your POJO to make it store date-time information properly without the need to change the JSON payload. I.e. created_at can be received as a long value like 1665148545 and translated into ZonedDateTime (or other date-time representations like Istant, LocalDateTime).
public class Plugins {
private String id;
private String name;
private ZonedDateTime createdAt;
public Plugins(#JsonProperty("id") String id,
#JsonProperty("name") String name,
#JsonProperty("created_at") long createdAt) {
this.id = id;
this.name = name;
this.createdAt = Instant.ofEpochSecond(createdAt)
.atZone(ZoneId.of("UTC"));
}
// getters, toString(), etc.
}
Usage example:
String json = """
{
"id": "1",
"name": "someName",
"created_at": 1665148545
}""";
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(json, Plugins.class));
Output:
lugins{id='1', name='someName', createdAt=2022-10-07T13:15:45}
You just need to register JavaTimeModule module and use required type from Java-8 time package. Take a look on below example:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
public class DateApp {
private final static JsonMapper JSON_MAPPER = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.addModule(new JavaTimeModule())
.build();
public static void main(String[] args) throws Exception {
String json = "{\"id\": \"1\",\"name\":\"someName\",\"created_at\": 1665148545}";
Plugins plugins = JSON_MAPPER.readValue(json, Plugins.class);
System.out.println(plugins);
}
}
#Data
#NoArgsConstructor
#AllArgsConstructor
class Plugins {
private String id;
private String name;
#JsonProperty("created_at")
private Instant createdAt;
}
Above code prints:
Plugins(id=1, name=someName, createdAt=2022-10-07T13:15:45Z)
Using Custom Deserialiser in jackson
You can achieve the date conversion from long to String or Date by using the custom deserialiser. This custom deserialiser will convert the long value from the json into the defined date format(either Date or String).
Please Note: Here, I have converted the epoch value into the String datatype. In case if Date datatype is needed, you can change the implementation of the deserialize method of CustomDateSerializer class accordingly.
You need to use the below annotation to the fields on which custom deserialisation is required.
#JsonDeserialize(using = CustomDateSerializer.class)
Please find the code below:
Plugins.java
public class Plugins {
private String id;
private String name;
#JsonDeserialize(using = CustomDateSerializer.class)
#JsonProperty("created_at")
private String createdAt;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCreatedAt() {
return createdAt;
}
public void setCreatedAt(String createdAt) {
this.createdAt = createdAt;
}
#Override
public String toString() {
return "Plugins{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", createdAt='" + createdAt + '\'' +
'}';
}
}
CustomDateSerializer.java
public class CustomDateSerializer extends StdDeserializer<String> {
public static String pattern = "dd MMM yyyy hh:mm:ss";
public CustomDateSerializer() {
this(StdDeserializer.class);
}
protected CustomDateSerializer(Class<?> c) {
super(c);
}
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
SimpleDateFormat formatter = new SimpleDateFormat(pattern);
return formatter.format(new Date(jsonParser.getLongValue()));//change the implementation of deserialise method if date format is needed.
}
}
Test.java
public class Test {
public static void main(String[] args) throws JsonProcessingException {
//For sample input json, here i have used Text Blocks feature available from JDK 15 to have the string in readable format.
String json = """
{
"id":"1",
"name":"test",
"created_at":1665158083000
}
""";
ObjectMapper mapper = new ObjectMapper();
Plugins test = mapper.readValue(json,Plugins.class);
System.out.println(test);
}
}
Output:
Plugins{id='1', name='test', createdAt='07 Oct 2022 09:24:43'}
I have a JSON Object with the following structure:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Record {
private String tid;
private String eid;
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
#JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime ts;
public Record() {
}
}
When I try and convert this to a string using the following method,
public static String convertToJSON(Object object) {
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
String json;
try {
json = ow.writeValueAsString(object);
LOGGER.info("(convertToJSON) JSON: {}", json);
} catch (Exception e) {
throw new JSONException("Error Converting JSON To string", e);
}
return json;
}
I get a comma separated value as below:
"ts" : [ 2021, 4, 4, 20, 17, 50, 522000000 ]
I want this printed as ISO-UTC "ts" : "2021-04-04 20:46:44:932" or "ts" : "2021-04-04 20:46:44:932z".
One was would be to add #JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss:SSS") to the record object but I would not prefer not changing the Json object definition as this has inflight data impact in production.
Is there a way to change my convertToJSON method to achieve this.
There are a lot of Stackoverlfow posts about that. It's quite simple, please try :
public class MyTest {
#Data
public static class Foo {
private LocalDateTime date;
}
public static void main(String[] args) throws JsonProcessingException {
Foo foo = new Foo();
foo.setDate(LocalDateTime.now());
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
System.out.println(objectMapper.writeValueAsString(foo));
}
}
The result will be : {"date":"2021-04-05T11:19:38.999185"}. Notice that i used #Data from Lombok to simplify.
For JavaTimeModule you will probably need to add Maven dependency :
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.12.2</version>
</dependency>
I have the following JSON string in REST response:
"09:41:50 CET"
For the corresponding POJO mapper class has a Date type for this field. So I've tried Jackson and GSON to map JSON to Java Object, but both failed with the following messages:
GSON: java.text.ParseException: Failed to parse date ["09:41:50 CET"]: Invalid number: 09:4
Jackson: InvalidFormatException: Cannot deserialize value of type `java.util.Date` from
String "09:41:50 CET": not a valid representation
Sadly I cannot modify in the POJO class the type to string or anything else, because I get those POJO classes from mvn dependency.
Try with this:
public static void main(String[] args) throws ParseException {
String jsonStr = "{ \"date\" : \"09:41:50 CET\" }";
Gson gson = new GsonBuilder().setDateFormat("HH:mm:ss").create();
JsonElement element = gson.fromJson (jsonStr, JsonElement.class);
OnlyDate date =gson.fromJson(element, new TypeToken<OnlyDate>(){}.getType());
System.out.println(date.getDate());
}
My example DTO is:
public class OnlyDate implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
#SerializedName("date")
private Date date ;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
You have to specify the dateFormat of your gson Element
Not sure what kind of rest you have however if you are using spring rest you can do it by implementing custom Converter check the example at https://www.baeldung.com/spring-mvc-custom-data-binder.
Since Jackson v2.0, you can use #JsonFormat annotation directly on Object members;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm:ss", timezone="CET")
private Date date;
I'm having problems to de-serialize an object containing a JSON field in spring. I have a DataTable data structure that has both String fields and a field that should contain a JSONObject:
public class DataTable {
private String identifier;
private String version;
private JSONObject content;
//getters and setters
}
I want to be able to persist this object and retrieve it later on.
When I post such a DataTable object to my Controller and try to retrieve it later, my content field will be empty, regardless of the contents I posted:
{
"identifier": "id1",
"version": "0.0.1",
"content": {
"empty": true
}
}
It seems that jackson is not able to correctly de-serialize the JSONObject type field and will just leave it empty instead. How can I make it deserialize the field correctly?
Jackson has the jackson-datatype-json-org module, which allows us to combine json.org objects with POJOs or deserialize to json.org objects as the top level object.
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-json-org</artifactId>
<version>${jackson2.version}</version>
</dependency>
Here is a test (don't forget to register the JsonOrgModule).
public class JsonOrgJacksonTest {
private final String json =
"{" +
" \"id\": \"one\"," +
" \"content\": {" +
" \"foo\": \"bar\"," +
" \"baz\": \"blah\"" +
" }" +
"}";
#Test
public void testJsonOrg() throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JsonOrgModule());
Model model = mapper.readValue(json, Model.class);
assertThat(model.getId()).isEqualTo("one");
assertThat(model.getContent().get("foo")).isEqualTo("bar");
assertThat(model.getContent().get("baz")).isEqualTo("blah");
}
public static class Model {
private String id;
private JSONObject content;
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public JSONObject getContent() { return content; }
public void setContent(JSONObject content) { this.content = content; }
}
}
JSONObject is not supposed to be serialized. It is for an internal use. If you want to have a JSON representation of this data structure without a model (dedidated class) try converting it into a Map.
Map<String,Object> result =
new ObjectMapper().readValue(dataTable.getContent(), HashMap.class);
You can use a custom deserializer for this:
public class Test {
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
final SimpleModule module = new SimpleModule("configModule", Version.unknownVersion());
module.addDeserializer(DataTable.class, new DeSerializer());
mapper.registerModule(module);
// DataTable readValue = mapper.readValue(<xml source>);
}
}
class DeSerializer extends StdDeserializer<DataTable> {
protected DeSerializer() {
super(DataTable.class);
}
#Override
public DataTable deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// use p.getText() and p.nextToken to navigate through the xml and construct DataTable object
return new DataTable();
}
}
Using Play Framework, I serialize my models via GSON. I specify which fields are exposed and which aren't.
This works great but I'd also like to #expose method too. Of course, this is too simple.
How can I do it ?
Thanks for your help !
public class Account extends Model {
#Expose
public String username;
#Expose
public String email;
public String password;
#Expose // Of course, this don't work
public String getEncodedPassword() {
// ...
}
}
The best solution I came with this problem was to make a dedicated serializer :
public class AccountSerializer implements JsonSerializer<Account> {
#Override
public JsonElement serialize(Account account, Type type, JsonSerializationContext context) {
JsonObject root = new JsonObject();
root.addProperty("id", account.id);
root.addProperty("email", account.email);
root.addProperty("encodedPassword", account.getEncodedPassword());
return root;
}
}
And to use it like this in my view:
GsonBuilder gson = new GsonBuilder();
gson.registerTypeAdapter(Account.class, new AccountSerializer());
Gson parser = gson.create();
renderJSON(parser.toJson(json));
But having #Expose working for a method would be great: it would avoid making a serializer just for showing methods!
Check out Gson on Fire: https://github.com/julman99/gson-fire
It's a library I made that extends Gson to handle cases like exposing method, results Post-serialization, Post-deserialization and many other things that I've needed over time with Gson.
This library is used in production in our company Contactive (http://goo.gl/yueXZ3), on both Android and the Java Backend
Gson's #Expose seem to only be supported on fields. There is an issue registered on this: #Expose should be used with methods.
Couple different options based on Cyril's answer:
Custom serializer with a shortcut:
public static class Sample
{
String firstName = "John";
String lastName = "Doe";
public String getFullName()
{
return firstName + " " + lastName;
}
}
public static class SampleSerializer implements JsonSerializer<Sample>
{
public JsonElement serialize(Sample src, Type typeOfSrc, JsonSerializationContext context)
{
JsonObject tree = (JsonObject)new Gson().toJsonTree(src);
tree.addProperty("fullName", src.getFullName());
return tree;
}
}
public static void main(String[] args) throws Exception
{
GsonBuilder gson = new GsonBuilder();
gson.registerTypeAdapter(Sample.class, new SampleSerializer());
Gson parser = gson.create();
System.out.println(parser.toJson(new Sample()));
}
-OR-
Annotation based serializer
public static class Sample
{
String firstName = "John";
String lastName = "Doe";
#ExposeMethod
public String getFullName()
{
return firstName + " " + lastName;
}
}
public static class MethodSerializer implements JsonSerializer<Object>
{
public JsonElement serialize(Object src, Type typeOfSrc, JsonSerializationContext context)
{
Gson gson = new Gson();
JsonObject tree = (JsonObject)gson.toJsonTree(src);
try
{
PropertyDescriptor[] properties = Introspector.getBeanInfo(src.getClass()).getPropertyDescriptors();
for (PropertyDescriptor property : properties)
{
if (property.getReadMethod().getAnnotation(ExposeMethod.class) != null)
{
Object result = property.getReadMethod().invoke(src, (Object[])null);
tree.add(property.getName(), gson.toJsonTree(result));
}
}
}
catch (Exception ex)
{
ex.printStackTrace();
}
return tree;
}
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD) //can use in method only.
public static #interface ExposeMethod {}
public static void main(String[] args) throws Exception
{
GsonBuilder gson = new GsonBuilder();
gson.registerTypeAdapter(Sample.class, new MethodSerializer());
Gson parser = gson.create();
System.out.println(parser.toJson(new Sample()));
}