@@ -2,6 +2,7 @@ package zio.schema.codec
22
33import java .nio .CharBuffer
44import java .nio .charset .StandardCharsets
5+ import java .util
56import java .util .concurrent .ConcurrentHashMap
67
78import scala .annotation .{ switch , tailrec }
@@ -19,6 +20,7 @@ import zio.json.{
1920 JsonFieldEncoder
2021}
2122import zio .prelude .NonEmptyMap
23+ import zio .schema .Schema .GenericRecord
2224import zio .schema ._
2325import zio .schema .annotation .{ rejectExtraFields , _ }
2426import zio .schema .codec .DecodeError .ReadError
@@ -338,7 +340,7 @@ object JsonCodec {
338340 case Schema .Tuple2 (l, r, _) => ZJsonEncoder .tuple2(schemaEncoder(l, cfg, discriminatorTuple), schemaEncoder(r, cfg, discriminatorTuple))
339341 case Schema .Optional (schema, _) => ZJsonEncoder .option(schemaEncoder(schema, cfg, discriminatorTuple))
340342 case Schema .Fail (_, _) => unitEncoder.contramap(_ => ())
341- case Schema .GenericRecord (_, structure , _) => recordEncoder(structure.toChunk , cfg)
343+ case s @ Schema .GenericRecord (_, _ , _) => recordEncoder(s , cfg)
342344 case Schema .Either (left, right, _) => ZJsonEncoder .either(schemaEncoder(left, cfg, discriminatorTuple), schemaEncoder(right, cfg, discriminatorTuple))
343345 case Schema .Fallback (left, right, _, _) => fallbackEncoder(schemaEncoder(left, cfg, discriminatorTuple), schemaEncoder(right, cfg, discriminatorTuple))
344346 case l @ Schema .Lazy (_) => ZJsonEncoder .suspend(schemaEncoder(l.schema, cfg, discriminatorTuple))
@@ -448,14 +450,12 @@ object JsonCodec {
448450 var first = true
449451 values.foreach {
450452 case (key, value) =>
451- if (first)
452- first = false
453+ if (first) first = false
453454 else {
454455 out.write(',' )
455- if (indent.isDefined)
456- ZJsonEncoder .pad(indent_, out)
456+ if (indent.isDefined) pad(indent_, out)
457457 }
458- string.encoder.unsafeEncode(JsonFieldEncoder .string.unsafeEncodeField( key) , indent_, out)
458+ string.encoder.unsafeEncode(key, indent_, out)
459459 if (indent.isEmpty) out.write(':' )
460460 else out.write(" : " )
461461 directEncoder.unsafeEncode(value, indent_, out)
@@ -561,7 +561,7 @@ object JsonCodec {
561561 pad(indent_, out)
562562
563563 if (discriminatorChunk.isEmpty && ! noDiscriminators) {
564- string.encoder.unsafeEncode(JsonFieldEncoder .string.unsafeEncodeField( caseName) , indent_, out)
564+ string.encoder.unsafeEncode(caseName, indent_, out)
565565 if (indent.isEmpty) out.write(':' )
566566 else out.write(" : " )
567567 }
@@ -598,36 +598,44 @@ object JsonCodec {
598598 }
599599 }
600600
601- private def recordEncoder [Z ](structure : Seq [Schema .Field [Z , _]], cfg : Config ): ZJsonEncoder [ListMap [String , _]] = {
602- (value : ListMap [String , _], indent : Option [Int ], out : Write ) =>
601+ private def recordEncoder (schema : Schema .GenericRecord , cfg : Config ): ZJsonEncoder [ListMap [String , _]] = {
602+ val nonTransientFields = schema.nonTransientFields.toArray
603+ val encoders = nonTransientFields.map(field => schemaEncoder(field.schema.asInstanceOf [Schema [Any ]], cfg))
604+ if (nonTransientFields.isEmpty) { (_ : ListMap [String , _], _ : Option [Int ], out : Write ) =>
605+ out.write(" {}" )
606+ } else { (value : ListMap [String , _], indent : Option [Int ], out : Write ) =>
603607 {
604- if (structure.isEmpty) {
605- out.write( " {} " )
606- } else {
607- out.write( '{' )
608- val indent_ = bump(indent)
608+ out.write( '{' )
609+ val doPrettyPrint = indent ne None
610+ var indent_ = indent
611+ if (doPrettyPrint) {
612+ indent_ = bump(indent)
609613 pad(indent_, out)
610- var first = true
611- structure.foreach {
612- case field if field.transient || isEmptyOptionalValue(field, value(field.fieldName), cfg) => ()
613- case f @ Schema .Field (_, a, _, _, _, _) =>
614- val enc = schemaEncoder(a.asInstanceOf [Schema [Any ]], cfg)
615- if (first)
616- first = false
617- else {
618- out.write(',' )
619- if (indent.isDefined)
620- ZJsonEncoder .pad(indent_, out)
621- }
622- string.encoder.unsafeEncode(JsonFieldEncoder .string.unsafeEncodeField(f.fieldName), indent_, out)
623- if (indent.isEmpty) out.write(':' )
624- else out.write(" : " )
625- enc.unsafeEncode(value(f.fieldName), indent_, out)
614+ }
615+ val strEnc = string.encoder
616+ var first = true
617+ var i = 0
618+ while (i < nonTransientFields.length) {
619+ val field = nonTransientFields(i)
620+ val fieldName = field.fieldName
621+ val fieldValue = value(fieldName)
622+ if (! isEmptyOptionalValue(field, fieldValue, cfg)) {
623+ if (first) first = false
624+ else {
625+ out.write(',' )
626+ if (doPrettyPrint) pad(indent_, out)
627+ }
628+ strEnc.unsafeEncode(fieldName, indent_, out)
629+ if (doPrettyPrint) out.write(" : " )
630+ else out.write(':' )
631+ encoders(i).unsafeEncode(fieldValue, indent_, out)
626632 }
627- pad(indent, out)
628- out.write('}' )
633+ i += 1
629634 }
635+ if (doPrettyPrint) pad(indent, out)
636+ out.write('}' )
630637 }
638+ }
631639 }
632640 }
633641
@@ -722,7 +730,7 @@ object JsonCodec {
722730 case Schema .NonEmptyMap (ks, vs, _) => mapDecoder(ks, vs).mapOrFail(m => NonEmptyMap .fromMapOption(m).toRight(" NonEmptyMap expected" ))
723731 case Schema .Set (s, _) => ZJsonDecoder .chunk(schemaDecoder(s, - 1 )).map(entries => entries.toSet)
724732 case Schema .Fail (message, _) => failDecoder(message)
725- case Schema .GenericRecord (_, structure , _) => recordDecoder(structure.toChunk, schema.annotations.contains(rejectExtraFields()) )
733+ case s @ Schema .GenericRecord (_, _ , _) => recordDecoder(s )
726734 case Schema .Either (left, right, _) => ZJsonDecoder .either(schemaDecoder(left, - 1 ), schemaDecoder(right, - 1 ))
727735 case s @ Schema .Fallback (_, _, _, _) => fallbackDecoder(s)
728736 case l @ Schema .Lazy (_) => ZJsonDecoder .suspend(schemaDecoder(l.schema, discriminator))
@@ -977,47 +985,46 @@ object JsonCodec {
977985 private def deAliasCaseName (alias : String , caseNameAliases : Map [String , String ]): String =
978986 caseNameAliases.getOrElse(alias, alias)
979987
980- private def recordDecoder [Z ](
981- structure : Seq [Schema .Field [Z , _]],
982- rejectAdditionalFields : Boolean
983- ): ZJsonDecoder [ListMap [String , Any ]] = { (trace : List [JsonError ], in : RetractReader ) =>
984- {
985- val builder : ChunkBuilder [(String , Any )] = zio.ChunkBuilder .make[(String , Any )](structure.size)
988+ private def recordDecoder (schema : GenericRecord ): ZJsonDecoder [ListMap [String , Any ]] = {
989+ val capacity = schema.fields.size * 2
990+ val spansWithDecoders =
991+ new util.HashMap [String , (JsonError .ObjectAccess , ZJsonDecoder [Any ])](capacity)
992+ val defaults = new util.HashMap [String , Any ](capacity)
993+ schema.fields.foreach { field =>
994+ val fieldName = field.fieldName
995+ val spanWithDecoder =
996+ (JsonError .ObjectAccess (fieldName), schemaDecoder(field.schema).asInstanceOf [ZJsonDecoder [Any ]])
997+ field.nameAndAliases.foreach(x => spansWithDecoders.put(x, spanWithDecoder))
998+ if (field.optional && field.defaultValue.isDefined) defaults.put(fieldName, field.defaultValue.get)
999+ }
1000+ val rejectAdditionalFields = schema.annotations.exists(_.isInstanceOf [rejectExtraFields])
1001+ (trace : List [JsonError ], in : RetractReader ) => {
9861002 Lexer .char(trace, in, '{' )
987- if (Lexer .firstField(trace, in)) {
988- while ({
989- val field = Lexer .string(trace, in).toString
990- structure.find(f => f.nameAndAliases.contains(field)) match {
991- case Some (s @ Schema .Field (_, schema, _, _, _, _)) =>
992- val fieldName = s.fieldName
993- val trace_ = JsonError .ObjectAccess (fieldName) :: trace
994- Lexer .char(trace_, in, ':' )
995- val value = schemaDecoder(schema).unsafeDecode(trace_, in)
996- builder += ((JsonFieldDecoder .string.unsafeDecodeField(trace_, fieldName), value))
997- case None if rejectAdditionalFields =>
998- throw UnsafeJson (JsonError .Message (s " unexpected field: $field" ) :: trace)
999- case None =>
1000- Lexer .char(trace, in, ':' )
1001- Lexer .skipValue(trace, in)
1002-
1003- }
1004- Lexer .nextField(trace, in)
1005- }) {
1006- ()
1007- }
1008- }
1009- val tuples = builder.result()
1010- val collectedFields : Set [String ] = tuples.map { case (fieldName, _) => fieldName }.toSet
1011- val resultBuilder = ListMap .newBuilder[String , Any ]
1012-
1013- // add fields with default values if they are not present in the JSON
1014- structure.foreach { field =>
1015- if (! collectedFields.contains(field.fieldName) && field.optional && field.defaultValue.isDefined) {
1016- val value = field.fieldName -> field.defaultValue.get
1017- resultBuilder += value
1003+ val map = defaults.clone().asInstanceOf [util.HashMap [String , Any ]]
1004+ var continue = Lexer .firstField(trace, in)
1005+ while (continue) {
1006+ val fieldNameOrAlias = Lexer .string(trace, in).toString
1007+ val spanWithDecoder = spansWithDecoders.get(fieldNameOrAlias)
1008+ if (spanWithDecoder ne null ) {
1009+ val span = spanWithDecoder._1
1010+ val dec = spanWithDecoder._2
1011+ val trace_ = span :: trace
1012+ Lexer .char(trace_, in, ':' )
1013+ val fieldName = span.field
1014+ map.put(fieldName, dec.unsafeDecode(trace_, in))
1015+ } else if (rejectAdditionalFields) {
1016+ throw UnsafeJson (JsonError .Message (s " unexpected field: $fieldNameOrAlias" ) :: trace)
1017+ } else {
1018+ Lexer .char(trace, in, ':' )
1019+ Lexer .skipValue(trace, in)
10181020 }
1021+ continue = Lexer .nextField(trace, in)
10191022 }
1020- (resultBuilder ++= tuples).result()
1023+ (ListMap .newBuilder[String , Any ] ++= ({ // to avoid O(n) insert operations
1024+ import scala .collection .JavaConverters .mapAsScalaMapConverter // use deprecated class for Scala 2.12 compatibility
1025+
1026+ map.asScala
1027+ }: @ scala.annotation.nowarn)).result()
10211028 }
10221029 }
10231030
@@ -1116,16 +1123,20 @@ object JsonCodec {
11161123 val caseTpeNames = discriminatorTuple.map(_._2).toArray
11171124 (a : Z , indent : Option [Int ], out : Write ) => {
11181125 out.write('{' )
1119- val indent_ = bump(indent)
1120- pad(indent_, out)
1126+ val doPrettyPrint = indent ne None
1127+ var indent_ = indent
1128+ if (doPrettyPrint) {
1129+ indent_ = bump(indent)
1130+ pad(indent_, out)
1131+ }
11211132 val strEnc = string.encoder
11221133 var first = true
11231134 var i = 0
11241135 while (i < tags.length) {
11251136 first = false
11261137 strEnc.unsafeEncode(tags(i), indent_, out)
1127- if (indent.isEmpty ) out.write(':' )
1128- else out.write(" : " )
1138+ if (doPrettyPrint ) out.write(" : " )
1139+ else out.write(':' )
11291140 strEnc.unsafeEncode(caseTpeNames(i), indent_, out)
11301141 i += 1
11311142 }
@@ -1139,15 +1150,15 @@ object JsonCodec {
11391150 if (first) first = false
11401151 else {
11411152 out.write(',' )
1142- if (indent.isDefined ) pad(indent_, out)
1153+ if (doPrettyPrint ) pad(indent_, out)
11431154 }
11441155 strEnc.unsafeEncode(schema.name, indent_, out)
1145- if (indent.isEmpty ) out.write(':' )
1146- else out.write(" : " )
1156+ if (doPrettyPrint ) out.write(" : " )
1157+ else out.write(':' )
11471158 enc.unsafeEncode(value, indent_, out)
11481159 }
11491160 }
1150- pad(indent, out)
1161+ if (doPrettyPrint) pad(indent, out)
11511162 out.write('}' )
11521163 }
11531164 }
@@ -1156,31 +1167,27 @@ object JsonCodec {
11561167 // scalafmt: { maxColumn = 400, optIn.configStyleArguments = false }
11571168 private [codec] object ProductDecoder {
11581169
1159- private [codec] def caseClass0Decoder [Z ](discriminator : Int , schema : Schema .CaseClass0 [Z ]): ZJsonDecoder [Z ] = { (trace : List [JsonError ], in : RetractReader ) =>
1160- def skipField (): Unit = {
1161- val rejectExtraFields = schema.annotations.collectFirst({ case _ : rejectExtraFields => () }).isDefined
1162- if (rejectExtraFields) {
1163- throw UnsafeJson (JsonError .Message (" extra field" ) :: trace)
1164- }
1165- Lexer .char(trace, in, '"' )
1166- Lexer .skipString(trace, in)
1167- Lexer .char(trace, in, ':' )
1168- Lexer .skipValue(trace, in)
1169- }
1170-
1171- if (discriminator == - 2 ) {
1172- while (Lexer .nextField(trace, in)) { skipField() }
1173- } else {
1174- if (discriminator == - 1 ) {
1175- Lexer .char(trace, in, '{' )
1176- }
1177- if (Lexer .firstField(trace, in)) {
1178- skipField()
1179- while (Lexer .nextField(trace, in)) { skipField() }
1170+ private [codec] def caseClass0Decoder [Z ](discriminator : Int , schema : Schema .CaseClass0 [Z ]): ZJsonDecoder [Z ] = {
1171+ val rejectExtraFields = schema.annotations.exists(_.isInstanceOf [rejectExtraFields])
1172+ (trace : List [JsonError ], in : RetractReader ) => {
1173+ var continue =
1174+ if (discriminator == - 2 ) Lexer .nextField(trace, in)
1175+ else {
1176+ if (discriminator == - 1 ) Lexer .char(trace, in, '{' )
1177+ Lexer .firstField(trace, in)
1178+ }
1179+ while (continue) {
1180+ if (rejectExtraFields) {
1181+ throw UnsafeJson (JsonError .Message (" extra field" ) :: trace)
1182+ }
1183+ Lexer .char(trace, in, '"' )
1184+ Lexer .skipString(trace, in)
1185+ Lexer .char(trace, in, ':' )
1186+ Lexer .skipValue(trace, in)
1187+ continue = Lexer .nextField(trace, in)
11801188 }
1189+ schema.defaultConstruct()
11811190 }
1182-
1183- schema.defaultConstruct()
11841191 }
11851192
11861193 private [codec] def caseClass1Decoder [A , Z ](discriminator : Int , schema : Schema .CaseClass1 [A , Z ]): ZJsonDecoder [Z ] = {
@@ -1571,13 +1578,15 @@ object JsonCodec {
15711578 if ((field.optional || field.transient) && field.defaultValue.isDefined) {
15721579 buffer(idx) = field.defaultValue.get
15731580 } else {
1574- val schema = field.schema match {
1575- case l @ Schema .Lazy (_) => l.schema
1576- case s => s
1581+ var schema = field.schema
1582+ schema match {
1583+ case l : Schema .Lazy [_] => schema = l.schema
1584+ case _ =>
15771585 }
15781586 buffer(idx) = schema match {
1587+ case _ : Schema .Optional [_] => None
15791588 case collection : Schema .Collection [_, _] => collection.empty
1580- case _ => schemaDecoder(schema).unsafeDecodeMissing( spans(idx) :: trace)
1589+ case _ => error( spans(idx) :: trace, " missing " )
15811590 }
15821591 }
15831592 }
@@ -1592,36 +1601,33 @@ object JsonCodec {
15921601
15931602 private object CaseClassJsonDecoder {
15941603
1595- def apply [Z ](caseClassSchema : Schema .Record [Z ], discriminator : Int ): CaseClassJsonDecoder [Z ] = {
1596- val len = caseClassSchema .fields.length
1604+ def apply [Z ](schema : Schema .Record [Z ], discriminator : Int ): CaseClassJsonDecoder [Z ] = {
1605+ val len = schema .fields.length
15971606 val fields = new Array [Schema .Field [Z , _]](len)
15981607 val decoders = new Array [ZJsonDecoder [_]](len)
15991608 val spans = new Array [JsonError .ObjectAccess ](len)
16001609 val names = new Array [String ](len)
1601- var aliases = Map .empty[ String , Int ]
1610+ val aliases = Array .newBuilder[( String , Int ) ]
16021611 var i = 0
1603- caseClassSchema .fields.foreach { field =>
1612+ schema .fields.foreach { field =>
16041613 val name = field.name.asInstanceOf [String ]
16051614 fields(i) = field
16061615 names(i) = name
16071616 spans(i) = JsonError .ObjectAccess (name)
16081617 decoders(i) = schemaDecoder(field.schema)
16091618 field.annotations.foreach {
1610- case annotation : fieldNameAliases =>
1611- annotation.aliases.foreach { alias =>
1612- aliases = aliases.updated(alias, i)
1613- }
1614- case _ =>
1619+ case annotation : fieldNameAliases => annotation.aliases.foreach(a => aliases += ((a, i)))
1620+ case _ =>
16151621 }
16161622 i += 1
16171623 }
16181624 new CaseClassJsonDecoder (
16191625 fields,
16201626 decoders,
16211627 spans,
1622- new StringMatrix (names, aliases.toArray ),
1628+ new StringMatrix (names, aliases.result() ),
16231629 discriminator,
1624- caseClassSchema .annotations.collectFirst({ case _ : rejectExtraFields => () }).isDefined
1630+ schema .annotations.exists(_. isInstanceOf [ rejectExtraFields])
16251631 )
16261632 }
16271633 }
0 commit comments