diff --git a/docs/manual/working/scalaGuide/main/json/code-2/Scala2JsonAutomatedSpec.scala b/docs/manual/working/scalaGuide/main/json/code-2/Scala2JsonAutomatedSpec.scala index 329389122..822863a44 100644 --- a/docs/manual/working/scalaGuide/main/json/code-2/Scala2JsonAutomatedSpec.scala +++ b/docs/manual/working/scalaGuide/main/json/code-2/Scala2JsonAutomatedSpec.scala @@ -4,7 +4,6 @@ package scalaguide.json -import play.api.libs.json.Json import org.specs2.mutable.Specification //#valueClass diff --git a/play-json/shared/src/main/scala-2.13+/play/api/libs/json/JsObjectBuilder.scala b/play-json/shared/src/main/scala-2.13+/play/api/libs/json/JsObjectBuilder.scala new file mode 100644 index 000000000..22a9236c1 --- /dev/null +++ b/play-json/shared/src/main/scala-2.13+/play/api/libs/json/JsObjectBuilder.scala @@ -0,0 +1,39 @@ +/* + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ + +package play.api.libs.json + +import scala.collection.mutable.{ Builder => MBuilder } + +private[json] final class JsObjectBuilder + extends MBuilder[(String, Json.JsValueWrapper), JsObject] { + + private val fs = Map.newBuilder[String, JsValue] + + def addOne(elem: (String, Json.JsValueWrapper)): this.type = { + val (name, wrapped) = elem + + fs += (name -> Json.unwrap(wrapped)) + + this + } + + override def addAll(xs: IterableOnce[(String, Json.JsValueWrapper)]): this.type = { + xs.iterator.foreach(addOne) + + this + } + + override def knownSize: Int = fs.knownSize + + override def sizeHint(size: Int): Unit = { + fs.sizeHint(size) + } + + def clear(): Unit = { + fs.clear() + } + + def result(): JsObject = JsObject(fs.result()) +} diff --git a/play-json/shared/src/main/scala-2.13-/play/api/libs/json/JsObjectBuilder.scala b/play-json/shared/src/main/scala-2.13-/play/api/libs/json/JsObjectBuilder.scala new file mode 100644 index 000000000..fe330fefa --- /dev/null +++ b/play-json/shared/src/main/scala-2.13-/play/api/libs/json/JsObjectBuilder.scala @@ -0,0 +1,35 @@ +/* + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ + +package play.api.libs.json + +import scala.collection.mutable.{ Builder => MBuilder } + +private[json] final class JsObjectBuilder + extends MBuilder[(String, Json.JsValueWrapper), JsObject] { + + private val fs = Map.newBuilder[String, JsValue] + + def +=(elem: (String, Json.JsValueWrapper)): this.type = { + val (name, wrapped) = elem + + fs += (name -> Json.unwrap(wrapped)) + + this + } + + override def ++=(xs: TraversableOnce[(String, Json.JsValueWrapper)]): this.type = { + xs.foreach(`+=`) + + this + } + + def knownSize: Int = fs.result().size + + def clear(): Unit = { + fs.clear() + } + + def result(): JsObject = JsObject(fs.result()) +} diff --git a/play-json/shared/src/main/scala/play/api/libs/json/Json.scala b/play-json/shared/src/main/scala/play/api/libs/json/Json.scala index 59dac64a9..a6425ee85 100644 --- a/play-json/shared/src/main/scala/play/api/libs/json/Json.scala +++ b/play-json/shared/src/main/scala/play/api/libs/json/Json.scala @@ -4,6 +4,8 @@ package play.api.libs.json +import scala.collection.mutable.{ Builder => MBuilder } + import java.io.InputStream /** @@ -226,12 +228,52 @@ object Json extends JsonFacade with JsMacros with JsValueMacros { implicit def toJsFieldJsValueWrapper[T](field: T)(implicit w: Writes[T]): JsValueWrapper = JsValueWrapperImpl(w.writes(field)) - def obj(fields: (String, JsValueWrapper)*): JsObject = JsObject(fields.map(f => (f._1, unwrap(f._2)))) + def obj(fields: (String, JsValueWrapper)*): JsObject = JsObject(fields.map { case (name, wrapped) => + name -> unwrap(wrapped) + }) + + /** + * Returns a JSON object builder. + * + * {{{ + * import play.api.libs.json.{ Json, JsObject } + * + * // Create a new builder + * val builder: JsObjectBuilder = JsObject.newBuilder + * + * // Add key-value pairs to the builder + * builder += ("name" -> "John Doe") + * builder += ("age" -> 25) + * + * // Clear the builder + * builder.clear() + * + * // Add more key-value pairs + * builder += ("email" -> "john.doe@example.com") + * builder += ("address" -> "123 Street") + * + * // Build the final JsObject + * val result: JsObject = builder.result() + * + * // Print the resulting JsObject + * println(result) + * }}} + * + * This will output: + * {{{ + * {"email":"john.doe@example.com","address":"123 Street"} + * }}} + */ + def newBuilder: MBuilder[(String, Json.JsValueWrapper), JsObject] = + new JsObjectBuilder() def arr(items: JsValueWrapper*): JsArray = JsArray(items.iterator.map(unwrap).toArray[JsValue]) - // Passed nulls will typecheck without needing the implicit conversion, so they need to checked at runtime - private def unwrap(wrapper: JsValueWrapper) = wrapper match { + /* + * Passed nulls will typecheck without needing the implicit conversion, + * so they need to checked at runtime. + */ + private[json] def unwrap(wrapper: JsValueWrapper) = wrapper match { case null => JsNull case JsValueWrapperImpl(value) => value } diff --git a/play-json/shared/src/test/scala/play/api/libs/json/JsonSharedSpec.scala b/play-json/shared/src/test/scala/play/api/libs/json/JsonSharedSpec.scala index 355f697a8..bc6e080a6 100644 --- a/play-json/shared/src/test/scala/play/api/libs/json/JsonSharedSpec.scala +++ b/play-json/shared/src/test/scala/play/api/libs/json/JsonSharedSpec.scala @@ -135,6 +135,22 @@ class JsonSharedSpec extends AnyWordSpec with Matchers with org.scalatestplus.sc "js.toJsObject(peach)(writes)".mustNot(typeCheck) } + "create object using builder" in { + val builder = Json.newBuilder + + builder += ("name" -> "John Doe") + builder += ("age" -> 25) + + val result: JsObject = builder.result() + + result.mustEqual( + Json.obj( + "name" -> "John Doe", + "age" -> 25 + ) + ) + } + "convert to a byte array containing the UTF-8 representation" in json { js => val json = js.parse(""" |{