Commons JSON is a lightweight Scala 3 library developed by Greenfossil for JSON processing and is inspired by Play JSON.
Add commons-json
as a dependency in your project:
- sbt
libraryDependencies += "com.greenfossil" %% "commons-json" % -version-
- Gradle
implementation group: 'com.greenfossil', name: 'commons-json_3', version: -version-
- Maven
<dependency> <groupId>com.greenfossil</groupId> <artifactId>commons-json_3</artifactId> <version>-version-</version> </dependency>
Note that this library requires at least Java 17 or later to build.
The entire JSON AST revolves in one type com.greenfossil.commons.json.JsValue
, and has the following subtypes:
JsNull
: JSON null valueJsString
: JSON stringJsNumber
: JSON number represented as ascala.math.BigDecimal
JsBoolean
: JSON booleanJsArray
: JSON array, consisting of aSeq[JsValue]
JsObject
: JSON object, consisting of aimmutable.ListMap[String, JsValue]
JsTemporal
: JSON value that is converted into Java Time Temporal types, e.g.java.util.Date
,java.sql.Date
,java.sql.Time
,java.sql.Timestamp
,java.time.LocalDateTime
,java.time.LocalDate
,java.time.LocalTime
,java.time.Instant
,java.time.OffsetDateTime
,java.time.OffsetTime
,java.time.ZonedDateTime
JsUndefined
: Non-existing JSON value, mainly used as result during path search
import com.greenfossil.commons.json.Json
// Parse JSON
val json = Json.parse(
"""{
| "firstname": "Homer",
| "lastname": "Simpson",
| "address": {
| "country": "Singapore",
| "street": "Orchard Road"
| },
| "children": ["Maggie", "Bart", "Lisa"]
|}""".stripMargin
)
Json.stringify(json)
or
json.toString
Result:
{"firstname":"Homer","lastname":"Simpson","address":{"country":"Singapore","street":"Orchard Road"},"children":["Maggie","Bart","Lisa"]}
Json.prettyPrint(json)
Result:
{
"firstname" : "Homer",
"lastname" : "Simpson",
"address" : {
"country" : "Singapore",
"street" : "Orchard Road"
},
"children" : [ "Maggie", "Bart", "Lisa" ]
}
There are several ways to use write a JSON text using com.greenfossil.commons.json.Json
.
val json = Json.obj(
"firstname" -> "Homer",
"lastname" -> "Simpson",
"birthday" -> java.time.LocalDate.now.minusYears(50),
"gender" -> Some("Male"),
"nationality" -> null
)
Result:
{"firstname":"Homer","lastname":"Simpson","birthday":"1972-12-12","gender":"Male","nationality":null}
Note that any value that is "None" will not be serialized.
Json.arr("Apple", "Banana", "Grapes")
Result:
["Apple","Banana","Grapes"]
val relationshipMap = Map("mother" -> "Marge", "father" -> "Homer")
Json.toJson(relationshipMap)
Result:
{"mother":"Marge","father":"Homer"}
Given a JSON value below:
val json = Json.parse(
"""{
| "firstname" : "Homer",
| "lastname" : "Simpson",
| "birthday" : "1972-12-12",
| "gender" : "Male",
| "nationality" : null
|}""".stripMargin)
There are multiple ways to read data from JSON object, for example:
To read a field in a specific type
(json \ "firstname").as[String]
// val res0: String = Homer
(json \ "birthday").as[java.time.LocalDate]
// val res1: java.time.LocalDate = 1972-12-12
(json \ "birthday").as[java.time.LocalDateTime]
// val res2: java.time.LocalDateTime = 1972-12-12T00:00
(json \ "nationality").as[String]
// val res3: String = null
Note that using the .as[T]
method will return a com.greenfossil.commons.json.JsonException
if the field doesn't exist.
To read a field in a specific type but may not exist
(json \ "nationality").asOpt[String]
// val res4: Option[String] = Some(null)
(json \ "race").asOpt[String]
// val res5: Option[String] = None
To read a field in a specific type but exclude null
values
(json \ "nationality").asNonNullOpt[String]
// val res6: Option[String] = None
Commons JSON allows you to query a field in a JSON object using an absolute or relative JSON path.
Given the JSON object below
val json = Json.parse(
"""{
| "name" : "Homer",
| "age" : 55,
| "spouse" : {
| "name" : "Marge",
| "age" : 50
| },
| "children" : ["Bart", "Maggie", "Lisa"]
|}""".stripMargin)
To query using an absolute path
(json \ "spouse" \ "name").as[String]
// val res0: String = Marge
(json \ "children" \ 0).as[String]
// val res1: String = Bart
To query using a relative path
(json \\ "name").as[Seq[String]]
// val res2: Seq[String] = List(Homer, Marge)
Given the below JSON object
val json = Json.parse(
"""{
| "date" : "2022-12-12",
| "time" : "17:32:49",
| "dateTime" : "2022-12-12T17:32:49",
| "instant" : "2022-12-12T09:32:49",
| "offsetDateTime" : "2022-12-12T17:32:49+08:00",
| "offsetTime" : "17:32:49+08:00",
| "zonedDateTime" : "2022-12-12T17:32:49+08:00[Asia/Singapore]"
|}""".stripMargin)
To read the fields and convert into Temporal types
(json \ "date").as[java.sql.Date]
// val res0: java.sql.Date = 2022-12-12
(json \ "date").as[java.time.LocalDate]
// val res1: java.time.LocalDate = 2022-12-12
(json \ "time").as[java.sql.Time]
// val res2: java.sql.Time = 17:32:49
(json \ "time").as[java.time.LocalTime]
// val res3: java.time.LocalTime = 17:32:49
(json \ "dateTime").as[java.util.Date]
// val res4: java.util.Date = Mon Dec 12 17:32:49 SGT 2022
(json \ "dateTime").as[java.sql.Timestamp]
// val res5: java.sql.Timestamp = 2022-12-12 17:32:49.0
(json \ "dateTime").as[java.time.LocalDateTime]
// val res6: java.time.LocalDateTime = 2022-12-12T17:32:49
(json \ "instant").as[java.time.Instant]
// val res7: java.time.Instant = 2022-12-12T01:32:49Z
(json \ "offsetDateTime").as[java.time.OffsetDateTime]
// val res8: java.time.OffsetDateTime = 2022-12-12T17:32:49+08:00
(json \ "offsetTime").as[java.time.OffsetTime]
// val res9: java.time.OffsetTime = 17:32:49+08:00
(json \ "zonedDateTime").as[java.time.ZonedDateTime]
// val res10: java.time.ZonedDateTime = 2022-12-12T17:32:49+08:00[Asia/Singapore]
(json \ "zonedDateTime").as[java.time.Instant]
// val res11: java.time.Instant = 2022-12-12T09:32:49Z
commons-json is licensed under the Apache license version 2. See LICENSE.