Skip to content

Commit

Permalink
Add read-values and write-values
Browse files Browse the repository at this point in the history
read-values dispatches to the ReadValues protocol. It returns an
iterator via an ObjectReader derived from the supplied mapper.
The returned iterator is reified in a manner similar to Eduction to
support reduction and sequence construction over it.

write-values relies on two protocols - WriteValues for the output
destination, similarly to WriteValue, and WriteAll for the type being
written, which can be an array or an Iterable.
It writes an array or iterable to destination via a SequenceWriter.
Importantly, write-values distables automatic flushing on serialization
to get good performance.
  • Loading branch information
bsless committed May 21, 2021
1 parent 65a88ca commit 0b8fc58
Showing 1 changed file with 129 additions and 2 deletions.
131 changes: 129 additions & 2 deletions src/clj/jsonista/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@
RatioSerializer FunctionalKeywordSerializer)
(com.fasterxml.jackson.core JsonGenerator$Feature JsonFactory)
(com.fasterxml.jackson.databind
JsonSerializer ObjectMapper module.SimpleModule
JsonSerializer ObjectMapper SequenceWriter
SerializationFeature DeserializationFeature Module)
(com.fasterxml.jackson.databind.module SimpleModule)
(java.io InputStream Writer File OutputStream DataOutput Reader)
(java.net URL)
(com.fasterxml.jackson.datatype.jsr310 JavaTimeModule)
(java.util List Map Date)
(java.util List Map Date Iterator)
(clojure.lang Keyword Ratio Symbol)))

(defn- ^Module clojure-module
Expand Down Expand Up @@ -194,6 +194,38 @@
(-read-value [this ^ObjectMapper mapper]
(.readValue mapper this ^Class Object)))

(defprotocol ReadValues
(-read-values [this mapper]))

(extend-protocol ReadValues

(Class/forName "[B")
(-read-values [this ^ObjectMapper mapper]
(.readValues (.readerFor mapper ^Class Object) ^bytes this))

nil
(-read-values [_ _])

File
(-read-values [this ^ObjectMapper mapper]
(.readValues (.readerFor mapper ^Class Object) this))

URL
(-read-values [this ^ObjectMapper mapper]
(.readValues (.readerFor mapper ^Class Object) this))

String
(-read-values [this ^ObjectMapper mapper]
(.readValues (.readerFor mapper ^Class Object) this))

Reader
(-read-values [this ^ObjectMapper mapper]
(.readValues (.readerFor mapper ^Class Object) this))

InputStream
(-read-values [this ^ObjectMapper mapper]
(.readValues (.readerFor mapper ^Class Object) this)))

(defprotocol WriteValue
(-write-value [this value mapper]))

Expand All @@ -214,6 +246,50 @@
(-write-value [this value ^ObjectMapper mapper]
(.writeValue mapper this value)))

(defprotocol WriteAll
(-write-all [this ^SequenceWriter writer]))

(extend-protocol WriteAll

(Class/forName "[Ljava.lang.Object;")
(-write-all [this ^SequenceWriter w]
(.writeAll w ^"[Ljava.lang.Object;" this))

Iterable
(-write-all [this ^SequenceWriter w]
(.writeAll w this)))

(defprotocol WriteValues
(-write-values [this values mapper]))

(defmacro ^:private -write-values*
[this value mapper]
`(doto ^SequenceWriter
(-write-all
~value
(-> ~mapper
(.writerFor Object)
(.without SerializationFeature/FLUSH_AFTER_WRITE_VALUE)
(.writeValuesAsArray ~this)))
(.close)))

(extend-protocol WriteValues
File
(-write-values [this value ^ObjectMapper mapper]
(-write-values* this value mapper))

OutputStream
(-write-values [this value ^ObjectMapper mapper]
(-write-values* this value mapper))

DataOutput
(-write-values [this value ^ObjectMapper mapper]
(-write-values* this value mapper))

Writer
(-write-values [this value ^ObjectMapper mapper]
(-write-values* this value mapper)))

;;
;; public api
;;
Expand Down Expand Up @@ -259,3 +335,54 @@
(-write-value to object default-object-mapper))
([to object ^ObjectMapper mapper]
(-write-value to object mapper)))

(defn- wrap-values
[^Iterator iterator]
(reify
Iterable
(iterator [this] iterator)
Iterator
(hasNext [this] (.hasNext iterator))
(next [this] (.next iterator))
(remove [this] (.remove iterator))
clojure.lang.IReduceInit
(reduce [_ f val]
(loop [ret val]
(if (.hasNext iterator)
(let [ret (f ret (.next iterator))]
(if (reduced? ret)
@ret
(recur ret)))
ret)))
clojure.lang.Sequential))

(defn read-values
"Decodes a sequence of values from a JSON as an iterator
from anything that satisfies [[ReadValue]] protocol.
By default, File, URL, String, Reader and InputStream are supported.
The returned object is an Iterable, Iterator and IReduceInit.
It can be reduced on via [[reduce]] and turned into a lazy sequence
via [[iterator-seq]].
To configure, pass in an ObjectMapper created with [[object-mapper]],
see [[object-mapper]] docstring for the available options."
"Read a stream of values via an iterator.
Returned object is an Iterable, Iterator and reducible, similar to Eduction."
([object]
(wrap-values (-read-values object default-object-mapper)))
([object ^ObjectMapper mapper]
(wrap-values (-read-values object mapper))))

(defn write-values
"Encode values as JSON and write using the provided [[WriteValue]] instance.
By default, File, OutputStream, DataOutput and Writer are supported.
By default, values can be an array or an Iterable.
To configure, pass in an ObjectMapper created with [[object-mapper]],
see [[object-mapper]] docstring for the available options."
([to object]
(-write-values to object default-object-mapper))
([to object ^ObjectMapper mapper]
(-write-values to object mapper)))

0 comments on commit 0b8fc58

Please sign in to comment.