JsonPrism

dev.constructive.eo.circe.JsonPrism
See theJsonPrism companion object
final class JsonPrism[A] extends Optic[Json, Json, A, A, Affine], Dynamic

Specialised optic from io.circe.Json to native A.

 JsonPrism[A]  Json)  // Fst = source (miss); Snd = single-walk writer

'''An Optional, not a Prism.''' A drilled focus lives INSIDE a document, so rebuilding needs the siblings — which a Prism's from(reverseGet) cannot see (it gets the focus alone). Carrying the source on the Affine seam fixes that at the carrier level: to captures a writer over the walk it already did, and from(Hit) applies it — so .modify / .replace preserve siblings whether called directly, upcast to Optic[…, Affine], or composed via andThen. Only the root full-cover prism is also a lawful Prism, and reverseGet stays for it. Mirrors dev.constructive.eo.avro.AvroRecordPrism.

Two call-surface tiers:

  • Default Ior-bearing: modify / get etc. accumulate Chain[JsonFailure]; partial success surfaces as Ior.Both(chain, inputJson).
  • *Unsafe: silent pass-through hot path.

Storage decomposition: a JsonPrism[A] holds a JsonFocus (Leaf vs Fields). The compatibility alias JsonFieldsPrism points back at this class so old code keeps compiling.

Attributes

Companion
object
Source
JsonPrism.scala
Graph
Supertypes
trait Dynamic
trait Optic[Json, Json, A, A, Affine]
class Object
trait Matchable
class Any

Members list

Grouped members

Operations

inline def cross[C, D](o: Optic[Json, Json, C, D, Affine])(using Accessor[Affine], ReverseAccessor[Affine]): Optic[A, A, C, D, Affine]

Build-then-observe across the build-output ⇄ read-input seam, preserving structure, on a shared carrier F. Flip self (it must be reversible — Accessor[F] and ReverseAccessor[F], i.e. an Iso or Review over Direct) so it reads T from B, then andThen that under the same carrier. The result is the full Optic[B, A, C, D, F], not a collapsed getter: its read capability follows the carrier (.get for Direct), and self's read focus A survives as the composite's write-back focus.

Build-then-observe across the build-output ⇄ read-input seam, preserving structure, on a shared carrier F. Flip self (it must be reversible — Accessor[F] and ReverseAccessor[F], i.e. an Iso or Review over Direct) so it reads T from B, then andThen that under the same carrier. The result is the full Optic[B, A, C, D, F], not a collapsed getter: its read capability follows the carrier (.get for Direct), and self's read focus A survives as the composite's write-back focus.

This is exactly self.reverse.andThen(that). The motivating case is ana.cross(cata): a Review (the unfold) crossed with a getter on the built S (the fold) — a (materializing) hylomorphism whose .get reads the folded value. When that sits on a different carrier (a Prism, a Fold, …), the cross-carrier cross overload below is selected instead.

Seam: that's source is self's T and its back-type is self's S.

Attributes

Inherited from:
Optic
Source
Optic.scala

Type members

Types

type X = (Json, A => Json)

Existential seam: Fst[X] = source json (Miss pass-through); Snd[X] = the single-walk sibling-preserving writer captured by to.

Existential seam: Fst[X] = source json (Miss pass-through); Snd[X] = the single-walk sibling-preserving writer captured by to.

Attributes

Source
JsonPrism.scala

Value members

Concrete methods

transparent inline def at(i: Int): Any
Extension method from JsonPrism

.at(i) — drill into the i-th array element.

.at(i) — drill into the i-th array element.

Attributes

Source
JsonPrism.scala
transparent inline def each: Any
Extension method from JsonPrism

.each — split into a JsonTraversal over the iterated array.

.each — split into a JsonTraversal over the iterated array.

Attributes

Source
JsonPrism.scala
transparent inline def field[B](inline selector: A => B)(using encB: Encoder[B], decB: Decoder[B]): JsonPrism[B]
Extension method from JsonPrism

