-
Notifications
You must be signed in to change notification settings - Fork 23
NativeJsonInput/Output with custom format #610
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
67be890
c8ec804
0c8ad07
d4e77b0
c93f4fe
16a3166
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
How to run benchmark: | ||
- compile `sbt commons-benchmark-js/fullOptJS` | ||
- open `fullopt-2.13.html` file in a browser | ||
- select test suite and run |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package com.avsystem.commons | ||
package serialization.nativejs | ||
|
||
import com.avsystem.commons.misc.{AbstractValueEnum, AbstractValueEnumCompanion, EnumCtx} | ||
|
||
/** | ||
* Specifies format used by `NativeJsonOutput.writeLong` / `NativeJsonInput.readLong` | ||
* to represent [[Long]]. JS does not support 64-bit representation. | ||
*/ | ||
final class NativeLongFormat(implicit ctx: EnumCtx) extends AbstractValueEnum | ||
object NativeLongFormat extends AbstractValueEnumCompanion[NativeLongFormat] { | ||
final val RawString: Value = new NativeLongFormat | ||
final val JsNumber: Value = new NativeLongFormat | ||
final val JsBigInt: Value = new NativeLongFormat | ||
} | ||
|
||
/** | ||
* Specifies format used by `NativeJsonOutput.writeTimestamp` / `NativeJsonInput.readTimestamp` | ||
* to represent timestamps. | ||
*/ | ||
final class NativeDateFormat(implicit ctx: EnumCtx) extends AbstractValueEnum | ||
object NativeDateFormat extends AbstractValueEnumCompanion[NativeDateFormat] { | ||
final val RawString: Value = new NativeDateFormat | ||
final val JsNumber: Value = new NativeDateFormat | ||
final val JsDate: Value = new NativeDateFormat | ||
} | ||
|
||
/** | ||
* Specifies format used by `NativeJsonOutput.writeBigInt` / `NativeJsonInput.readBigInt` | ||
* to represent [[BigInt]]. | ||
* | ||
* Note that [[scala.scalajs.js.JSON.stringify]] does not know how to serialize a BigInt and throws an error | ||
*/ | ||
final class NativeBigIntFormat(implicit ctx: EnumCtx) extends AbstractValueEnum | ||
object NativeBigIntFormat extends AbstractValueEnumCompanion[NativeBigIntFormat] { | ||
final val RawString: Value = new NativeBigIntFormat | ||
final val JsBigInt: Value = new NativeBigIntFormat | ||
} | ||
|
||
/** | ||
* Adjusts format produced by [[NativeJsonOutput]]. | ||
* | ||
* @param longFormat format used to [[Long]] | ||
* @param dateFormat format used to represent timestamps | ||
* @param bigIntFormat format used to represent [[BigInt]] | ||
*/ | ||
final case class NativeFormatOptions( | ||
longFormat: NativeLongFormat = NativeLongFormat.RawString, | ||
dateFormat: NativeDateFormat = NativeDateFormat.RawString, | ||
bigIntFormat: NativeBigIntFormat = NativeBigIntFormat.RawString, | ||
) | ||
object NativeFormatOptions { | ||
final val RawString = NativeFormatOptions() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
package com.avsystem.commons | ||
package serialization.nativejs | ||
|
||
import com.avsystem.commons.annotation.explicitGenerics | ||
import com.avsystem.commons.serialization.GenCodec.ReadFailure | ||
import com.avsystem.commons.serialization.* | ||
import com.avsystem.commons.serialization.json.RawJson | ||
|
||
import scala.scalajs.js | ||
import scala.scalajs.js.JSON | ||
|
||
class NativeJsonInput(value: js.Any, options: NativeFormatOptions) extends InputAndSimpleInput { self => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extend There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What com.avsystem.commons.serialization.nativejs.NativeFormatOptions should I use for the benchmark? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The defaults. You can also add a separate benchmark for choosing them if you'd like to make a decision based on performance. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
private def read[T](expected: String)(matcher: PartialFunction[Any, T]): T = | ||
matcher.applyOrElse(value, (o: Any) => throw new ReadFailure(s"Cannot read $expected, got: ${js.typeOf(o)}")) | ||
|
||
override def readNull(): Boolean = | ||
value == null | ||
|
||
override def readString(): String = | ||
read("String") { | ||
case s: String => s | ||
} | ||
|
||
override def readDouble(): Double = | ||
read("Double") { | ||
case v: Double => v | ||
} | ||
|
||
override def readInt(): Int = | ||
read("Int") { | ||
case v: Int => v | ||
} | ||
|
||
override def readLong(): Long = { | ||
def fromString(s: String): Long = | ||
try s.toLong | ||
catch { | ||
case e: NumberFormatException => throw new ReadFailure(s"Cannot read Long", e) | ||
} | ||
read("Long") { | ||
case s: String => fromString(s) | ||
case i: Int => i | ||
case d: Double if d.isWhole => d.toLong | ||
case b: js.BigInt => fromString(b.toString) | ||
// for some reason pattern match on js.BigInt type does not seem to work, check type manually | ||
case b if js.typeOf(b) == "bigint" => fromString(b.asInstanceOf[js.BigInt].toString) | ||
} | ||
} | ||
|
||
override def readBigInt(): BigInt = { | ||
def fromString(s: String): BigInt = | ||
try BigInt(s) | ||
catch { | ||
case e: NumberFormatException => throw new ReadFailure(s"Cannot read BigInt", e) | ||
} | ||
|
||
read("BigInt") { | ||
case s: String => fromString(s) | ||
case i: Int => BigInt(i) | ||
case d: Double if d.isWhole => BigInt(d.toLong) | ||
case b: js.BigInt => fromString(b.toString) | ||
// for some reason pattern match on js.BigInt type does not seem to work, check type manually | ||
case b if js.typeOf(b) == "bigint" => fromString(b.asInstanceOf[js.BigInt].toString) | ||
} | ||
} | ||
|
||
override def readBigDecimal(): BigDecimal = { | ||
def fromString(s: String): BigDecimal = | ||
try BigDecimal(s) | ||
catch { | ||
case e: NumberFormatException => throw new ReadFailure(s"Cannot read BigDecimal", e) | ||
} | ||
read("BigDecimal") { | ||
case s: String => fromString(s) | ||
case i: Int => BigDecimal(i) | ||
case d: Double => BigDecimal(d) | ||
} | ||
} | ||
|
||
override def readBoolean(): Boolean = | ||
read("Boolean") { | ||
case v: Boolean => v | ||
} | ||
|
||
override def readList(): ListInput = | ||
read("List") { | ||
case array: js.Array[js.Any @unchecked] => new NativeJsonListInput(array, options) | ||
} | ||
|
||
override def readObject(): ObjectInput = | ||
read("Object") { | ||
case obj: js.Object => new NativeJsonObjectInput(obj.asInstanceOf[js.Dictionary[js.Any]], options) | ||
} | ||
|
||
override def readTimestamp(): Long = options.dateFormat match { | ||
case NativeDateFormat.RawString | NativeDateFormat.JsNumber => | ||
readLong() // lenient behaviour, accept any value that can be interpreted as Long | ||
case NativeDateFormat.JsDate => | ||
read("js.Date") { | ||
case v: js.Date => v.getTime().toLong | ||
} | ||
} | ||
|
||
override def skip(): Unit = () | ||
|
||
override def readBinary(): Array[Byte] = | ||
read("Binary") { | ||
case array: js.Array[Int @unchecked] => array.iterator.map(_.toByte).toArray | ||
} | ||
|
||
override def readCustom[T](typeMarker: TypeMarker[T]): Opt[T] = | ||
typeMarker match { | ||
case RawJson => JSON.stringify(readRaw()).opt | ||
case _ => Opt.Empty | ||
} | ||
|
||
def readRaw(): js.Any = value | ||
} | ||
|
||
final class NativeJsonListInput(array: js.Array[js.Any], options: NativeFormatOptions) extends ListInput { | ||
private var it = 0 | ||
|
||
override def hasNext: Boolean = | ||
it < array.length | ||
|
||
override def nextElement(): Input = { | ||
val in = new NativeJsonInput(array(it), options) | ||
it += 1 | ||
in | ||
} | ||
} | ||
|
||
final class NativeJsonObjectInput(dict: js.Dictionary[js.Any], options: NativeFormatOptions) extends ObjectInput { | ||
private val it = dict.iterator | ||
|
||
override def hasNext: Boolean = | ||
it.hasNext | ||
|
||
override def peekField(name: String): Opt[FieldInput] = | ||
if (dict.contains(name)) Opt(new NativeJsonFieldInput(name, dict(name), options)) else Opt.Empty | ||
|
||
override def nextField(): FieldInput = { | ||
val (key, value) = it.next() | ||
new NativeJsonFieldInput(key, value, options) | ||
} | ||
} | ||
|
||
final class NativeJsonFieldInput( | ||
val fieldName: String, | ||
value: js.Any, | ||
options: NativeFormatOptions, | ||
) extends NativeJsonInput(value, options) | ||
with FieldInput | ||
|
||
object NativeJsonInput { | ||
@explicitGenerics | ||
def read[T: GenCodec](value: js.Any, options: NativeFormatOptions = NativeFormatOptions.RawString): T = | ||
GenCodec.read[T](new NativeJsonInput(value, options)) | ||
|
||
@explicitGenerics | ||
def readString[T: GenCodec](value: String, options: NativeFormatOptions = NativeFormatOptions.RawString): T = | ||
ddworak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
read[T](JSON.parse(value), options) | ||
} |
Uh oh!
There was an error while loading. Please reload this page.