play.api.libs.json.EnvReads.scala Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc.
*/
package play.api.libs.json
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeParseException
import java.time.temporal.ChronoUnit
import java.time.temporal.TemporalUnit
import java.time.temporal.UnsupportedTemporalTypeException
import java.time.temporal.{ Temporal => JTemporal }
import java.time.Clock
import java.time.DateTimeException
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.OffsetDateTime
import java.time.Period
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.ZonedDateTime
import java.time.{ Duration => JDuration }
import java.util.Locale
import scala.util.control.NonFatal
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.node.ArrayNode
import com.fasterxml.jackson.databind.node.ObjectNode
import play.api.libs.json.jackson.JacksonJson
trait EnvReads {
import scala.language.implicitConversions
/**
* Deserializer for Jackson JsonNode
*/
implicit object JsonNodeReads extends Reads[JsonNode] {
def reads(json: JsValue): JsResult[JsonNode] =
JsSuccess(JacksonJson.get.jsValueToJsonNode(json))
}
/**
* Deserializer for Jackson ObjectNode
*/
implicit object ObjectNodeReads extends Reads[ObjectNode] {
def reads(json: JsValue): JsResult[ObjectNode] = {
json.validate[JsObject].map(jo => JacksonJson.get.jsValueToJsonNode(jo).asInstanceOf[ObjectNode])
}
}
/**
* Deserializer for Jackson ArrayNode
*/
implicit object ArrayNodeReads extends Reads[ArrayNode] {
def reads(json: JsValue): JsResult[ArrayNode] = {
json.validate[JsArray].map(ja => JacksonJson.get.jsValueToJsonNode(ja).asInstanceOf[ArrayNode])
}
}
/**
* Reads for the `java.util.Date` type.
*
* @param pattern a date pattern, as specified in `java.text.SimpleDateFormat`.
* @param corrector a simple string transformation function that can be used to transform input String before parsing. Useful when standards are not exactly respected and require a few tweaks
*/
def dateReads(pattern: String, corrector: String => String = identity): Reads[java.util.Date] =
new Reads[java.util.Date] {
def reads(json: JsValue): JsResult[java.util.Date] = json match {
case n: JsNumber => n.validate[Long].map(l => new java.util.Date(l))
case JsString(s) =>
parseJDate(pattern, corrector(s)) match {
case Some(d) => JsSuccess(d)
case None =>
JsError(
Seq(
JsPath ->
Seq(JsonValidationError("error.expected.date.isoformat", pattern))
)
)
}
case _ =>
JsError(
Seq(
JsPath ->
Seq(JsonValidationError("error.expected.date"))
)
)
}
}
private def parseJDate(pattern: String, input: String): Option[java.util.Date] = {
// REMEMBER THAT SIMPLEDATEFORMAT IS NOT THREADSAFE
val df = new java.text.SimpleDateFormat(pattern)
df.setLenient(false)
try {
Some(df.parse(input))
} catch {
case x: java.text.ParseException =>
None
}
}
/**
* the default implicit java.util.Date reads
*/
implicit val DefaultDateReads: Reads[java.util.Date] = dateReads("yyyy-MM-dd")
/**
* ISO 8601 Reads
*/
object IsoDateReads extends Reads[java.util.Date] {
import java.util.Date
val millisAndTz = "yyyy-MM-dd'T'HH:mm:ss.SSSX"
val millis = "yyyy-MM-dd'T'HH:mm:ss.SSS"
val tz = "yyyy-MM-dd'T'HH:mm:ssX"
val mini = "yyyy-MM-dd'T'HH:mm:ss"
val WithMillisAndTz = """^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}.+$""".r
val WithMillis = """^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}$""".r
val WithTz = """^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}[^.]+$""".r
def reads(json: JsValue): JsResult[Date] = json match {
case n: JsNumber => n.validate[Long].map(l => new Date(l))
case JsString(s) =>
(s match {
case WithMillisAndTz() => millisAndTz -> parseJDate(millisAndTz, s)
case WithMillis() => millis -> parseJDate(millis, s)
case WithTz() => tz -> parseJDate(tz, s)
case _ => mini -> parseJDate(mini, s)
}) match {
case (_, Some(d)) => JsSuccess(d)
case (p, None) =>
JsError(
Seq(
JsPath ->
Seq(JsonValidationError("error.expected.date.isoformat", p))
)
)
}
case js => JsError("error.expected.date.isoformat")
}
}
/**
* Reads for the `java.sql.Date` type.
*
* @param pattern a date pattern, as specified in `java.text.SimpleDateFormat`.
* @param corrector a simple string transformation function that can be used to transform input String before parsing. Useful when standards are not exactly respected and require a few tweaks
*/
def sqlDateReads(pattern: String, corrector: String => String = identity): Reads[java.sql.Date] =
dateReads(pattern, corrector).map(d => new java.sql.Date(d.getTime))
/**
* the default implicit SqlDate reads
*/
implicit val DefaultSqlDateReads: Reads[java.sql.Date] = sqlDateReads("yyyy-MM-dd")
/** Typeclass to implement way of parsing string as Java8 temporal types. */
trait TemporalParser[T <: JTemporal] {
def parse(input: String): Option[T]
}
/** Parsing companion */
object TemporalParser {
/** Instance of local date/time based on specified pattern. */
implicit def LocalDateTimePatternParser(pattern: String): TemporalParser[LocalDateTime] =
LocalDateTimeFormatterParser(DateTimeFormatter.ofPattern(pattern))
/** Instance of local date/time based on formatter. */
implicit def LocalDateTimeFormatterParser(formatter: DateTimeFormatter): TemporalParser[LocalDateTime] =
new TemporalParser[LocalDateTime] {
def parse(input: String): Option[LocalDateTime] =
try {
Some(LocalDateTime.parse(input, formatter))
} catch {
case _: DateTimeParseException => None
case _: UnsupportedTemporalTypeException => None
}
}
/** Instance of offset date/time based on specified pattern. */
implicit def OffsetDateTimePatternParser(pattern: String): TemporalParser[OffsetDateTime] =
OffsetDateTimeFormatterParser(DateTimeFormatter.ofPattern(pattern))
/** Instance of offset date/time based on formatter. */
implicit def OffsetDateTimeFormatterParser(formatter: DateTimeFormatter): TemporalParser[OffsetDateTime] =
new TemporalParser[OffsetDateTime] {
def parse(input: String): Option[OffsetDateTime] =
try {
Some(OffsetDateTime.parse(input, formatter))
} catch {
case _: DateTimeParseException => None
case _: UnsupportedTemporalTypeException => None
}
}
/** Instance of date based on specified pattern. */
implicit def DatePatternParser(pattern: String): TemporalParser[LocalDate] =
DateFormatterParser(DateTimeFormatter.ofPattern(pattern))
/** Instance of date based on formatter. */
implicit def DateFormatterParser(formatter: DateTimeFormatter): TemporalParser[LocalDate] =
new TemporalParser[LocalDate] {
def parse(input: String): Option[LocalDate] =
try {
Some(LocalDate.parse(input, formatter))
} catch {
case _: DateTimeParseException => None
case _: UnsupportedTemporalTypeException => None
}
}
/** Instance of instant parser based on specified pattern. */
implicit def InstantPatternParser(pattern: String): TemporalParser[Instant] =
InstantFormatterParser(DateTimeFormatter.ofPattern(pattern))
/** Instance of instant parser based on formatter. */
implicit def InstantFormatterParser(formatter: DateTimeFormatter): TemporalParser[Instant] =
new TemporalParser[Instant] {
def parse(input: String): Option[Instant] =
try {
Some(Instant.from(formatter.parse(input)))
} catch {
case _: DateTimeParseException => None
case _: UnsupportedTemporalTypeException => None
}
}
/** Instance of zoned date/time based on specified pattern. */
implicit def ZonedDateTimePatternParser(pattern: String): TemporalParser[ZonedDateTime] =
ZonedDateTimeFormatterParser(DateTimeFormatter.ofPattern(pattern))
/** Instance of zoned date/time based on formatter. */
implicit def ZonedDateTimeFormatterParser(formatter: DateTimeFormatter): TemporalParser[ZonedDateTime] =
new TemporalParser[ZonedDateTime] {
def parse(input: String): Option[ZonedDateTime] =
try {
Some(ZonedDateTime.parse(input, formatter))
} catch {
case _: DateTimeParseException => None
case _: UnsupportedTemporalTypeException => None
}
}
/** Instance of LocalTime parser based on specified pattern. */
implicit def LocalTimePatternParser(pattern: String): TemporalParser[LocalTime] =
LocalTimeFormatterParser(DateTimeFormatter.ofPattern(pattern))
/** Instance of LocalTime parser based on formatter. */
implicit def LocalTimeFormatterParser(formatter: DateTimeFormatter): TemporalParser[LocalTime] =
new TemporalParser[LocalTime] {
def parse(input: String): Option[LocalTime] =
try {
Some(LocalTime.from(formatter.parse(input)))
} catch {
case _: DateTimeParseException => None
case _: UnsupportedTemporalTypeException => None
}
}
}
/**
* @tparam A the parsing type
* @tparam B the temporal type
*/
private final class TemporalReads[A, B <: JTemporal](
parsing: A,
corrector: String => String,
p: A => TemporalParser[B],
epoch: Long => B
) extends Reads[B] {
def reads(json: JsValue): JsResult[B] = json match {
case n: JsNumber => n.validate[Long].map(epoch)
case JsString(s) =>
p(parsing).parse(corrector(s)) match {
case Some(d) => JsSuccess(d)
case None =>
JsError(
Seq(
JsPath ->
Seq(JsonValidationError("error.expected.date.isoformat", parsing))
)
)
}
case _ =>
JsError(
Seq(
JsPath ->
Seq(JsonValidationError("error.expected.date"))
)
)
}
}
/**
* Reads for the `java.time.LocalDateTime` type.
*
* @tparam T Type of argument to instantiate date/time parser
* @param parsing Argument to instantiate date/time parser. Actually either a pattern (string) or a formatter (`java.time.format.DateTimeFormatter`)
* @param corrector a simple string transformation function that can be used to transform input String before parsing. Useful when standards are not exactly respected and require a few tweaks. Function `identity` can be passed if no correction is needed.
* @param p Typeclass instance based on `parsing`
* @see [[DefaultWrites.TemporalFormatter]]
*
* {{{
* import java.time.format.DateTimeFormatter
*
* import play.api.libs.json.Reads.localDateTimeReads
*
* val customReads1 = localDateTimeReads("dd/MM/yyyy, HH:mm:ss")
* val customReads2 = localDateTimeReads(DateTimeFormatter.ISO_DATE_TIME)
* val customReads3 = localDateTimeReads(
* DateTimeFormatter.ISO_DATE_TIME, _.drop(1))
* }}}
*/
def localDateTimeReads[T](parsing: T, corrector: String => String = identity)(implicit
p: T => TemporalParser[LocalDateTime]
): Reads[LocalDateTime] =
new TemporalReads[T, LocalDateTime](
parsing,
corrector,
p,
{ (millis: Long) =>
LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneOffset.UTC)
}
)
/**
* The default typeclass to reads `java.time.LocalDateTime` from JSON.
* Accepts date/time formats as '2011-12-03T10:15:30', '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30+01:00[Europe/Paris]'.
*/
implicit val DefaultLocalDateTimeReads: Reads[LocalDateTime] =
localDateTimeReads(DateTimeFormatter.ISO_DATE_TIME)
/**
* Reads for the `java.time.OffsetDateTime` type.
*
* Note: it is intentionally not supported to read an OffsetDateTime
* from a number.
*
* @tparam T the type of argument to instantiate date/time parser
* @param parsing The argument to instantiate date/time parser. Actually either a pattern (string) or a formatter (`java.time.format.DateTimeFormatter`)
* @param corrector a simple string transformation function that can be used to transform input String before parsing. Useful when standards are not exactly respected and require a few tweaks. Function `identity` can be passed if no correction is needed.
* @param p the implicit conversion based on `parsing`
* @see [[DefaultWrites.TemporalFormatter]]
*
* {{{
* import java.time.format.DateTimeFormatter
*
* import play.api.libs.json.Reads.offsetDateTimeReads
*
* val customReads1 = offsetDateTimeReads("dd/MM/yyyy, HH:mm:ss (Z)")
* val customReads2 = offsetDateTimeReads(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
* val customReads3 = offsetDateTimeReads(
* DateTimeFormatter.ISO_OFFSET_DATE_TIME, _.drop(1))
* }}}
*/
def offsetDateTimeReads[T](parsing: T, corrector: String => String = identity)(implicit
p: T => TemporalParser[OffsetDateTime]
): Reads[OffsetDateTime] = new Reads[OffsetDateTime] {
def reads(json: JsValue): JsResult[OffsetDateTime] = json match {
case JsString(s) =>
p(parsing).parse(corrector(s)) match {
case Some(d) => JsSuccess(d)
case None =>
JsError(
Seq(
JsPath ->
Seq(JsonValidationError("error.expected.date.isoformat", parsing))
)
)
}
case _ =>
JsError(
Seq(
JsPath ->
Seq(JsonValidationError("error.expected.date"))
)
)
}
}
/**
* The default typeclass to reads `java.time.OffsetDateTime` from JSON.
* Accepts date/time formats as '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30+01:00[Europe/Paris]'.
*/
implicit val DefaultOffsetDateTimeReads: Reads[OffsetDateTime] =
offsetDateTimeReads(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
/**
* Reads for the `java.time.ZonedDateTime` type.
*
* @tparam T Type of argument to instantiate date/time parser
* @param parsing Argument to instantiate date/time parser. Actually either a pattern (string) or a formatter (`java.time.format.DateTimeFormatter`)
* @param corrector a simple string transformation function that can be used to transform input String before parsing. Useful when standards are not exactly respected and require a few tweaks. Function `identity` can be passed if no correction is needed.
* @param p Typeclass instance based on `parsing`
* @see [[DefaultWrites.TemporalFormatter]]
*
* {{{
* import java.time.format.DateTimeFormatter
*
* import play.api.libs.json.Reads.zonedDateTimeReads
*
* val customReads1 = zonedDateTimeReads("dd/MM/yyyy, HH:mm:ss")
* val customReads2 = zonedDateTimeReads(DateTimeFormatter.ISO_DATE_TIME)
* val customReads3 = zonedDateTimeReads(
* DateTimeFormatter.ISO_DATE_TIME, _.drop(1))
* }}}
*/
def zonedDateTimeReads[T](parsing: T, corrector: String => String = identity)(implicit
p: T => TemporalParser[ZonedDateTime]
): Reads[ZonedDateTime] =
new TemporalReads[T, ZonedDateTime](
parsing,
corrector,
p,
{ (millis: Long) =>
ZonedDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneOffset.UTC)
}
)
/**
* The default typeclass to reads `java.time.ZonedDateTime` from JSON.
* Accepts date/time formats as '2011-12-03T10:15:30', '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30+01:00[Europe/Paris]'.
*/
implicit val DefaultZonedDateTimeReads: Reads[ZonedDateTime] =
zonedDateTimeReads(DateTimeFormatter.ISO_DATE_TIME)
/**
* Reads for the `java.time.LocalDate` type.
*
* @tparam T Type of argument to instantiate date parser
* @param parsing Argument to instantiate date parser. Actually either a pattern (string) or a formatter (`java.time.format.DateTimeFormatter`)
* @param corrector a simple string transformation function that can be used to transform input String before parsing. Useful when standards are not exactly respected and require a few tweaks. Function `identity` can be passed if no correction is needed.
* @param p Typeclass instance based on `parsing`
* @see [[DefaultWrites.TemporalFormatter]]
*
* {{{
* import java.time.format.DateTimeFormatter
*
* import play.api.libs.json.Reads.localDateReads
*
* val customReads1 = localDateReads("dd/MM/yyyy, HH:mm:ss")
* val customReads2 = localDateReads(DateTimeFormatter.ISO_DATE)
* val customReads3 = localDateReads(DateTimeFormatter.ISO_DATE, _.drop(1))
* }}}
*/
def localDateReads[T](parsing: T, corrector: String => String = identity)(implicit
p: T => TemporalParser[LocalDate]
): Reads[LocalDate] =
new Reads[LocalDate] {
def reads(json: JsValue): JsResult[LocalDate] = json match {
case n: JsNumber => n.validate[Long].map(epoch)
case JsString(s) =>
p(parsing).parse(corrector(s)) match {
case Some(d) => JsSuccess(d)
case _ =>
JsError(
Seq(
JsPath ->
Seq(JsonValidationError("error.expected.date.isoformat", parsing))
)
)
}
case _ =>
JsError(
Seq(
JsPath ->
Seq(JsonValidationError("error.expected.date"))
)
)
}
@inline def epoch(millis: Long): LocalDate = LocalDate.now(
Clock.fixed(Instant.ofEpochMilli(millis), ZoneOffset.UTC)
)
}
/**
* The default typeclass to reads `java.time.LocalDate` from JSON.
* Accepts date formats as '2011-12-03'.
*/
implicit val DefaultLocalDateReads: Reads[LocalDate] =
localDateReads(DateTimeFormatter.ISO_DATE)
/**
* Reads for the `java.time.Instant` type.
*
* @tparam T Type of argument to instantiate date parser
* @param parsing Argument to instantiate date parser. Actually either a pattern (string) or a formatter (`java.time.format.DateTimeFormatter`)
* @param corrector a simple string transformation function that can be used to transform input String before parsing. Useful when standards are not exactly respected and require a few tweaks. Function `identity` can be passed if no correction is needed.
* @param p Typeclass instance based on `parsing`
* @see [[DefaultWrites.TemporalFormatter]]
*
* {{{
* import java.time.format.DateTimeFormatter
*
* import play.api.libs.json.Reads.instantReads
*
* val customReads1 = instantReads("dd/MM/yyyy, HH:mm:ss")
* val customReads2 = instantReads(DateTimeFormatter.ISO_INSTANT)
* val customReads3 = instantReads(DateTimeFormatter.ISO_INSTANT, _.drop(1))
* }}}
*/
def instantReads[T](parsing: T, corrector: String => String = identity)(implicit
p: T => TemporalParser[Instant]
): Reads[Instant] = new TemporalReads[T, Instant](parsing, corrector, p, Instant.ofEpochMilli(_))
/**
* The default typeclass to reads `java.time.Instant` from JSON.
* Accepts instant formats as '2011-12-03T10:15:30Z', '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30+01:00[Europe/Paris]'.
*/
implicit val DefaultInstantReads: Reads[Instant] =
instantReads(DateTimeFormatter.ISO_DATE_TIME)
// ---
/**
* Reads for the `java.time.LocalTime` type.
*
* @tparam T Type of argument to instantiate time parser
* @param parsing Argument to instantiate time parser. Actually either a pattern (string) or a formatter (`java.time.format.DateTimeFormatter`)
* @param corrector a simple string transformation function that can be used to transform input String before parsing. Useful when standards are not exactly respected and require a few tweaks. Function `identity` can be passed if no correction is needed.
* @param p Typeclass instance based on `parsing`
* @see [[DefaultWrites.TemporalFormatter]]
*
* {{{
* import java.time.format.DateTimeFormatter
*
* import play.api.libs.json.Reads.localTimeReads
*
* val customReads1 = localTimeReads("dd/MM/yyyy, HH:mm:ss")
* val customReads2 = localTimeReads(DateTimeFormatter.ISO_TIME)
* val customReads3 = localTimeReads(DateTimeFormatter.ISO_TIME, _.drop(1))
* }}}
*/
def localTimeReads[T](parsing: T, corrector: String => String = identity)(implicit
p: T => TemporalParser[LocalTime]
): Reads[LocalTime] =
new Reads[LocalTime] {
def reads(json: JsValue): JsResult[LocalTime] = json match {
case n: JsNumber => n.validate[Long].map(epoch)
case JsString(s) =>
p(parsing).parse(corrector(s)) match {
case Some(d) => JsSuccess(d)
case _ =>
JsError(
Seq(
JsPath ->
Seq(JsonValidationError("error.expected.date.isoformat", parsing))
)
)
}
case _ =>
JsError(
Seq(
JsPath ->
Seq(JsonValidationError("error.expected.date"))
)
)
}
@inline def epoch(nanos: Long): LocalTime = LocalTime.ofNanoOfDay(nanos)
}
/**
* The default typeclass to reads `java.time.LocalTime` from JSON.
* Accepts date formats as '10:15:30' (or '10:15').
*/
implicit val DefaultLocalTimeReads: Reads[LocalTime] =
localTimeReads(DateTimeFormatter.ISO_TIME)
// ---
/**
* Reads for the `java.time.ZoneId` type.
*/
implicit val ZoneIdReads: Reads[ZoneId] = Reads[ZoneId] {
case JsString(s) =>
try {
JsSuccess(ZoneId.of(s))
} catch {
case _: DateTimeException => JsError(JsonValidationError("error.expected.timezone", s))
}
case _ => JsError(JsonValidationError("error.expected.jsstring"))
}
/** Deserializer for a `Locale` from a IETF BCP 47 string representation */
implicit val localeReads: Reads[Locale] =
Reads[Locale] {
_.validate[String].flatMap(KeyReads.LanguageTagReads.readKey(_))
}
/** Deserializer for a `Locale` from an object representation */
val localeObjectReads: Reads[Locale] = Reads[Locale] { json =>
def base: JsResult[Locale] =
(for {
l <- (json \ "language").validate[String]
c <- (json \ "country").validateOpt[String]
v <- (json \ "variant").validateOpt[String]
} yield (l, c, v)).flatMap {
case (l, Some(country), Some(variant)) =>
JsSuccess(new Locale(l, country, variant))
case (l, Some(country), _) =>
JsSuccess(new Locale(l, country))
case (l, _, Some(_)) =>
JsError("error.invalid.locale")
case (l, _, _) => JsSuccess(new Locale(l))
}
base.flatMap { baseLocale =>
for {
ats <- (json \ "attributes").validateOpt[Set[String]]
kws <- (json \ "keywords").validateOpt[Map[String, String]]
spt <- (json \ "script").validateOpt[String]
ext <- (json \ "extension").validateOpt(Reads.mapReads[Char, String] { s =>
if (s.size == 1) JsSuccess(s.charAt(0))
else JsError("error.invalid.character")
})
} yield {
val builder = new Locale.Builder()
builder.setLocale(baseLocale)
ats.foreach(_.foreach { builder.addUnicodeLocaleAttribute(_) })
kws.foreach(_.foreach { case (key, typ) =>
builder.setUnicodeLocaleKeyword(key, typ)
})
ext.foreach(_.foreach { case (key, value) =>
builder.setExtension(key, value)
})
spt.foreach { builder.setScript(_) }
builder.build()
}
}
}
private def jdurationNumberReads(unit: TemporalUnit) =
Reads[JDuration] {
case n: JsNumber => n.validate[Long].map(l => JDuration.of(l, unit))
case _ => JsError("error.expected.longDuration")
}
/**
* Deserializer of Java Duration from an integer (long) number,
* using the specified temporal unit.
*/
def javaDurationNumberReads(unit: TemporalUnit): Reads[JDuration] =
jdurationNumberReads(unit)
/** Deserializer of Java Duration from a number of milliseconds. */
val javaDurationMillisReads: Reads[JDuration] =
javaDurationNumberReads(ChronoUnit.MILLIS)
/**
* Deserializer of Java Duration, from either a time-based amount of time
* (string representation such as '34.5 seconds'),
* or from a number of milliseconds (see [[javaDurationMillisReads]]).
*
* @see [[java.time.Duration]]
* @see [[DefaultJavaPeriodReads]] if you want to use [[java.time.Period]] instead.
*/
implicit val DefaultJavaDurationReads: Reads[JDuration] = Reads[JDuration] {
case JsString(repr) =>
try {
JsSuccess(JDuration.parse(repr))
} catch {
case _: DateTimeParseException => JsError("error.invalid.duration")
}
case js => javaDurationMillisReads.reads(js)
}
/** Deserializer of Java Period from a number (integer) of days. */
val javaPeriodDaysReads: Reads[Period] =
Reads.IntReads.map(Period.ofDays)
/** Deserializer of Java Period from a number (integer) of weeks. */
val javaPeriodWeeksReads: Reads[Period] =
Reads.IntReads.map(Period.ofWeeks)
/** Deserializer of Java Period from a number (integer) of months. */
val javaPeriodMonthsReads: Reads[Period] =
Reads.IntReads.map(Period.ofMonths)
/** Deserializer of Java Period from a number (integer) of years. */
val javaPeriodYearsReads: Reads[Period] =
Reads.IntReads.map(Period.ofYears)
/**
* Deserializer of Java Period, from either a time-based amount of time
* in the ISO-8601 calendar system, such as '2 years, 3 months and 4 days'
* or from a number of days (see [[javaPeriodDaysReads]]).
*
* @see [[java.time.Period]]
* @see [[DefaultJavaDurationReads]] if you want to use [[java.time.Duration]] instead.
*/
implicit val DefaultJavaPeriodReads: Reads[Period] = Reads[Period] {
case JsString(repr) =>
try {
JsSuccess(Period.parse(repr))
} catch {
case _: DateTimeParseException => JsError("error.invalid.stringPeriod")
}
case js => javaPeriodDaysReads.reads(js)
}
protected def parseBigDecimal(input: String): JsResult[java.math.BigDecimal] =
BigDecimalParser.parse(input, JsonParserSettings.settings)
protected def parseBigInteger(input: String): JsResult[java.math.BigInteger] = {
if (input.length > JsonParserSettings.settings.bigDecimalParseSettings.digitsLimit) {
JsError("error.expected.numberdigitlimit")
} else {
try {
JsSuccess(new java.math.BigInteger(input))
} catch {
case _: NumberFormatException =>
JsError(JsonValidationError("error.expected.numberformatexception"))
}
}
}
}
trait EnvKeyReads { self: KeyReads.type =>
/**
* Reads an object key as a locale, considering the key
* to be a [[https://tools.ietf.org/html/rfc5646 language tag]].
*/
implicit object LanguageTagReads extends KeyReads[Locale] {
def readKey(key: String): JsResult[Locale] =
try {
JsSuccess(Locale.forLanguageTag(key))
} catch {
case NonFatal(cause) => JsError(JsonValidationError(Seq("error.expected.languageTag"), cause.getMessage))
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy