package sjs
- Alphabetic
- By Inheritance
- sjs
- AnyRef
- Any
- Hide All
- Show All
- Public
- All
Type Members
-
trait
FamilyFormats
extends LowPriorityFamilyFormats
Automatically create product/coproduct marshallers (i.e.
Automatically create product/coproduct marshallers (i.e. families of sealed traits and case classes/objects) for spray-json.
Shapeless allows us to view sealed traits as "co-products" (aka
Coproducts) and to view case classes / objects as "products" (akaHLists).Here we write marshallers for
HLists andCoproducts and a converter to/from the generic form.Customisation
Users may provide an implicit
CoproductHint[T]for their sealed traits, allowing the disambiguation scheme to be customised. Some variants are provided to cater for common needs.Users may also provide an implicit
ProductHint[T]for their case classes, allowing wire format field names to be customised and to specialise the handling ofJsNullentries. By default,Noneoptional parameters are omitted and any formatter that outputsJsNullwill be respected.Performance
TL;DR these things are bloody fast, don't even think about runtime performance concerns unless you have hard proof that these serialisers are a bottleneck.
However, **compilation times** may be negatively impacted. Many of the problems are fundamental performance issues in the Scala compiler. In particular, compile times appear to be quadratic with respect to the number of case classes/objects in a sealed family, with compile times becoming unbearable for 30+ implementations of a sealed trait. It may be wise to use encapsulation to reduce the number of implementations of a sealed trait if this becomes a problem. For more information see https://github.com/milessabin/shapeless/issues/381
Benchmarking has shown that these formats are within 5% of the performance of hand-crafted spray-json family format serialisers. The extra overhead can be explained by the conversion to/from
LabelledGeneric, and administration around custom type hinting. These are all just churn of small objects - not computational - and is nothing compared to the performance bottleneck introduced by using anEither(which uses exception throwing for control flow), e.g. https://github.com/spray/spray-json/issues/133 or howOptionfields used to be handled https://github.com/spray/spray-json/pull/136The best performance is obtained by constructing an "explicit implicit" (see the tests for examples) for anything that you specifically wish to convert to / from (e.g. the top level family, if it is on a popular endpoint) because it will reuse the formatters at all stages in the hierarchy. However, the overhead for not defining the
implicit valis less that you might expect (it only accounts for another 5%) whereas eagerly definingimplicit vals for everything in the hierarchy can (counterintuitively) slow things down by 50%.Logging at the
tracelevel is enabled to allow visibility of when formatters are being instantiated.Caveats
If shapeless fails to derive a family format for you, it won't tell you what was missing in your tree. e.g. if you have a
UUIDin a deeply nested case class in aMyFamilysealed trait, it will simply say "can't find implicit for MyFamily" not "can't find implicit for UUID". When that happens, you just have to work out what is missing by trial and error. Sorry!Also, the Scala compiler has some funny [order dependency rules](https://issues.scala-lang.org/browse/SI-7755) which will sometimes make it look like you're missing an implicit for an element. The best way to avoid this is:
1. define the protocol/formatters in sibling, or otherwise independent, non-cyclic, packages. In particular, if you define your domain objects in
foo.domainand your formats infoo.formats, note that you will not be able to access the formats from thefooparent package (this catches a lot of people out). Another approach is to use separate projects for the domain and formats, which avoids the problem entirely whilst allowing you to provide zero dependency packages of your domain objects to downstream consumers (I believe this to be good practice in a microservices world, as it effectively means exporting your schema).2. define all your custom rules in an
objectthat extendsFamilyFormatsso that the implicit resolution priority rules work in your favour (see tests for an example of this style). The derivedfamilyFormatwill win over implicit formats that have been inherited from a lower implicit scope, so you will often have to explicitly bring them back into the higher scope by listing each -- see FamilyFormats for an example usingSymbolFormatand a user-defined format. i.e. provide an explicitimplicit val symbolFormat = SymbolJsonFormat, similarly forJsObjectFormat. - trait JsonFormatHints extends AnyRef
- trait KeyResolver extends AnyRef
-
class
MapBasedKeyResolver
extends KeyResolver
Customizable key resolver, substitutes keys from provided map
Value Members
-
def
deserError[T](msg: String, cause: Throwable = null)(implicit arg0: Typeable[T]): Nothing
- Annotations
- @inline()
-
def
serError[T](msg: String)(implicit arg0: Typeable[T]): Nothing
- Annotations
- @inline()
-
def
unexpectedJson[T](got: JsValue)(implicit arg0: Typeable[T]): Nothing
- Annotations
- @inline()
-
object
AutoKeyResolver
extends KeyResolver
Automatic key resolver, returs symbol
as-is - object FamilyFormats extends DefaultJsonProtocol with FamilyFormats
- object JsonFormat
-
object
RecordFormats
Generic marshallers for extensible records
- object RootJsonFormat