@@ -2,6 +2,7 @@ package zio.schema.codec
2
2
3
3
import java .nio .CharBuffer
4
4
import java .nio .charset .StandardCharsets
5
+ import java .util
5
6
import java .util .concurrent .ConcurrentHashMap
6
7
7
8
import scala .annotation .{ switch , tailrec }
@@ -19,6 +20,7 @@ import zio.json.{
19
20
JsonFieldEncoder
20
21
}
21
22
import zio .prelude .NonEmptyMap
23
+ import zio .schema .Schema .GenericRecord
22
24
import zio .schema ._
23
25
import zio .schema .annotation .{ rejectExtraFields , _ }
24
26
import zio .schema .codec .DecodeError .ReadError
@@ -338,7 +340,7 @@ object JsonCodec {
338
340
case Schema .Tuple2 (l, r, _) => ZJsonEncoder .tuple2(schemaEncoder(l, cfg, discriminatorTuple), schemaEncoder(r, cfg, discriminatorTuple))
339
341
case Schema .Optional (schema, _) => ZJsonEncoder .option(schemaEncoder(schema, cfg, discriminatorTuple))
340
342
case Schema .Fail (_, _) => unitEncoder.contramap(_ => ())
341
- case Schema .GenericRecord (_, structure , _) => recordEncoder(structure.toChunk , cfg)
343
+ case s @ Schema .GenericRecord (_, _ , _) => recordEncoder(s , cfg)
342
344
case Schema .Either (left, right, _) => ZJsonEncoder .either(schemaEncoder(left, cfg, discriminatorTuple), schemaEncoder(right, cfg, discriminatorTuple))
343
345
case Schema .Fallback (left, right, _, _) => fallbackEncoder(schemaEncoder(left, cfg, discriminatorTuple), schemaEncoder(right, cfg, discriminatorTuple))
344
346
case l @ Schema .Lazy (_) => ZJsonEncoder .suspend(schemaEncoder(l.schema, cfg, discriminatorTuple))
@@ -448,14 +450,12 @@ object JsonCodec {
448
450
var first = true
449
451
values.foreach {
450
452
case (key, value) =>
451
- if (first)
452
- first = false
453
+ if (first) first = false
453
454
else {
454
455
out.write(',' )
455
- if (indent.isDefined)
456
- ZJsonEncoder .pad(indent_, out)
456
+ if (indent.isDefined) pad(indent_, out)
457
457
}
458
- string.encoder.unsafeEncode(JsonFieldEncoder .string.unsafeEncodeField( key) , indent_, out)
458
+ string.encoder.unsafeEncode(key, indent_, out)
459
459
if (indent.isEmpty) out.write(':' )
460
460
else out.write(" : " )
461
461
directEncoder.unsafeEncode(value, indent_, out)
@@ -561,7 +561,7 @@ object JsonCodec {
561
561
pad(indent_, out)
562
562
563
563
if (discriminatorChunk.isEmpty && ! noDiscriminators) {
564
- string.encoder.unsafeEncode(JsonFieldEncoder .string.unsafeEncodeField( caseName) , indent_, out)
564
+ string.encoder.unsafeEncode(caseName, indent_, out)
565
565
if (indent.isEmpty) out.write(':' )
566
566
else out.write(" : " )
567
567
}
@@ -598,36 +598,44 @@ object JsonCodec {
598
598
}
599
599
}
600
600
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 ) =>
603
607
{
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)
609
613
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)
626
632
}
627
- pad(indent, out)
628
- out.write('}' )
633
+ i += 1
629
634
}
635
+ if (doPrettyPrint) pad(indent, out)
636
+ out.write('}' )
630
637
}
638
+ }
631
639
}
632
640
}
633
641
@@ -722,7 +730,7 @@ object JsonCodec {
722
730
case Schema .NonEmptyMap (ks, vs, _) => mapDecoder(ks, vs).mapOrFail(m => NonEmptyMap .fromMapOption(m).toRight(" NonEmptyMap expected" ))
723
731
case Schema .Set (s, _) => ZJsonDecoder .chunk(schemaDecoder(s, - 1 )).map(entries => entries.toSet)
724
732
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 )
726
734
case Schema .Either (left, right, _) => ZJsonDecoder .either(schemaDecoder(left, - 1 ), schemaDecoder(right, - 1 ))
727
735
case s @ Schema .Fallback (_, _, _, _) => fallbackDecoder(s)
728
736
case l @ Schema .Lazy (_) => ZJsonDecoder .suspend(schemaDecoder(l.schema, discriminator))
@@ -977,47 +985,46 @@ object JsonCodec {
977
985
private def deAliasCaseName (alias : String , caseNameAliases : Map [String , String ]): String =
978
986
caseNameAliases.getOrElse(alias, alias)
979
987
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 ) => {
986
1002
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)
1018
1020
}
1021
+ continue = Lexer .nextField(trace, in)
1019
1022
}
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()
1021
1028
}
1022
1029
}
1023
1030
@@ -1116,16 +1123,20 @@ object JsonCodec {
1116
1123
val caseTpeNames = discriminatorTuple.map(_._2).toArray
1117
1124
(a : Z , indent : Option [Int ], out : Write ) => {
1118
1125
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
+ }
1121
1132
val strEnc = string.encoder
1122
1133
var first = true
1123
1134
var i = 0
1124
1135
while (i < tags.length) {
1125
1136
first = false
1126
1137
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(':' )
1129
1140
strEnc.unsafeEncode(caseTpeNames(i), indent_, out)
1130
1141
i += 1
1131
1142
}
@@ -1139,15 +1150,15 @@ object JsonCodec {
1139
1150
if (first) first = false
1140
1151
else {
1141
1152
out.write(',' )
1142
- if (indent.isDefined ) pad(indent_, out)
1153
+ if (doPrettyPrint ) pad(indent_, out)
1143
1154
}
1144
1155
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(':' )
1147
1158
enc.unsafeEncode(value, indent_, out)
1148
1159
}
1149
1160
}
1150
- pad(indent, out)
1161
+ if (doPrettyPrint) pad(indent, out)
1151
1162
out.write('}' )
1152
1163
}
1153
1164
}
@@ -1156,31 +1167,27 @@ object JsonCodec {
1156
1167
// scalafmt: { maxColumn = 400, optIn.configStyleArguments = false }
1157
1168
private [codec] object ProductDecoder {
1158
1169
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)
1180
1188
}
1189
+ schema.defaultConstruct()
1181
1190
}
1182
-
1183
- schema.defaultConstruct()
1184
1191
}
1185
1192
1186
1193
private [codec] def caseClass1Decoder [A , Z ](discriminator : Int , schema : Schema .CaseClass1 [A , Z ]): ZJsonDecoder [Z ] = {
@@ -1571,13 +1578,15 @@ object JsonCodec {
1571
1578
if ((field.optional || field.transient) && field.defaultValue.isDefined) {
1572
1579
buffer(idx) = field.defaultValue.get
1573
1580
} 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 _ =>
1577
1585
}
1578
1586
buffer(idx) = schema match {
1587
+ case _ : Schema .Optional [_] => None
1579
1588
case collection : Schema .Collection [_, _] => collection.empty
1580
- case _ => schemaDecoder(schema).unsafeDecodeMissing( spans(idx) :: trace)
1589
+ case _ => error( spans(idx) :: trace, " missing " )
1581
1590
}
1582
1591
}
1583
1592
}
@@ -1592,36 +1601,33 @@ object JsonCodec {
1592
1601
1593
1602
private object CaseClassJsonDecoder {
1594
1603
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
1597
1606
val fields = new Array [Schema .Field [Z , _]](len)
1598
1607
val decoders = new Array [ZJsonDecoder [_]](len)
1599
1608
val spans = new Array [JsonError .ObjectAccess ](len)
1600
1609
val names = new Array [String ](len)
1601
- var aliases = Map .empty[ String , Int ]
1610
+ val aliases = Array .newBuilder[( String , Int ) ]
1602
1611
var i = 0
1603
- caseClassSchema .fields.foreach { field =>
1612
+ schema .fields.foreach { field =>
1604
1613
val name = field.name.asInstanceOf [String ]
1605
1614
fields(i) = field
1606
1615
names(i) = name
1607
1616
spans(i) = JsonError .ObjectAccess (name)
1608
1617
decoders(i) = schemaDecoder(field.schema)
1609
1618
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 _ =>
1615
1621
}
1616
1622
i += 1
1617
1623
}
1618
1624
new CaseClassJsonDecoder (
1619
1625
fields,
1620
1626
decoders,
1621
1627
spans,
1622
- new StringMatrix (names, aliases.toArray ),
1628
+ new StringMatrix (names, aliases.result() ),
1623
1629
discriminator,
1624
- caseClassSchema .annotations.collectFirst({ case _ : rejectExtraFields => () }).isDefined
1630
+ schema .annotations.exists(_. isInstanceOf [ rejectExtraFields])
1625
1631
)
1626
1632
}
1627
1633
}
0 commit comments