Reputation: 973
When getting values back out of a Postgres database with clojure.java.jdbc, I'm ending up with
{:id 1, :name "example", :created_at #object[java.time.LocalDateTime 0x15e0aadc "2019-02-08T12:52:11.438633"]}
when I want
{:id 1, :name "example", :created_at :created_at "2019-02-08T12:52:11.438633"]}
I want the date to be returned as JSON and consumable by JavaScript. The second example may not be the ideal format for that and so I'm open to others, but a Java object is not going to work.
I tried a couple of approaches, but the compiler produced a number of 'unmatched parenthesis' errors as a result of the date #object.
Thoughts on how to do this? Do I do something with the original SQL query, do I do something with my returned data, or something else?
Upvotes: 2
Views: 1438
Reputation: 487
Extend the java.time.Instant in your db.core.clj file. See https://rundis.github.io/blog/2015/clojure_dates.html for examples on extending types and protocols.
(extend-type java.time.Instant
jdbc/ISQLParameter
(set-parameter [v ^PreparedStatement stmt ^long idx]
(.setTimestamp stmt idx (Timestamp. (.toEpochMilli v)))))
(extend-protocol cheshire.generate/JSONable
java.time.Instant
(to-json [dt gen]
(cheshire.generate/write-string gen (str dt))))
Upvotes: 1
Reputation: 29958
I tried recreating your result with the H2 db, but it is giving me a Clojure #inst
result instead of an object reference.
OK, here is the problem. As your printout says, you have a java.time.LocalDateTime
(a Clojure #inst
is a java.util.Date
object).
If you want a string version, all you need to do is call the member function toString
on it:
(let [ldt (LocalDateTime/parse "2019-02-01T00:00:00" )]
(.toString ldt) => <#java.lang.String "2019-02-01T00:00">
However, you have no Time Zone information attached. So, you probably want a ZonedDateTime. WE WILL ASSUME UTC TIME ZONE BELOW (please verify for your DB server config):
(let [jud (Date.)
ldt (LocalDateTime/parse "2019-02-01T00:00:00")
zdt (.atZone ldt (ZoneId/of "UTC")) ; *** big assumption here! ***
inst (.toInstant zdt)
inst-str (.toString inst) ]
; a Clojure #inst is just a java.util.Date object
jud => #inst "2019-02-09T19:38:30.088-00:00"
; a ZonedDateTime can print in 2 ways
zdt => #object[java.time.ZonedDateTime 0x780af31 "2019-02-01T00:00Z[UTC]"]
(.toString zdt) => "2019-02-01T00:00Z[UTC]"
; a java.time.Instant also prints in 2 ways:
instant => #object[java.time.Instant 0x48301adb "2019-02-01T00:00:00Z"]
instant-str => "2019-02-01T00:00:00Z"
Note that the ZDT has a suffix like [UTC]
tacked onto the end, so you may wish to convert it to an Instant and then use the .toString
method to get a simpler string representation of it (ISO-8601).
If you don't mind using an external library to make this easier, the tupelo.java-time
lib has a helper function that is very handy:
(ns xyz
(require [tupelo.java-time :as tjt] ... ))
(tjt/string-date-time-iso zdt) => "2019-02-01T00:00:00Z"
There are many other helper functions available. Please see the API Docs and the unit tests for examples.
I finally got my Postgres install fixed up (had to reset the password to make Hikari work). Here is my test code:
(ns tst.demo.jdbc-pool
(:use demo.core tupelo.core tupelo.test)
(:require
[clojure.java.jdbc :as jdbc]
[hikari-cp.core :as pool]
[tupelo.java-time :as tjt] ) )
(def datasource-options-pg
{:adapter "postgresql"
:database-name "alan"
:server-name "localhost"
:port-number 5433
:username "alan"
:password "secret" } )
(def ^:dynamic db-conn nil)
(defn with-connection-pool
"Creates and uses a connection for test function"
[tst-fn]
(let [datasource (pool/make-datasource datasource-options-pg)]
(binding [db-conn {:datasource datasource}]
(tst-fn)
(pool/close-datasource datasource)))) ; close the connection - also closes/destroys the in-memory database
(use-fixtures
:once with-connection-pool) ; use the same db connection pool for all tests
The above is all just config stuff. Here is the unit test that verifies the behavior:
(dotest
(jdbc/db-do-commands db-conn ["drop table if exists langs"])
(jdbc/db-do-commands db-conn
[(jdbc/create-table-ddl :langs [[:id :serial]
[:lang "varchar not null"]
[:creation :timestamptz]])])
(jdbc/insert-multi! db-conn :langs
[{:lang "Clojure" :creation (tjt/iso-str->timestamp "2008-01-01T12:34:56Z")}
{:lang "Java" :creation (tjt/iso-str->timestamp "1995-06-01T07:08:09Z")}])
(let [result (jdbc/query db-conn ["select * from langs"])]
(is= (vec result)
[{:id 1, :lang "Clojure",
:creation #inst "2008-01-01T12:34:56.000000000-00:00"}
{:id 2, :lang "Java",
:creation #inst "1995-06-01T07:08:09.000000000-00:00"}])))
So you can see I am still getting a java.util.Date
result, which Clojure prints with the #inst
formatting. I'm not sure how you are getting JDBC to output the LocalDateTime
format.
Upvotes: 1
Reputation: 3078
I’d imagine that cheshire.core/encode would give you nice JSON from that map.
Read more about cheshire at https://github.com/dakrone/cheshire
Upvotes: 2