Skip to content

Commit

Permalink
Add docs for CSV encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
ybasket committed Jun 8, 2020
1 parent 69dc261 commit 9696370
Showing 1 changed file with 58 additions and 0 deletions.
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,64 @@ println(rows.compile.toList.unsafeRunSync())

There's also support for full auto-derivation, just `import fs2.data.csv.generic.auto._` for everything, `import fs2.data.csv.generic.auto.row._` for `RowDecoder` support only or `import fs2.data.csv.generic.auto.csvrow._` for `CsvRowDecoder` support.

### Encoding

`fs2-data` does not only support decoding CSV, it also offers the reverse, the encoding of data types into CSV streams. For this, a hierarchy of `Encoder` type classes exists which mirrors the one for decoding, with `CellEncoder` for single cells, `RowEncoder` for rows and `CsvRowEncoder` for rows with headers. Once you have a stream of `Row` or `CsvRow`, there are pipes to encode this into a valid CSV representation, taking care of proper escaping.

As for decoding, there's support for deriving encoders (semi-)automatically using the `fs2-data-csv-generic` module:

Coming back to the example of [shapeless][shapeless] `HList`:

```scala
import fs2._
import fs2.data.csv.generic.hlist._
import fs2.data.csv._
import shapeless._

val data = Some(3) :: "test" :: 42 :: HNil
println(
Stream.emit(data)
.through(encode[Pure, Option[Int] :: String :: Int :: HNil])
.through(writeWithoutHeaders[Pure])
.through(toRowStrings[Pure](/* separator: Char = ',', newline: String = "\n"*/))
.compile
.toList // List of the rows, each encoded into one String with newline
)
```

The usage of `Pure` is possible as encoding (different to decoding) can't fail, simplifying its usage. Custom encoders can easily be defined by using `contramap` on an existing encoder or by generic derivation:

```scala
import fs2.data.csv.generic.semiauto._

sealed trait Advanced
@CsvValue("Active") case object On extends Advanced
case class Unknown(name: String) extends Advanced

implicit val unknownEncoder = deriveCellEncoder[Unknown] // works as we have an implicit CellEncoder[String]
// Alternatively: implicit val unknownEncoder = CellEncoder[String].contramap[Unknown](_.name)
implicit val advancedEncoder = deriveCellEncoder[Advanced]

println(advancedEncoder(On)) // prints Active
println(advancedEncoder(Unknown("Off"))) // prints Off
```

The generic derivation for encoding supports the same annotations and primitive types as the one for decoding. Example for a case class:

```scala
case class MyRow(s: String, @CsvName("x") i: Option[Int], j: Int)

implicit val myRowEncoder = deriveCsvRowEncoder[MyRow]

val rows = Stream(MyRow("I'm happy", Some(3), 42))
.through(encodeRow)
.through(encodeRowWithFirstHeaders)

println(rows.compile.toList) // List(NonEmptyList(s, x, j), NonEmptyList(I'm happy, 3, 42))
```

There are different pipes for different handling of headers in the `csv` package object.

### Development
This project builds using [mill][mill]. You can install `mill` yourself or use the provided `millw` wrapper, in this case replace `mill` with `./millw` in the following commands:
* compile everything: `mill __.compile`
Expand Down

0 comments on commit 9696370

Please sign in to comment.