Skip to content

Commit

Permalink
Merge 1998fd5 into 6b9dff5
Browse files Browse the repository at this point in the history
  • Loading branch information
gzoller committed Dec 23, 2019
2 parents 6b9dff5 + 1998fd5 commit ae3ab39
Show file tree
Hide file tree
Showing 92 changed files with 6,609 additions and 825 deletions.
21 changes: 9 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,25 @@ Advanced Features:
- Sealed trait-style enumerations
- Extensible to other encodings (JSON, CSV, MongoDB, and DynamoDB are provided by ScalaJack, but you can roll your own too!)

> **Note:** Scala 2.12.10 or 2.13.1 on JDK 13.0.1 or later is strongly recommended!
> **Note:** 2.13.1 on JDK 13.0.1 or later is strongly recommended!
>Scala has done a lot of very recent work to improve compatibility with later JDKs and it's been a bumpy road. The combination above has been tested.
Combinations of earlier versions are known to have compabitility problems. If you must use earlier Scala or JVM versions then use JDK 1.8.

> **Note:** Deprecation of 2.12.x
>This version of ScalaJack will be the last that supports the 2.12 versions of Scala. Going forward only 2.13 will be suppoted. The libraries between 2.12
and 2.13 are becoming divergent so maintaining them both will be unwieldy.

## Use

ScalaJack is extremely simple to use.

Include it in your projects by adding the following to your build.sbt:

libraryDependencies ++= Seq("co.blocke" %% "scalajack" % "6.1.0")
libraryDependencies ++= Seq("co.blocke" %% "scalajack" % "6.2.0")

If you want to use the optional MongoDB serialization support include this as well:

libraryDependencies ++= Seq("co.blocke" %% "scalajack_mongo" % "6.1.0")
libraryDependencies ++= Seq("co.blocke" %% "scalajack_mongo" % "6.2.0")

DynamoDB helpers are available here:

libraryDependencies ++= Seq("co.blocke" %% "scalajack_dynamo" % "6.1.0")
libraryDependencies ++= Seq("co.blocke" %% "scalajack_dynamo" % "6.2.0")

ScalaJack is hosted on Bintray/JCenter. If you're using pre-v0.13.9 of SBT you may need to enable the bintray resolver in your build.sbt with

Expand Down Expand Up @@ -74,16 +70,17 @@ Couldn't be simpler!
* [Null and None treatment](doc/nullAndNone.md)
* [Externalized Type Hints](doc/externalTypes.md)
* [View/SpliceInto](doc/viewSplice.md)
* [Filter *(new)*](doc/filter.md)
* [Union type *(new)*](doc/union.md)
* [Filter](doc/filter.md)
* [Union type](doc/union.md)
* [ScalaJack Configuration](doc/config.md)
* [Gimme Speed!](doc/speed.md)

Non-JSON Formats:
* [YAML *(new)*](doc/yaml.md)
* [MongoDB](doc/mongo.md)
* [Delimited (e.g. CSV) *(improved)*](doc/delimited.md)
* [Delimited (e.g. CSV)](doc/delimited.md)
* [DynamoDB](doc/dynamo.md)
* [Json4s *(new)*](doc/json4s.md)
* [Json4s](doc/json4s.md)

## Benchmarks

Expand Down
14 changes: 14 additions & 0 deletions TODO
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

[ ] -- Map one kind of WIRE to another:

val sj: ScalaJack[JSON]()

val mapper = sj.mapper(YamlFlavor())
mapper.map[Foo](f, Foo => Foo, JSON): YAML

Possibly use implicit converters?


[ ] -- Stream input/output, e.g. Mutliple back-to-back YAML documents, but not a List[Document]

