All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.dylemma.spac.json.package.scala Maven / Gradle / Ivy

There is a newer version: 0.12.1
Show newest version
package io.dylemma.spac

import io.dylemma.spac.json.impl.{ArrayIndexContextMatcher, JsonParserTypedFirst, ObjectFieldContextMatcher}
import org.tpolecat.typename.TypeName

import scala.language.implicitConversions

/** This package provides extensions to the core "spac" library which allow for the handling of JSON data.
  *
  * Rather than creating explicit classes that extend `Parser`, `Transformer`, and `Splitter`,
  * this package provides type aliases and implicit extensions.
  * For example, `JsonParser[A]` is just a type alias for `Parser[JsonEvent, A]`,
  * and `JsonParser` is just a call to `Parser[JsonEvent]`.
  *
  * Implicit JsonParsers are available for each of the JSON primitive types:
  *
  *  - `string`
  *  - `number` (expressed as `Int`, `Long`, `Float`, or `Double`)
  *  - `boolean`
  *  - `null` (expressed as `None.type`)
  *
  * Helpers are available for parsing JSON arrays and objects:
  *
  *  - `JsonParser.listOf[A]` to parse an `array`` where each value is an `A`
  *  - `JsonParser.objectOf[A]` to parse an `object` where the value for each field an `A`
  *  - `JsonParser.objectOfNullable[A]` to parse an `object` where the value for each field is either `null` or an `A`, filtering out the `null`s
  *  - `JsonParser.fieldOf[A](fieldName)` to parse a specific field from an object
  *
  * A DSL for creating json-specific ContextMatchers is provided to make it more convenient to call `Splitter.json`.
  * For example:
  * {{{
  *     Splitter.json("foo" \ "bar").as[String].parseFirst
  * }}}
  * Can be used to capture `rootJson.foo.bar` as a String in
  * {{{
  *  {
  *    "foo": {
  *      "bar": "hello"
  *    }
  *  }
  * }}}
  *
  * To "split" values inside arrays, index-related context matchers are available, e.g.
  * {{{
  *     Splitter.json("foo" \ anyIndex).as[Int].parseToList
  * }}}
  * Can be used to capture each of the numbers in the "foo" array in
  * {{{
  *  {
  *    "foo": [1, 2, 3]
  *  }
  * }}}
  *
  * A note about JsonEvents in spac:
  * JSON doesn't have any explicit markers for when a field ends, or when an array index starts or ends;
  * those context changes are essentially inferred by the presence of some other event.
  * For example, instead of a "field end" event, typically there will be either a new "field start" or a token representing the end of the current object.
  * With spac, splitters and context matchers generally operate under the assumption that a "stack push" event (like a field start) will eventually be
  * followed by a corresponding "stack pop" event (i.e. field end).
  *
  * To allow for this, these "inferred" events (FieldEnd, IndexStart, IndexEnd) are explicitly represented as JsonEvents in the stream being parsed.
  * Keep this in mind when creating JSON ContextMatchers:
  *
  *  - `field`-related matchers will match a stack like `case ObjectStart :: FieldStart(_) :: _`
  *  - `index`-related matchers will match a stack like `case ArrayStart :: IndexStart(_) :: _`
  *
  * @groupname aliases JSON-specific Type and Value aliases
  * @groupname extensions JSON-specific extensions for Parser and Splitter
  * @groupname contextMatcherSyntax JSON Context Matcher Construction
  * @groupname event JSON Event Representation
  * @groupname support Backend Parser Support
  * @groupprio extensions 0
  * @groupprio aliases 1
  * @groupprio contextMatcherSyntax 2
  * @groupprio event 3
  * @groupprio support 4
  */