.field(_.x) — drill via selector lambda.

.field(_.x) — drill via selector lambda.

Attributes

Source
JsonPrism.scala
transparent inline def fields(inline selectors: A => Any*): Any
Extension method from JsonPrism

.fields(_.a, _.b, ...) — focus a NamedTuple over selected fields.

.fields(_.a, _.b, ...) — focus a NamedTuple over selected fields.

Attributes

Source
JsonPrism.scala
def from(aff: Affine[X, A]): Json

Rebuild: Hit applies the captured writer to the (possibly modified) focus; Miss returns the source unchanged.

Rebuild: Hit applies the captured writer to the (possibly modified) focus; Miss returns the source unchanged.

Attributes

Source
JsonPrism.scala
def get(input: Json | String): Ior[Chain[JsonFailure], A]

Read the focus. String input is parsed first (parse failures arrive as JsonFailure.ParseFailed); navigation / decode failures surface as Ior.Left(chain).

Read the focus. String input is parsed first (parse failures arrive as JsonFailure.ParseFailed); navigation / decode failures surface as Ior.Left(chain).

Attributes

Source
JsonPrism.scala
inline def getOptionUnsafe(input: Json | String): Option[A]

Silent read — None on any parse / navigation / decode failure.

Silent read — None on any parse / navigation / decode failure.

Attributes

Source
JsonPrism.scala
inline def reverseGet(a: A): Json

Encode a standalone. Lawful only for the ROOT full-cover prism (a real Prism.reverseGet); on a drilled prism it cannot restore siblings, which is why the Optic seam carries the source instead of calling this. Kept for root full-cover users and the reverseGet round-trip laws.

Encode a standalone. Lawful only for the ROOT full-cover prism (a real Prism.reverseGet); on a drilled prism it cannot restore siblings, which is why the Optic seam carries the source instead of calling this. Kept for root full-cover users and the reverseGet round-trip laws.

Attributes

Source
JsonPrism.scala
transparent inline def selectDynamic(inline name: String): Any

Dynamic field sugar — codecPrism[Person].name lowers to codecPrism[Person].field(_.name). Compile-time checked against A's case fields; the field's codecs are summoned at the call site, so the result is a precisely-typed child JsonPrism.

Dynamic field sugar — codecPrism[Person].name lowers to codecPrism[Person].field(_.name). Compile-time checked against A's case fields; the field's codecs are summoned at the call site, so the result is a precisely-typed child JsonPrism.

Attributes

Source
JsonPrism.scala
def to(json: Json): Affine[X, A]

Navigate + decode in ONE walk. Hit carries the decoded focus and a writer that splices a new focus back into the SOURCE json (siblings preserved); Miss carries the source for pass-through.

Navigate + decode in ONE walk. Hit carries the decoded focus and a writer that splices a new focus back into the SOURCE json (siblings preserved); Miss carries the source for pass-through.

Attributes

Source
JsonPrism.scala

Inherited methods

def andThen[C, IB, G[_, _]](inner: Optic[A, Unit, C, IB, G])(using rc: ReadCompose[Affine, G]): rc.Out[Json, C]

ANY outer ∘ read-only inner — the inner is honestly one-way (T = Unit: a Getter, AffineFold, or Fold), so only the two READ sides matter and the composite collapses to the read-only join of their strengths via compose.ReadCompose (Getter / PickFold / ForgetFold).

ANY outer ∘ read-only inner — the inner is honestly one-way (T = Unit: a Getter, AffineFold, or Fold), so only the two READ sides matter and the composite collapses to the read-only join of their strengths via compose.ReadCompose (Getter / PickFold / ForgetFold).

A trait member (not an extension in the companion) deliberately: once a receiver is statically one of the fused concrete classes, its andThen member overloads enter resolution and Scala 3 never falls back to extension methods when they all fail — the collapse must be in the member overload set to be reachable without an expected-type ascription.

Only the inner's T is pinned to Unit; its B stays free (IB) even though read-only inners always have B = Unit. That keeps this overload strictly LESS specific than the same-carrier andThen above (which accepts every argument this one does whenever B = Unit at the receiver), so a same-carrier read-only ∘ read-only call resolves unambiguously to the AssociativeFunctor path and this one fires exactly on the cross-seam cells the generic member cannot type.

Attributes

Inherited from:
Optic
Source
Optic.scala
inline def andThen[C, D](o: Optic[A, A, C, D, Affine]): Optic[Json, Json, C, D, Affine]

Compose with another optic under the shared carrier F. Requires AssociativeFunctor[F]. Cross-carrier composition (Lens → Optional, Lens → Traversal, …) goes through the Morph-summoning overload of this same method.

Compose with another optic under the shared carrier F. Requires AssociativeFunctor[F]. Cross-carrier composition (Lens → Optional, Lens → Traversal, …) goes through the Morph-summoning overload of this same method.

Attributes

Example
case class Address(street: String)
case class Person(address: Address)
val streetLens = lens[Person](_.address).andThen(lens[Address](_.street))
Inherited from:
Optic
Source
Optic.scala
def modify(f: A => A): (Json | String) => Ior[Chain[JsonFailure], Json]

Decode each focus, apply f, re-encode in place. String input is parsed first; failures accumulate as Chain[JsonFailure], with the input Json preserved on the Ior.Both side.

Decode each focus, apply f, re-encode in place. String input is parsed first; failures accumulate as Chain[JsonFailure], with the input Json preserved on the Ior.Both side.

Attributes

Inherited from:
JsonOpticOps (hidden)
Source
JsonOpticOps.scala
def modifyUnsafe(f: A => A): (Json | String) => Json

Silent modify — any parse / navigation / decode failure passes the input through unchanged (an unparseable String becomes Json.Null first).

Silent modify — any parse / navigation / decode failure passes the input through unchanged (an unparseable String becomes Json.Null first).

Attributes

Inherited from:
JsonOpticOps (hidden)
Source
JsonOpticOps.scala
def place(a: A): (Json | String) => Ior[Chain[JsonFailure], Json]

Replace each focus with the encoding of a. Same parse / failure-accumulation behaviour as modify.

Replace each focus with the encoding of a. Same parse / failure-accumulation behaviour as modify.

Attributes

Inherited from:
JsonOpticOps (hidden)
Source
JsonOpticOps.scala
def placeUnsafe(a: A): (Json | String) => Json

Silent place — input pass-through on any failure.

Silent place — input pass-through on any failure.

Attributes

Inherited from:
JsonOpticOps (hidden)
Source
JsonOpticOps.scala
def transfer[C](f: C => A): (Json | String) => C => Ior[Chain[JsonFailure], Json]

place from a foreign source: replace each focus with the encoding of f(c).

place from a foreign source: replace each focus with the encoding of f(c).

Attributes

Inherited from:
JsonOpticOps (hidden)
Source
JsonOpticOps.scala
def transferUnsafe[C](f: C => A): (Json | String) => C => Json

Silent transfer — input pass-through on any failure.

Silent transfer — input pass-through on any failure.

Attributes

Inherited from:
JsonOpticOps (hidden)
Source
JsonOpticOps.scala
def transform(f: Json => Json): (Json | String) => Ior[Chain[JsonFailure], Json]

Rewrite the raw Json at each focus — no decode of the focus, so f sees (and must return) plain Json. Same parse / failure-accumulation behaviour as modify.

Rewrite the raw Json at each focus — no decode of the focus, so f sees (and must return) plain Json. Same parse / failure-accumulation behaviour as modify.

Attributes

Inherited from:
JsonOpticOps (hidden)
Source
JsonOpticOps.scala
def transformUnsafe(f: Json => Json): (Json | String) => Json

Silent transform — input pass-through on any failure.

Silent transform — input pass-through on any failure.

Attributes

Inherited from:
JsonOpticOps (hidden)
Source
JsonOpticOps.scala