Possibly combine with a mapper above
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ val dynamo = "com.amazonaws" % "aws-java-sdk-dynamodb" % "1.11.538"
val json4s = "org.json4s" %% "json4s-core" % "3.6.6"
val json4sNative = "org.json4s" %% "json4s-native" % "3.6.6"
val cats = "org.typelevel" %% "cats-core" % "2.0.0"
val snakeyaml = "org.snakeyaml" % "snakeyaml-engine" % "2.0"

lazy val basicSettings = Seq(
resolvers += Resolver.jcenterRepo,
Expand Down Expand Up @@ -67,7 +68,7 @@ lazy val scalajack = project
Seq("org.scala-lang" % "scala-reflect" % scalaVersion.value) ++
Seq("org.apache.commons" % "commons-text" % "1.6") ++
Seq("commons-codec" % "commons-codec" % "1.12") ++
Seq(json4s, cats) ++
Seq(json4s, cats, snakeyaml) ++
test(scalatest) ++
test(json4sNative)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,6 @@ case class DelimitedParser(
showError("DelimitedFlavor does not support traits")
)

def skipOverElement(): Unit = {} // has no meaning for delimited input, i.e. no trait or capture support that would require skipping
def nextIsObject: Boolean = false
def nextIsArray: Boolean = false
def nextIsBoolean: Boolean = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@ import co.blocke.scalajack.model
import scala.collection.Map
import scala.collection.mutable

case class DelimitedWriter(delimiter: Char) extends Writer[String] {
case class DelimitedWriter(delimiter: Char) extends Writer[DELIMITED] {

def writeArray[Elem](
t: Iterable[Elem],
elemTypeAdapter: TypeAdapter[Elem],
out: mutable.Builder[String, String]): Unit =
def writeArray[Elem](t: Iterable[Elem], elemTypeAdapter: TypeAdapter[Elem], out: mutable.Builder[DELIMITED, DELIMITED]): Unit =
if (t != null) {
val sb = model.StringBuilder()
val sb = model.StringBuilder()
val iter = t.iterator
while (iter.hasNext) {
elemTypeAdapter.write(iter.next, this, sb)
Expand All @@ -25,36 +22,32 @@ case class DelimitedWriter(delimiter: Char) extends Writer[String] {
writeString(sb.result(), out)
}

def writeBigInt(t: BigInt, out: mutable.Builder[String, String]): Unit =
def writeBigInt(t: BigInt, out: mutable.Builder[DELIMITED, DELIMITED]): Unit =
out += t.toString
def writeBoolean(t: Boolean, out: mutable.Builder[String, String]): Unit =
def writeBoolean(t: Boolean, out: mutable.Builder[DELIMITED, DELIMITED]): Unit =
out += t.toString
def writeDecimal(t: BigDecimal, out: mutable.Builder[String, String]): Unit =
def writeDecimal(t: BigDecimal, out: mutable.Builder[DELIMITED, DELIMITED]): Unit =
out += t.toString
def writeDouble(t: Double, out: mutable.Builder[String, String]): Unit =
def writeDouble(t: Double, out: mutable.Builder[DELIMITED, DELIMITED]): Unit =
out += t.toString
def writeInt(t: Int, out: mutable.Builder[String, String]): Unit =
def writeInt(t: Int, out: mutable.Builder[DELIMITED, DELIMITED]): Unit =
out += t.toString
def writeLong(t: Long, out: mutable.Builder[String, String]): Unit =
def writeLong(t: Long, out: mutable.Builder[DELIMITED, DELIMITED]): Unit =
out += t.toString

def writeMap[Key, Value, To](
t: Map[Key, Value],
keyTypeAdapter: TypeAdapter[Key],
valueTypeAdapter: TypeAdapter[Value],
out: mutable.Builder[String, String]): Unit =
def writeMap[Key, Value, To](t: Map[Key, Value], keyTypeAdapter: TypeAdapter[Key], valueTypeAdapter: TypeAdapter[Value], out: mutable.Builder[DELIMITED, DELIMITED]): Unit =
throw new ScalaJackError(
"Map-typed data is not supported for delimited output"
)

def writeNull(out: mutable.Builder[String, String]): Unit = {} // write empty field
def writeNull(out: mutable.Builder[DELIMITED, DELIMITED]): Unit = {} // write empty field

def writeObject[T](
t: T,
orderedFieldNames: List[String],
t: T,
orderedFieldNames: List[String],
fieldMembersByName: Map[String, ClassHelper.ClassFieldMember[T, Any]],
out: mutable.Builder[String, String],
extras: List[(String, ExtraFieldValue[_])] = List.empty[(String, ExtraFieldValue[_])]
out: mutable.Builder[DELIMITED, DELIMITED],
extras: List[(String, ExtraFieldValue[_])] = List.empty[(String, ExtraFieldValue[_])]
): Unit =
if (t != null) {
var first = true
Expand All @@ -75,7 +68,7 @@ case class DelimitedWriter(delimiter: Char) extends Writer[String] {
}
}

def writeString(t: String, out: mutable.Builder[String, String]): Unit =
def writeString(t: String, out: mutable.Builder[DELIMITED, DELIMITED]): Unit =
if (t != null) {
val t0 = t.replaceAll("\"", "\"\"")
val toWrite =
Expand All @@ -86,17 +79,17 @@ case class DelimitedWriter(delimiter: Char) extends Writer[String] {
out += toWrite
}
// $COVERAGE-OFF$Never called for delimited output
def writeRaw(t: Any, out: mutable.Builder[String, String]): Unit =
writeString(t.asInstanceOf[String], out)
def writeRaw(t: DELIMITED, out: mutable.Builder[DELIMITED, DELIMITED]): Unit =
writeString(t, out)
// $COVERAGE-ON$

def writeTuple[T](
t: T,
t: T,
writeFns: List[typeadapter.TupleTypeAdapterFactory.TupleField[_]],
out: mutable.Builder[String, String]
out: mutable.Builder[DELIMITED, DELIMITED]
): Unit = {
var first = true
val sb = model.StringBuilder()
val sb = model.StringBuilder()
writeFns.foreach { f =>
if (first)
first = false
Expand Down
37 changes: 15 additions & 22 deletions core/src/main/scala/co.blocke.scalajack/json/JsonParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ case class JsonParser(js: JSON, jackFlavor: JackFlavor[JSON]) extends Parser {
type WIRE = JSON

private val jsChars: Array[Char] = js.toCharArray
private var i = 0
private val max: Int = jsChars.length
private var i = 0
private val max: Int = jsChars.length

@inline def whitespace(): Unit =
while (i < max && jsChars(i).isWhitespace) i += 1
Expand All @@ -29,7 +29,7 @@ case class JsonParser(js: JSON, jackFlavor: JackFlavor[JSON]) extends Parser {
null
} else if (jsChars(i) == '"') {
i += 1
val mark = i
val mark = i
var captured: Option[String] = None
while (i < max && jsChars(i) != '"') {
if (jsChars(i) == '\\') { // Oops! Special char found. Reset and try again while capturing/translating special chars
Expand Down Expand Up @@ -80,7 +80,7 @@ case class JsonParser(js: JSON, jackFlavor: JackFlavor[JSON]) extends Parser {
i += 2

case 'u' =>
val hexEncoded = js.substring(i + 2, i + 6)
val hexEncoded = js.substring(i + 2, i + 6)
val unicodeChar = Integer.parseInt(hexEncoded, 16).toChar
builder.append(unicodeChar.toString)
i += 6
Expand Down Expand Up @@ -129,9 +129,7 @@ case class JsonParser(js: JSON, jackFlavor: JackFlavor[JSON]) extends Parser {
}
}

def expectList[E, TO](
elemTypeAdapter: TypeAdapter[E],
builder: mutable.Builder[E, TO]): TO = {
def expectList[E, TO](elemTypeAdapter: TypeAdapter[E], builder: mutable.Builder[E, TO]): TO = {
if (jsChars(i) != '[')
throw new ScalaJackError(showError("Expected start of list here"))
i += 1
Expand Down Expand Up @@ -180,10 +178,7 @@ case class JsonParser(js: JSON, jackFlavor: JackFlavor[JSON]) extends Parser {
result
}

def expectMap[K, V, TO](
keyTypeAdapter: TypeAdapter[K],
valueTypeAdapter: TypeAdapter[V],
builder: mutable.Builder[(K, V), TO]): TO = {
def expectMap[K, V, TO](keyTypeAdapter: TypeAdapter[K], valueTypeAdapter: TypeAdapter[V], builder: mutable.Builder[(K, V), TO]): TO = {
whitespace()
if (jsChars(i) != '{')
throw new ScalaJackError(showError("Expected start of object here"))
Expand Down Expand Up @@ -222,7 +217,7 @@ case class JsonParser(js: JSON, jackFlavor: JackFlavor[JSON]) extends Parser {
hintLabel: String
): (mutable.BitSet, Array[Any], java.util.HashMap[String, _]) = {
whitespace()
val args = classBase.argsTemplate.clone()
val args = classBase.argsTemplate.clone()
val fieldBits = classBase.fieldBitsTemplate.clone()
val captured =
if (classBase.isSJCapture) new java.util.HashMap[String, String]()
Expand All @@ -246,19 +241,17 @@ case class JsonParser(js: JSON, jackFlavor: JackFlavor[JSON]) extends Parser {
if (i == max || jsChars(i) != ':')
throw new ScalaJackError(showError("Expected colon here"))
i += 1
classBase.fieldMembersByName
.get(key)
.map { field =>
classBase.fieldMembersByName.get(key) match {
case Some(field) =>
whitespace()
fieldBits -= field.index
args(field.index) = field.valueTypeAdapter.read(this)
}
.getOrElse {
case None => // found some input field not present in class
val mark = i
skipOverElement()
if (classBase.isSJCapture && key != hintLabel)
captured.put(key, js.substring(mark, i))
}
}
whitespace()
}
if (i == max || jsChars(i) != '}')
Expand All @@ -276,7 +269,7 @@ case class JsonParser(js: JSON, jackFlavor: JackFlavor[JSON]) extends Parser {
i += 1
}

def skipOverElement(): Unit = {
private def skipOverElement(): Unit = {
whitespace()
jsChars(i) match {
case '[' =>
Expand Down Expand Up @@ -367,7 +360,7 @@ case class JsonParser(js: JSON, jackFlavor: JackFlavor[JSON]) extends Parser {

def resolveTypeMembers(
typeMembersByName: Map[String, ClassHelper.TypeMember[_]],
converterFn: HintBijective
converterFn: HintBijective
): Map[Type, Type] = {
val mark = i
whitespace()
Expand Down Expand Up @@ -414,13 +407,13 @@ case class JsonParser(js: JSON, jackFlavor: JackFlavor[JSON]) extends Parser {
msg + "\n" + clip.replaceAll("[\n\t]", "~") + "\n" + ("-" * dashes) + "^"
}

def mark(): Int = i
def mark(): Int = i
def revertToMark(mark: Int): Unit = i = mark

def nextIsString: Boolean = nullCheck() || jsChars(i) == '"'
def nextIsNumber: Boolean = isNumberChar(jsChars(i))
def nextIsObject: Boolean = nullCheck() || jsChars(i) == '{'
def nextIsArray: Boolean = nullCheck() || jsChars(i) == '['
def nextIsArray: Boolean = nullCheck() || jsChars(i) == '['
def nextIsBoolean: Boolean =
(i + 4 <= max && js.substring(i, i + 4) == "true") || (i + 5 <= max && js
.substring(i, i + 5) == "false")
Expand Down

0 comments on commit ae3ab39

Please sign in to comment.