package object json {

	/** Like the `Parser` companion object, but only for creating Parsers whose input type is `JsonEvent`.
	  *
	  * @see [[JsonParserApplyOps]]
	  * @group aliases
	  */
	val JsonParser: ParserApplyWithBoundInput[JsonEvent] = Parser[JsonEvent]

	/** Type alias for a `Parser` whose input type is `JsonEvent`
	  *
	  * @group aliases
	  */
	type JsonParser[+Out] = Parser[JsonEvent, Out]

	/** Provides JSON-specific Parser constructor methods to the `JsonParser` object, for example `JsonParser.fieldOf`.
	  *
	  * Technically `JsonParser` is not a companion object, it is a partially-applied version of the `Parser` companion
	  * object which binds the input type to `JsonEvent`, so "companion methods" must instead be added as extension methods.
	  *
	  * @group extensions
	  * @groupname primitive Primitives
	  * @groupname high Objects and Arrays
	  */
	implicit class JsonParserApplyOps(private val parserApply: ParserApplyWithBoundInput[JsonEvent]) extends AnyVal {

		/** Creates a JsonParser which captures the first JsonEvent it sees, expecting a primitive that can satisfy the `matchPrimitive` function.
		  *
		  * This is the low-level method used to implement the `forString`, `forInt`, etc parsers.
		  * Its main use outside of those is if you want to create a parser that handles multiple different primitives without resorting to the use of `orElse`.
		  *
		  * @param describePrimitive A message describing the type of event that this parser expects. Used to construct a SpacException if the `matchPrimitive` returns `None`
		  * @param matchPrimitive    A function used to extract a value from the first JsonEvent this parser's handler encounters.
		  * @tparam A The type of the extracted value
		  * @return A JsonParser which attempts to extract some value from the first event it encounters, throwing a SpacException if it cannot
		  * @group primitive
		  */
		def forPrimitive[A](describePrimitive: String, matchPrimitive: JsonEvent => Option[A]): JsonParser[A] = new JsonParserTypedFirst(describePrimitive, matchPrimitive)

		/** Wraps an existing JsonParser, creating a new JsonParser that will succeed with `None` if it encounters a `null`,
		  * or succeed with a `Some(t)` if the wrapped parser succeeds.
		  *
		  * Used to parse values that may or may not be null.
		  *
		  * Typical usage would be `JsonParser.nullable[String]`, passing the underlying `JsonParser[String]` implicitly.
		  *
		  * @param parser The underlying parser which would typically fail upon encountering a `null`
		  * @tparam T The type of the underlying parser's extracted value
		  * @return A JsonParser which parses `null` as `None`, or else delegates to the underlying `parser`
		  * @group primitive
		  */
		def nullable[T](implicit parser: JsonParser[T]): JsonParser[Option[T]] = parserApply.oneOf(parser.map(Some(_)), forNull)

		/** A JsonParser that captures the string value from a `JsString` event, failing if the first event is not a `JsString`.
		  *
		  * @group primitive
		  */
		def forString: JsonParser[String] = jsonParserForPrimitiveString

		/** A JsonParser that captures a `JLong` event as an `Int` value, failing if the first event is not a `JLong`.
		  *
		  * @group primitive
		  */
		def forInt: JsonParser[Int] = jsonParserForPrimitiveInt

		/** A JsonParser that captures a `JLong` event as a `Long` value, failing if the first event is not a `JLong`.
		  *
		  * @group primitive
		  */
		def forLong: JsonParser[Long] = jsonParserForPrimitiveLong

		/** A JsonParser that captures a `JDouble` event as a `Float` value, failing if the first event is not a `JDouble`.
		  *
		  * @group primitive
		  */
		def forFloat: JsonParser[Float] = jsonParserForPrimitiveFloat

		/** A JsonParser that captures a `JDouble` event as a `Double` value, failing if the first event is not a `JDouble`.
		  *
		  * @group primitive
		  */
		def forDouble: JsonParser[Double] = jsonParserForPrimitiveDouble

		/** A JsonParser that captures the `Boolean` value from a `JBool` event, failing if the first event is not a `JBool`.
		  *
		  * @group primitive
		  */
		def forBoolean: JsonParser[Boolean] = jsonParserForPrimitiveBoolean

		/** A JsonParser that returns `None` upon encountering a `JNull` event, failing upon encountering any other event or an EOF.
		  *
		  * @group primitive
		  */
		def forNull: JsonParser[None.type] = jsonParserForPrimitiveNull

		/** A JsonParser that parses a JSON object by parsing the given mandatory field with the implicitly-available `JsonParser[T]`.
		  *
		  * The returned parser will fail if the first event is not an `ObjectStart`,
		  * or if the expected field does not appear inside the object,
		  * or if the value of the expected field causes the underlying parser to fail.
		  *
		  * This is a shortcut for `Splitter.json(fieldName).as[T].parseFirst`.
		  *
		  * E.g.
		  * {{{
		  *    val rawJson = """{ "foo": 1, "bar": true }"""
		  *    val parser = JsonParser.fieldOf[Boolean]("bar")
		  *    parser.parse(rawJson) // returns `true`
		  * }}}
		  *
		  * @param fieldName The name of the expected field
		  * @param callerPos Macro-derived location of the code calling this method, used to form a SpacTraceElement when the returned parser fails
		  * @tparam T The type of the extracted value
		  * @return A JsonParser that parses the given field from a JSON object as a value of type `T`
		  * @group high
		  */
		def fieldOf[T: TypeName : JsonParser](fieldName: String)(implicit callerPos: CallerPos): JsonParser[T] = fieldOf[T](fieldName, implicitly[JsonParser[T]])

		/** A JsonParser that parses a JSON object by parsing the given mandatory field with the given `JsonParser[T]`.
		  *
		  * Note that if the given `parser` is available implicitly, you can use the other `fieldOf` signature.
		  *
		  * The returned parser will fail if the first event is not an `ObjectStart`,
		  * or if the expected field does not appear inside the object,
		  * or if the value of the expected field causes the underlying parser to fail.
		  *
		  * This is a shortcut for `Splitter.json(fieldName).as[T].parseFirst`.
		  *
		  * E.g.
		  * {{{
		  *    val rawJson = """{ "foo": 1, "bar": true }"""
		  *    val parser = JsonParser.fieldOf[Boolean]("bar")
		  *    parser.parse(rawJson) // returns `true`
		  * }}}
		  *
		  * @param fieldName The name of the expected field
		  * @param parser    The underlying parser used to parse the value inside the expected field in the object
		  * @param callerPos Macro-derived location of the code calling this method, used to form a SpacTraceElement when the returned parser fails
		  * @tparam T The type of the extracted value
		  * @return A JsonParser that parses the given field from a JSON object as a value of type `T`
		  * @group high
		  */
		def fieldOf[T: TypeName](fieldName: String, parser: JsonParser[T])(implicit callerPos: CallerPos): JsonParser[T] = Splitter.json(fieldName).joinBy(parser).parseFirst
			.expectInputs[JsonEvent](List("a '{' token" -> {_.isObjectStart}))
			.withName(s"JsonParser.fieldOf[${implicitly[TypeName[T]].value}]($fieldName)")

		/** A JsonParser that parses a JSON object by parsing the given optional field with an implicitly-available `JsonParser[T]`.
		  *
		  * Unlike with `fieldOf`, the returned parser will succeed with a `None` if the expected field is missing,
		  * or if the value in the expected field is null.
		  * However, it will still fail if the first event is not an `ObjectStart`.
		  *
		  * E.g.
		  * {{{
		  *    val parser = JsonParser.nullableFieldOf[Int]("foo")
		  *    parser.parse("{}") // returns None
		  *    parser.parse(12) // throws a SpacException
		  *    parser.parse("""{ "foo": 42 }""") // returns 42
		  *    parser.parse("""{ "foo": null }""") // returns null
		  *    parser.parse("""{ "foo": "hello" }""") // throws a SpacException
		  * }}}
		  *
		  * @param fieldName The name of the field
		  * @param callerPos Macro-derived location of the code calling this method, used to form a SpacTraceElement when the returned parser fails
		  * @tparam T The type of the extracted value
		  * @return A JsonParser that parses the given field from a JSON object, wrapping a successfully-parsed value in `Some`, and treating null or a missing field as `None`
		  * @group high
		  */
		def nullableFieldOf[T: TypeName : JsonParser](fieldName: String)(implicit callerPos: CallerPos): JsonParser[Option[T]] = nullableFieldOf[T](fieldName, implicitly[JsonParser[T]])

		/** A JsonParser that parses a JSON object by parsing the given optional field with the given `parser`.
		  *
		  * Note that if the given `parser` is available implicitly, you can use the other `nullableFieldOf` signature.
		  *
		  * Unlike with `fieldOf`, the returned parser will succeed with a `None` if the expected field is missing,
		  * or if the value in the expected field is null.
		  * However, it will still fail if the first event is not an `ObjectStart`.
		  *
		  * E.g.
		  * {{{
		  *    val parser = JsonParser.nullableFieldOf("foo", JsonParser.forInt)
		  *    parser.parse("{}") // returns None
		  *    parser.parse("12") // throws a SpacException
		  *    parser.parse("""{ "foo": 42 }""") // returns 42
		  *    parser.parse("""{ "foo": null }""") // returns null
		  *    parser.parse("""{ "foo": "hello" }""") // throws a SpacException
		  * }}}
		  *
		  * @param fieldName The name of the field
		  * @param parser    The underlying parser used to parse the value inside the expected field in the object
		  * @param callerPos Macro-derived location of the code calling this method, used to form a SpacTraceElement when the returned parser fails
		  * @tparam T The type of the extracted value
		  * @return A JsonParser that parses the given field from a JSON object, wrapping a successfully-parsed value in `Some`, and treating null or a missing field as `None`
		  * @group high
		  */
		def nullableFieldOf[T: TypeName](fieldName: String, parser: JsonParser[T])(implicit callerPos: CallerPos): JsonParser[Option[T]] = Splitter.json(fieldName).joinBy(nullable(parser)).parseFirstOpt
			.map(_.flatten)
			.expectInputs[JsonEvent](List("a '{' token" -> {_.isObjectStart}))
			.withName(s"JsonParser.nullableFieldOf[${implicitly[TypeName[T]].value}]($fieldName)")

		/** A JsonParser that parses a JSON array by parsing each item in the array via the implicitly-available `JsonParser[T]`,
		  * collecting the values to a List.
		  *
		  * This is a shortcut for `Splitter.json(anyIndex).as[T].parseToList`.
		  *
		  * The returned parser will fail if the first event is not an `ArrayStart`,
		  * or if any of the values inside the array cause the underlying parser to fail.
		  * E.g. if the underlying parser is `JsonParser.forInt`, but one of the values in the array is a string,
		  * the exception thrown by `JsonParser.forInt` will bubble up through the returned parser.
		  *
		  * E.g.
		  * {{{
		  *    val parser = JsonParser.listOf[Int]
		  *    parser.parse("[1, 2, 3]") // returns List(1, 2, 3)
		  *    parser.parse("[]") // returns Nil
		  *    parser.parse("42") // throws a SpacException
		  *    parser.parse("[1, 2, false]") // throws a SpacException
		  * }}}
		  *
		  * @param callerPos Macro-derived location of the code calling this method, used to form a SpacTraceElement when the returned parser fails
		  * @tparam T The type of the values inside the array
		  * @return A JsonParser that parses an array of values as a List[T]
		  * @group high
		  */
		def listOf[T: TypeName : JsonParser](implicit callerPos: CallerPos): JsonParser[List[T]] = listOf[T](implicitly[JsonParser[T]])

		/** A JsonParser that parses a JSON array by parsing each item in the array via the given `parser`,
		  * collecting the values to a List.
		  *
		  * Note that if the given `parser` is available implicitly, you can use the other `listOf` signature instead.
		  *
		  * This is a shortcut for `Splitter.json(anyIndex).joinBy(parser).parseToList`.
		  *
		  * The returned parser will fail if the first event is not an `ArrayStart`,
		  * or if any of the values inside the array cause the underlying parser to fail.
		  * E.g. if the underlying parser is `JsonParser.forInt`, but one of the values in the array is a string,
		  * the exception thrown by `JsonParser.forInt` will bubble up through the returned parser.
		  *
		  * E.g.
		  * {{{
		  *    val parser = JsonParser.listOf(JsonParser.forInt)
		  *    parser.parse("[1, 2, 3]") // returns List(1, 2, 3)
		  *    parser.parse("[]") // returns Nil
		  *    parser.parse("42") // throws a SpacException
		  *    parser.parse("[1, 2, false]") // throws a SpacException
		  * }}}
		  *
		  * @param parser    The underlying parser to use for each value inside the array
		  * @param callerPos Macro-derived location of the code calling this method, used to form a SpacTraceElement when the returned parser fails
		  * @tparam T The type of the values inside the array
		  * @return A JsonParser that parses an array of values as a List[T]
		  * @group high
		  */
		def listOf[T: TypeName](parser: JsonParser[T])(implicit callerPos: CallerPos): JsonParser[List[T]] = Splitter.json(anyIndex).joinBy(parser).parseToList
			.expectInputs[JsonEvent](List("a '[' token" -> {_.isArrayStart}))
			.withName(s"JsonParser.listOf[${implicitly[TypeName[T]].value}]")

		/** A JsonParser that parses a JSON object by interpreting every field as a value of type `T` using the implicitly-available `JsonParser[T]`,
		  * yielding a `Map` containing the parsed `field -> value` pairs.
		  *
		  * This is a shortcut for `Splitter.json(anyField).map(field -> implicitly[JsonParser[T]].map(field -> _)).parseToMap`.
		  *
		  * The returned parser will fail if the first event is not an `ObjectStart`,
		  * or if any of the field values in the object cause the underlying parser to fail.
		  * E.g. if the underlying parser is `JsonParser.forString`, but one of the fields contains some non-string value,
		  * the exception thrown by `JsonParser.forString` will bubble up through the returned parser.
		  *
		  * E.g.
		  * {{{
		  *    val parser = JsonParser.objectOf[Int]
		  *    parser.parse("""{ "foo": 1, "bar": 2 }""") // returns Map("foo" -> 1, "bar" -> 2)
		  *    parser.parse("""{ "foo": 1, "bar": "whoops" }""") // throws a SpacException
		  *    parser.parse("13") // throws a SpacException
		  * }}}
		  *
		  * @param callerPos Macro-derived location of the code calling this method, used to form a SpacTraceElement when the returned parser fails
		  * @tparam T The type of the values inside each field
		  * @return A JsonParser that parses an object as a `Map[String, T]`
		  * @group high
		  */
		def objectOf[T: TypeName : JsonParser](implicit callerPos: CallerPos): JsonParser[Map[String, T]] = objectOf[T](implicitly[JsonParser[T]])

		/** A JsonParser that parses a JSON object by interpreting every field as a value of type `T` using the given `parser`,
		  * yielding a `Map` containing the parsed `field -> value` pairs.
		  *
		  * Note that if the given `parser` is available implicitly, you can use the other `objectOf` signature instead.
		  *
		  * This is a shortcut for `Splitter.json(anyField).map(field -> parser.map(field -> _)).parseToMap`.
		  *
		  * The returned parser will fail if the first event is not an `ObjectStart`,
		  * or if any of the field values in the object cause the underlying parser to fail.
		  * E.g. if the underlying parser is `JsonParser.forString`, but one of the fields contains some non-string value,
		  * the exception thrown by `JsonParser.forString` will bubble up through the returned parser.
		  *
		  * E.g.
		  * {{{
		  *    val parser = JsonParser.objectOf(JsonParser.forInt)
		  *    parser.parse("""{ "foo": 1, "bar": 2 }""") // returns Map("foo" -> 1, "bar" -> 2)
		  *    parser.parse("""{ "foo": 1, "bar": "whoops" }""") // throws a SpacException
		  *    parser.parse("13") // throws a SpacException
		  * }}}
		  *
		  * @param parser    The underlying parser used to parse each field in the object
		  * @param callerPos Macro-derived location of the code calling this method, used to form a SpacTraceElement when the returned parser fails
		  * @tparam T The type of the values inside each field
		  * @return A JsonParser that parses an object as a `Map[String, T]`
		  * @group high
		  */
		def objectOf[T: TypeName](parser: JsonParser[T])(implicit callerPos: CallerPos): JsonParser[Map[String, T]] = Splitter.json(anyField)
			.map { field => parser.map(field -> _) }
			.parseToMap
			.expectInputs[JsonEvent](List("a '{' token" -> {_.isObjectStart}))
			.withName(s"JsonParser.objectOf[${implicitly[TypeName[T]].value}]")

		/** A JsonParser that parses a JSON object by interpreting every possibly-null field as a value of type `T` using
		  * an implicitly-available `JsonParser[T]`, filtering out null fields and yielding a `Map` containing `field -> value` pairs
		  * of the successfully-parsed fields.
		  *
		  * Note that while fields with `null` instead of an expected `T` value are filtered out,
		  * non-null fields that cause the underlying parser to fail will cause the returned parser to fail as well.
		  * As with `objectOf`, the returned parser will fail if the first event is not an `ObjectStart`.
		  *
		  * E.g.
		  * {{{
		  *    val parser = JsonParser.objectOfNullable[Int]
		  *    parser.parse("""{ "foo": 1, "bar": 2 }""") // returns Map("foo" -> 1, "bar" -> 2)
		  *    parser.parse("""{ "foo": 1, "bar": null }""") // returns Map("foo" -> 1)
		  *    parser.parse("""{ "foo": 1, "bar": "whoops" }""") // throws a SpacException
		  *    parser.parse("13") // throws a SpacException
		  * }}}
		  *
		  * @param callerPos Macro-derived location of the code calling this method, used to form a SpacTraceElement when the returned parser fails
		  * @tparam T The type of the values inside each field
		  * @return A JsonParser that parses an object as a `Map[String, T]`, ignoring fields with `null` values
		  * @group high
		  */
		def objectOfNullable[T: TypeName : JsonParser](implicit callerPos: CallerPos): JsonParser[Map[String, T]] = objectOfNullable[T](implicitly[JsonParser[T]])

		/** A JsonParser that parses a JSON object by interpreting every possibly-null field as a value of type `T` using
		  * the given `JsonParser[T]`, filtering out null fields and yielding a `Map` containing `field -> value` pairs
		  * of the successfully-parsed fields.
		  *
		  * Note that if the given `parser` is available implicitly, you can use the other `objectOfNullable` signature instead.
		  *
		  * Note that while fields with `null` instead of an expected `T` value are filtered out,
		  * non-null fields that cause the underlying parser to fail will cause the returned parser to fail as well.
		  * As with `objectOf`, the returned parser will fail if the first event is not an `ObjectStart`.
		  *
		  * E.g.
		  * {{{
		  *    val parser = JsonParser.objectOfNullable(JsonParser.forInt)
		  *    parser.parse("""{ "foo": 1, "bar": 2 }""") // returns Map("foo" -> 1, "bar" -> 2)
		  *    parser.parse("""{ "foo": 1, "bar": null }""") // returns Map("foo" -> 1)
		  *    parser.parse("""{ "foo": 1, "bar": "whoops" }""") // throws a SpacException
		  *    parser.parse("13") // throws a SpacException
		  * }}}
		  *
		  * @param parser    The underlying parser used to parse values (aside from null) for each of the fields in the input object
		  * @param callerPos Macro-derived location of the code calling this method, used to form a SpacTraceElement when the returned parser fails
		  * @tparam T The type of the values inside each field
		  * @return A JsonParser that parses an object as a `Map[String, T]`, ignoring fields with `null` values
		  * @group high
		  */
		def objectOfNullable[T: TypeName](parser: JsonParser[T])(implicit callerPos: CallerPos): JsonParser[Map[String, T]] = Splitter.json(anyField)
			.map { field => nullable(parser).map(field -> _) }
			.collect { case (field, Some(value)) => field -> value }
			.parseToMap
			.expectInputs[JsonEvent](List("a '{' token" -> {_.isObjectStart}))
			.withName(s"JsonParser.objectOfNullable[${implicitly[TypeName[T]].value}]")
	}

	/** Implicit version of `JsonParser.forString`
	  *
	  * @group extensions
	  */
	implicit val jsonParserForPrimitiveString: JsonParser[String] = JsonParser.forPrimitive("a String value", _.asString.map(_.stringValue))

	/** Implicit version of `JsonParser.forInt`
	  *
	  * @group extensions
	  */
	implicit val jsonParserForPrimitiveInt: JsonParser[Int] = JsonParser.forPrimitive("an Int value", _.asLong.map(_.longValue.intValue))

	/** Implicit version of `JsonParser.forLong`
	  *
	  * @group extensions
	  */
	implicit val jsonParserForPrimitiveLong: JsonParser[Long] = JsonParser.forPrimitive("a Long value", _.asLong.map(_.longValue))

	/** Implicit version of `JsonParser.forFloat`
	  *
	  * @group extensions
	  */
	implicit val jsonParserForPrimitiveFloat: JsonParser[Float] = JsonParser.forPrimitive("a Float value", _.asDouble.map(_.doubleValue.floatValue))

	/** Implicit version of `JsonParser.forDouble`
	  *
	  * @group extensions
	  */
	implicit val jsonParserForPrimitiveDouble: JsonParser[Double] = JsonParser.forPrimitive("a Double value", _.asDouble.map(_.doubleValue))

	/** Implicit version of `JsonParser.forBoolean`
	  *
	  * @group extensions
	  */
	implicit val jsonParserForPrimitiveBoolean: JsonParser[Boolean] = JsonParser.forPrimitive("a Boolean value", _.asBool.map(_.booleanValue))

	/** Implicit version of `JsonParser.forNull`
	  *
	  * @group extensions
	  */
	implicit val jsonParserForPrimitiveNull: JsonParser[None.type] = JsonParser.forPrimitive("a Null value", _.asNull.map(_ => None))

	/** Like the `Transformer` companion object, but only for creating `Transformers` whose input type is `JsonEvent`.
	  *
	  * @group aliases
	  */
	val JsonTransformer: TransformerApplyWithBoundInput[JsonEvent] = Transformer[JsonEvent]

	/** Type alias for a `Transformer` whose input type is `JsonEvent`.
	  *
	  * @group aliases
	  */
	type JsonTransformer[+Out] = Transformer[JsonEvent, Out]

	/** Like the `Splitter` companion object, but only for creating `Splitters` whose input type is `JsonEvent`.
	  *
	  * @group aliases
	  */
	val JsonSplitter: SplitterApplyWithBoundInput[JsonEvent] = Splitter[JsonEvent]

	/** Type alias for a `Splitter` whose input type is `JsonEvent`
	  *
	  * @group aliases
	  */
	type JsonSplitter[+C] = Splitter[JsonEvent, C]

	/** Adds `Splitter.json`, for constructing json context matcher-based JsonSplitters
	  *
	  * @group extensions
	  */
	implicit class JsonSplitterApplyOps(private val splitter: Splitter.type) extends AnyVal {
		/** Create a Splitter for JsonEvents using the given `matcher` to determine where sub-streams start and end.
		  * For example, `Splitter.json("foo")`, when applied to the json:
		  * {{{
		  * {
		  *   "foo": [1, 2],
		  *   "bar": true
		  * }
		  * }}}
		  * would identify the value of the object's "foo" field as a sub-stream of JsonEvents, containing
		  * the events `ArrayStart, IndexStart(0), JLong(1), IndexEnd, IndexStart(1), JLong(2), IndexEnd, ArrayEnd`.
		  *
		  * Any context matched by the `matcher` will be passed through the `joiner` functions if you
		  * call `as`, `map`, or `flatMap` on the resulting splitter, and thus the matched context
		  * can be used to decide how you parse each sub-stream.
		  *
		  * @param matcher A ContextMatcher used to identify where each sub-stream begins and ends,
		  *                and extracts some context value to identify each sub-stream.
		  * @param pos     Used to construct a SpacFrameElement if a parser constructed from this splitter fails
		  * @tparam C The type of the "context" matched by the `matcher`
		  * @return A new JsonSplitter that will split a stream into sub-streams identified by the `matcher`
		  */
		def json[C](matcher: ContextMatcher[JsonStackElem, C])(implicit pos: CallerPos): JsonSplitter[C] = splitter.fromMatcher(matcher)
	}

	/** Adds `splitter.asNullable[A]`, for handling possibly-null values in a JSON substream
	  *
	  * @group extensions
	  */
	implicit class JsonSplitterOps[C](private val splitter: JsonSplitter[C]) extends AnyVal {

		/** Alternative to `splitter.as[A]` which also accepts `null` in place of the normal JSON for `A`.
		  *
		  * @tparam A Any type for which an implicit `JsonParser[A]` exists
		  */
		def asNullable[A: JsonParser]: JsonTransformer[Option[A]] = splitter.joinBy(JsonParser.nullable[A])
	}

	// ------------------------------------------------------

	/** @group aliases */
	type JsonContextMatcher[+C] = ContextMatcher[JsonStackElem, C]

	/** Context matcher that matches events within a given JSON object field.
	  * Specifically this matcher looks for an `ObjectStart` followed by a `FieldStart(name)` in the JSON context stack.
	  *
	  * When calling `Splitter.json`, the fact that this method is `implicit` allows you to use the underlying field name
	  * directly instead of directly calling this method, e.g. `Splitter.json("address" \ "street")`
	  *
	  * @param name the name of the field
	  * @group contextMatcherSyntax
	  */
	implicit def field(name: String): JsonContextMatcher[Unit] = new ObjectFieldContextMatcher[Unit](name, field => if (field.fieldName == name) Some(()) else None)

	/** Context matcher that matches events within certain JSON object fields, extracting some context value based on the name of the matched field.
	  * Specifically this matcher looks for an `ObjectStart` followed by a `FieldStart(name)`, then plugs the `name` into `contextFromName`.
	  * If `contextFromName` returns a `Some`, the match succeeds and the value inside the Some is returned.
	  *
	  * @param contextFromName a function that extracts a context value based on the name of the JSON object field
	  * @group contextMatcherSyntax
	  */
	def field[A](contextFromName: String => Option[A]): JsonContextMatcher[A] = new ObjectFieldContextMatcher[A]("field<>", field => contextFromName(field.fieldName))

	/** Context matcher that matches events within JSON object fields whose names case the given `f` predicate to return `true`.
	  * Specifically this matcher looks for an `ObjectStart` followed by a `FieldStart(name)` where `f(name)` is `true`.
	  * If the match succeeds, the name of the field is also extracted as a context value.
	  *
	  * @param f A predicate applied to field names to determine whether the current JSON context stack matches
	  * @group contextMatcherSyntax
	  */
	def fieldWhere(f: String => Boolean): JsonContextMatcher[String] = new ObjectFieldContextMatcher[String]("fieldWhere<>", field => Some(field.fieldName).filter(f))

	/** Context matcher that matches events with any JSON object field, capturing the name of the field as context.
	  * Specifically this matcher looks for an `ObjectStart` followed by a `FieldStart(name)`, for any `name`.
	  *
	  * @group contextMatcherSyntax
	  */
	def anyField: JsonContextMatcher[String] = new ObjectFieldContextMatcher[String]("anyField", field => Some(field.fieldName))

	/** Context matcher that matches events within a JSON array at a specific index.
	  * Specifically this matcher looks for an `ArrayStart` followed by an `IndexStart(i)` in the JSON context stack.
	  *
	  * @group contextMatcherSyntax
	  */
	def index(i: Int): JsonContextMatcher[Unit] = new ArrayIndexContextMatcher[Unit](s"index($i)", e => if (e.index == i) Some(()) else None)

	/** Context matcher that matches events within a JSON array, at certain indexes.
	  * Specifically this matcher looks for an `ArrayStart` followed by an `IndexStart(i)` in the JSON context stack,
	  * where the `i` from the `IndexStart` is passed to `contextFromIndex`.
	  * If `contextFromIndex(i)` returns a `Some`, the match succeeds and the value inside the `Some` is returned as a context value.
	  *
	  * @group contextMatcherSyntax
	  */
	def index[A](contextFromIndex: Int => Option[A]): JsonContextMatcher[A] = new ArrayIndexContextMatcher[A]("index<>", e => contextFromIndex(e.index))

	/** Context matcher that matches events within a JSON array, at certain indexes.
	  * Specifically this matcher looks for an `ArrayStart` followed by an `IndexStart(i)` in the JSON context stack, where `f(i)` is `true`.
	  * If the match succeeds, the `i` index will be captured as a context value.
	  *
	  * @group contextMatcherSyntax
	  */
	def indexWhere(f: Int => Boolean): JsonContextMatcher[Int] = new ArrayIndexContextMatcher[Int]("indexWhere<>", e => Some(e.index).filter(f))

	/** Context matcher that matches events within a JSON array, at any index.
	  * Specifically this matcher looks for an `ArrayStart` followed by an `IndexStart(_)` in the JSON context stack.
	  * If the match succeeds, the `i` index will be captured as a context value.
	  *
	  * @group contextMatcherSyntax
	  */
	def anyIndex: JsonContextMatcher[Int] = new ArrayIndexContextMatcher[Int]("anyIndex", e => Some(e.index))
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy