Skip to content

Commit 8a81743

Browse files
fix field ordering Scala 3 writes macro (#1177)
(cherry picked from commit 68c2665) Co-authored-by: xuwei-k <6b656e6a69@gmail.com>
1 parent 487cdbd commit 8a81743

File tree

2 files changed

+40
-7
lines changed

2 files changed

+40
-7
lines changed

play-json/shared/src/main/scala-3/play/api/libs/json/JsMacroImpl.scala

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -669,11 +669,11 @@ object JsMacroImpl { // TODO: debug
669669
.toSeq
670670
.partition { case WritableField(_, _, t) => isOptionalType(t) }
671671

672-
type ElementAcc = MBuilder[(String, JsValue), Map[String, JsValue]]
672+
type ElementAcc = MBuilder[(String, JsValue), List[(String, JsValue)]]
673673

674674
def withIdents[U: Type](f: Expr[ElementAcc] => Expr[U]): Expr[U] =
675675
'{
676-
val ok = Map.newBuilder[String, JsValue]
676+
val ok = List.newBuilder[(String, JsValue)]
677677

678678
${ f('{ ok }) }
679679
}
@@ -685,7 +685,7 @@ object JsMacroImpl { // TODO: debug
685685
val fieldMap = withFields(tupled, tupleTpe, tprElements, debug)
686686

687687
withIdents[JsObject] { bufOk =>
688-
val values: Seq[Expr[Unit]] = required.map { case WritableField(param, i, pt) =>
688+
val values: Seq[(Expr[Unit], Int)] = required.map { case WritableField(param, i, pt) =>
689689
val pname = param.name
690690

691691
val withField = fieldMap.get(pname) match {
@@ -697,7 +697,7 @@ object JsMacroImpl { // TODO: debug
697697
)
698698
}
699699

700-
pt.asType match {
700+
val expr = pt.asType match {
701701
case pTpe @ '[p] =>
702702
val writes: Expr[Writes[p]] = resolve(pt) match {
703703
case Some((givenWrites, _)) =>
@@ -715,9 +715,10 @@ object JsMacroImpl { // TODO: debug
715715
}).asTerm
716716
}.asExprOf[Unit]
717717
}
718+
expr -> i
718719
} // end of required.map
719720

720-
val extra: Seq[Expr[Unit]] = optional.map {
721+
val extra: Seq[(Expr[Unit], Int)] = optional.map {
721722
case WritableField(param, i, optType @ OptionTypeParameter(pt)) =>
722723
val pname = param.name
723724

@@ -730,7 +731,7 @@ object JsMacroImpl { // TODO: debug
730731
)
731732
}
732733

733-
pt.asType match {
734+
val expr = pt.asType match {
734735
case pTpe @ '[p] =>
735736
val writes: Expr[Writes[p]] = resolve(pt) match {
736737
case Some((givenWrites, _)) =>
@@ -757,6 +758,7 @@ object JsMacroImpl { // TODO: debug
757758
}).asTerm
758759
}.asExprOf[Unit]
759760
}
761+
expr -> i
760762
} // end of extra.collect
761763

762764
if (values.isEmpty && extra.isEmpty) {
@@ -766,7 +768,7 @@ object JsMacroImpl { // TODO: debug
766768

767769
'{ JsObject(Map.empty) }
768770
} else {
769-
val fields = values ++ extra
771+
val fields = (values ++ extra).sortBy(_._2).map(_._1)
770772

771773
val resExpr = '{ JsObject(${ bufOk }.result()) }
772774

play-json/shared/src/test/scala/play/api/libs/json/MacroSpec.scala

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,10 +538,41 @@ class MacroSpec extends AnyWordSpec with Matchers with org.scalatestplus.scalach
538538
Json.toJson(GenericCaseClassWithDefault(3, "foo")).mustEqual(expectedJson)
539539
Json.fromJson(expectedJson).mustEqual(JsSuccess(expected))
540540
}
541+
542+
"field ordering" in {
543+
// https://github.com/playframework/play-json/issues/1038
544+
val instance: OWrites[FieldOrderTest] = Json.writes[FieldOrderTest]
545+
546+
val value = FieldOrderTest(1, 2, 3, Some(4), 5, 6)
547+
548+
instance
549+
.writes(value)
550+
.fields
551+
.mustEqual(
552+
Seq(
553+
"x1" -> 1,
554+
"x2" -> 2,
555+
"x3" -> 3,
556+
"x4" -> 4,
557+
"x5" -> 5,
558+
"x6" -> 6,
559+
).map { case (k, v) => k -> JsNumber(v) }
560+
)
561+
assert(instance.writes(value).value.isInstanceOf[ImmutableLinkedHashMap[?, ?]])
562+
}
541563
}
542564
}
543565

544566
object MacroSpec {
567+
case class FieldOrderTest(
568+
x1: Int,
569+
x2: Int,
570+
x3: Int,
571+
x4: Option[Int],
572+
x5: Int,
573+
x6: Int,
574+
)
575+
545576
sealed trait Family
546577
case class Simple(bar: String) extends Family
547578
case class Lorem[T](ipsum: T, age: Int) extends Family

0 commit comments

Comments
 (0)