pipilam
pipilam

Reputation: 587

LocalDateTime format in Spring Boot

Hey I've got the same problem as here: JSON Java 8 LocalDateTime format in Spring Boot I tried solutionts from there and it does not work. Could someone tell me what I did wrong?

I added

spring.jackson.serialization.write-dates-as-timestamps=false

to application.property My model class looks like this:

package bookrental.model.book;

import bookrental.model.account.User;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.util.ISO8601DateFormat;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.*;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.Date;

@Entity
@Getter
@Setter
@EqualsAndHashCode
@AllArgsConstructor
@NoArgsConstructor
public class BookRentals {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    @OneToOne
    private Book book;
    @OneToOne
    private User user;
    @JsonFormat(pattern = ("yyyy/MM/dd HH:mm:ss"))
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    private LocalDateTime dateOfRental;

    public BookRentals(Book book, User user) {
        this.book = book;
        this.user = user;
    }

}

I set time like this:

private BookRentals prepareBookToRent(int userID, Book book) {
        BookRentals bookRentals = new BookRentals(book, new User(userID));
        bookRentals.setDateOfRental(LocalDateTime.now());
        return bookRentals;
    }

I added dependency:

<dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.9.7</version>
        </dependency>

And my JSON, looks like this:

[
    {
        "book": {
            "author": "Henryk Sienkiewicz",
            "category": "powieść historyczna",
            "id": 1,
            "title": "Krzyżacy"
        },
        "class": "bookrental.model.book.BookRentals",
        "dateOfRental": {
            "class": "java.time.LocalDateTime",
            "dayOfMonth": 19,
            "dayOfWeek": "WEDNESDAY",
            "dayOfYear": 353,
            "hour": 0,
            "minute": 13,
            "month": "DECEMBER",
            "monthValue": 12,
            "nano": 758649300,
            "second": 8,
            "year": 2018
        },
        "id": 1,
        "user": {
            "id": 2,
            "name": "piotri",
            "password": "123"
        }
    }
]

What else should I do?

I didn't try solutions with classes, because I dont know, where I should put them in what package. // EDIT After Erik's advice, pom.xml looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.book.rental.piotrek</groupId>
<artifactId>BookRental</artifactId>
<version>1.0-SNAPSHOT</version>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>8</source>
                <target>8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.1.RELEASE</version>
    <relativePath/>
</parent>


<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.22</version>
    </dependency>
    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
    </dependency>
    <dependency>
        <groupId>net.sf.flexjson</groupId>
        <artifactId>flexjson</artifactId>
        <version>2.1</version>
    </dependency>
</dependencies>

</project>

Upgrading didn't work. JSON:

[
    {
        "book": {
            "author": "Henryk Sienkiewicz",
            "category": "powieść historyczna",
            "id": 1,
            "title": "Krzyżacy"
        },
        "dateOfRental": {
            "dayOfMonth": 19,
            "dayOfWeek": "WEDNESDAY",
            "dayOfYear": 353,
            "hour": 11,
            "minute": 22,
            "month": "DECEMBER",
            "monthValue": 12,
            "nano": 884499000,
            "second": 17,
            "year": 2018
        },
        "id": 7,
        "user": {
            "id": 5,
            "name": "admin",
            "password": "123"
        }
    }
]

BookRentals:

package bookrental.model.book;

import bookrental.model.account.User;
import lombok.*;

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@Getter
@Setter
@EqualsAndHashCode
@AllArgsConstructor
@NoArgsConstructor
public class BookRentals {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    @OneToOne
    private Book book;
    @OneToOne
    private User user;
    private LocalDateTime dateOfRental;

    public BookRentals(Book book, User user) {
        this.book = book;
        this.user = user;
    }
}

//EDIT2

Hey. Accidently I found the cause of problem. I've got class that is responsible for finding exact rantals for exact user. When I go to /books/rentals/{userID} Im getting properly foramtted date. As you can see method return List<BookRentals>. In BookRentalsService I return ResponseEntity and I think because of that it looks like this. Do you know how to resolve it?

    package bookrental.service.account;

    import bookrental.model.book.BookRentals;
    import bookrental.repository.book.BookRentalsRepository;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

    import java.util.List;

    @Service
    public class UserRentalsService {

        private final BookRentalsRepository bookRentalsRepository;

        @Autowired
        public UserRentalsService(BookRentalsRepository bookRentalsRepository) {
            this.bookRentalsRepository = bookRentalsRepository;
        }

        public List<BookRentals> findUserRentalsByGivenID(int userID) {
            return bookRentalsRepository.getUserRentalsByGivenID(userID);
        }
    }


package bookrental.controller.account;

import bookrental.model.book.BookRentals;
import bookrental.service.account.UserRentalsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserRentalsController {

    private final UserRentalsService userRentalsService;

    @Autowired
    public UserRentalsController(UserRentalsService userRentalsService) {
        this.userRentalsService = userRentalsService;
    }

    @GetMapping("books/rentals/{userID}")
    public List<BookRentals> findUserRentalsByGivenID(@PathVariable int userID) {
        return userRentalsService.findUserRentalsByGivenID(userID);
    }
}

BookRentalsService

package bookrental.service.book.rentals;

import bookrental.model.account.User;
import bookrental.model.book.Book;
import bookrental.model.book.BookRentals;
import bookrental.repository.account.UserRepository;
import bookrental.repository.book.BookRepository;
import bookrental.repository.book.BookRentalsRepository;
import flexjson.JSONSerializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Service
public class BookRentalService {

    private final UserRepository userRepository;
    private final BookRepository bookRepository;
    private final BookRentalsRepository bookRentalsRepository;

    @Autowired
    public BookRentalService(BookRepository bookRepository, BookRentalsRepository bookRentalsRepository, UserRepository userRepository) {
        this.bookRepository = bookRepository;
        this.bookRentalsRepository = bookRentalsRepository;
        this.userRepository = userRepository;
    }

    ....

    public ResponseEntity<String> findAllRentals() {
        List<BookRentals> rentedBooks = new ArrayList<>();
        bookRentalsRepository.findAll().forEach(rentedBooks::add);
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "application/json; charset=utf-8");
        return new ResponseEntity<>(new JSONSerializer().exclude("book.class")
                .exclude("book.available")
                .exclude("dateOfReturn")
                .exclude("*.class")
                .exclude("user.amountOfCashToPay")
                .exclude("password")
                .serialize(rentedBooks), headers, HttpStatus.OK);
    }

}

Upvotes: 5

Views: 11829

Answers (4)

Justin Kersten
Justin Kersten

Reputation: 51

Here is what did the trick for me, running Spring Boot 3.0.2:

I just added the annotation

@JsonFormat(shape = Shape.STRING)

to the regarding field inside my dto, resulting in

@JsonFormat(shape = Shape.STRING)
private LocalDateTime timestamp;

This way the result went from

"timestamp": [
    2023,
    2,
    3,
    12,
    29,
    20,
    584451000
],

to the wanted ISO format

"timestamp": "2023-02-03T12:29:20.584451",

The reason this happens is because the default value for the json format is Shape.ANY and the now isolated ObjectMapper parses single integers to an array. The annotation forces the ObjectMapper to actually parse the value as a String.

Upvotes: 2

Michael Gantman
Michael Gantman

Reputation: 7808

In your code remove the annotation @JsonSerialize(using = LocalDateTimeSerializer.class) and modify your annotation
@JsonFormat(pattern = ("yyyy/MM/dd HH:mm:ss")) to this:
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy/MM/dd HH:mm:ss"))
See Spring Data JPA - ZonedDateTime format for json serialization for more details

Upvotes: 0

Erik Pragt
Erik Pragt

Reputation: 14677

This is an updated example using Spring 2.1.1:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;

@SpringBootApplication
class PipilamApplication {
    public static void main(String[] args) {
        SpringApplication.run(PipilamApplication.class, args);
    }
}

@RestController
class Controller {

    @GetMapping("/demo")
    public Demo demo() {
        return new Demo("pipilam",LocalDateTime.now());
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Demo {
    String name;
    LocalDateTime dateTime;
}

Connecting to http://localhost:8080/demo gives the following output:

{"name":"pipilam","dateTime":"2018-12-19T20:16:12.780268"}

No configuration nor annotations needed. Consider my previous answer deprecated. This is the pom.xml I used:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.stackoverflow</groupId>
    <artifactId>pipilam</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>pipilam</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

You can find the project in Github here: https://github.com/bodiam/spring-boot-java8-json-time

Upvotes: 1

Patric
Patric

Reputation: 1637

Do you even need the @JsonSerialize(using = LocalDateTimeSerializer.class) and @JsonDeserialize(using = LocalDateTimeDeserializer.class)?

I had the exact same problem, also using the jackson-datatype-jsr310 dependency. Switching to spring-boot-starter-json solved the issue for me:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-json</artifactId>
    <version>2.0.3.RELEASE</version>
</dependency>

Upvotes: 0

Related Questions