Skip to content

wix-incubator/peninsula

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Peninsula

Peninsula is a Scala lib providing a collection of useful tools working with json AST and facilitating json transformations without ever converting your jsons into domain objects.

Its main goal is to eventually make building data centric coast-to-coast applications an easier and a more intuitive process.

Technically Peninsula is an abstraction layer on top of Json4s.

Disclaimer: even if it's used on production at Wix - this is an early stage lib - with plenty of space for optimization and improvement. Contributions and comments are very welcome!

Installation

Add the following dependency to your pom if you use maven

<dependency>
    <groupId>com.wix</groupId>
    <artifactId>peninsula</artifactId>
    <version>0.1.5</version>
</dependency>

For SBT users

val peninsula = "com.wix" % "peninsula" % "0.1.5"
libraryDependencies += peninsula

Examples

All examples below can also be found in the following test: ExampleTest.scala

Extraction

Easily extract top level and nested values from json.

import com.wix.peninsula.Json

case class Person(id: Long, name: String)
case class Item(name: String, sale: Boolean)

val json = Json.parse(
  """
    {
      "id": 1,
      "name": "John",
      "location": {"city": "Vilnius", "country": "LT"},
      "customer": { "id": 1, "name": "John"},
      "items": [{"name": "tomatoes", "sale": true}, {"name": "snickers", "sale": false}]
    }
  """)
  
json.extractString("location.city")
result: "Vilnius"

json.extractStringOpt("location.city")
result: Some("Vilnius")

json.extractStringOpt("location.postCode")
result: None

json.extract[Person]("customer")
result: Person(id = 1, name = "John")

json.extract[Seq[Boolean]]("items.sale")
result: Seq(true, false)

json.extract[Seq[Item]]("items")
result: Seq(Item(name = "tomatoes", sale = true), Item(name = "snickers", sale = false))

json.extract[Item]("items[1]") 
result: Item(name = "snickers", sale = false)

You can also subselect a json by path and extract values from it

val location: Json = json("location")

location.extractString("city")
result: "Vilnius"

location.extractString("country")
result: "LT"

val items: Json = json("items")

items.extractString("[0].name")
result: "tomatoes"

items.extractString("[1].name")
result: "snickers"

items.extract[Seq[String]]("name")
result: Seq("tomatoes", "snickers")

Inspection

json("location.city").exists
result: true

json("location.postCode").exists
result: false

json("items[1].name").exists
result: true

json("items[1].name").contains("snickers")
result: true

json("id").isNull
result: false

json("mobile").isNull
result: true

Here you can find comprehensive list of extraction methods.

Basic Json Transformation

Build a transformation configuration to describe the rules that later can be used for transforming one json into another. TransformationConfig represents a collection of copy configurations that each copy one bit from the original json into the resulting json in a specific way.

In the example below copyFields("id", "slug") copies 2 fields id and slug as they are without making any changes to the property names of values. It expects the value to be a primitive type.

copyField("name" -> "title") copies the name field - but renames the property to title.

copy("images") copies the images field into the resulting json without making any assumptions about the value type. i.e. images value can be an object or an array.

import com.wix.peninsula.CopyConfigFactory._
import com.wix.peninsula._

val config = TransformationConfig()
  .add(copyFields("id", "slug"))
  .add(copyField("name" -> "title"))
  .add(copy("images"))

val json = Json.parse(
  """
  {  
     "id":1,
     "slug":"raw-metal",
     "name":"Raw Metal Gym",
     "images":{  
        "top":"//images/top.jpg",
        "background":"//images/background.png"
     }
  }
  """)

json.transform(config)
com.wix.peninsula.Json =
{
  "id": 1,
  "slug": "raw-metal",
  "title": "Raw Metal Gym",
  "images": {
    "top": "//images/top.jpg",
    "background": "//images/background.png"
  }
}

A More Advanced Json Transformation

In the below example the mergeObject copier merges anything in the object specified onto the top level of the resulting json.

Nested properties can be accessed using dot based selectors e.g. media.pictures.headerBackground.

Also note how json values can be validated and transformed before copying them over to the resulting json.

import com.wix.peninsula._
import com.wix.peninsula.CopyConfigFactory._
import com.wix.peninsula.JsonValidators.NonEmptyStringValidator
import org.json4s.JsonAST.JString

object HttpsAppender extends JsonMapper {
  override def map(json: Json): Json = Json(json.node match {
    case JString(url) => JString("https:" + url)
    case x => x
  })
}

val config = TransformationConfig()
  .add(copyField("id"))
  .add(mergeObject("texts"))
  .add(copyField("images.top" -> "media.pictures.headerBackground")
    .withValidators(NonEmptyStringValidator)
    .withMapper(HttpsAppender))

val json = Json.parse(
  """
  {  
     "id":1,
     "slug":"raw-metal",
     "name":"Raw Metal Gym",
     "texts": {
       "name": "Raw metal gym",
       "description": "The best gym in town. Come and visit us today!"
     },
     "images":{
        "top":"//images/top.jpg",
        "background":"//images/background.png"
     }
  }
  """)

json.transform(config)
result: com.wix.peninsula.Json =
{
  "id" : 1,
  "name" : "Raw metal gym",
  "description" : "The best gym in town. Come and visit us today!",
    "media" : {
      "pictures" : {
        "headerBackground" : "https://images/top.jpg"
      }
    }
}

Basic Json Translation

Translation differs from transformation in that it keeps the original json and merges the translation on top it. In a basic case, when the translation and the original json have the same structure - no transformation config is needed.

import com.wix.peninsula._

val json = Json.parse(
  """
  {
    "id":1,
    "slug":"raw-metal",
    "name":"Raw Metal Gym",
    "images":{
      "top":"//images/top.jpg",
      "background":"//images/background.png"
    }
  }
  """)

val config = Json.parse(
  """
  {
    "name":"Metalinis Gymas",
    "images":{
      "background":"//images/translated-background.png"
    }
  }
  """)

json.translate(config)

result: com.wix.peninsula.Json =
{
  "id": 1,
  "slug": "raw-metal",
  "name":"Metalinis Gymas",
  "images": {
    "top": "//images/top.jpg",
    "background":"//images/translated-background.png"
  }
}

Custom Json Translation

import com.wix.peninsula._
import com.wix.peninsula.CopyConfigFactory._

val json = Json.parse(
  """
    {
       "id":1,
       "slug":"raw-metal",
       "name":"Raw Metal Gym",
       "images":{
          "top":"//images/top.jpg",
          "background":"//images/background.png"
       },
       "features": [
          { "id": 1, "description": "Convenient location" },
          { "id": 2, "description": "Lots of space" }
       ]
    }
  """)

val translation = Json.parse(
  """
    {
       "title":"Metalinis Gymas",
       "media": {
          "backgroundImage":"//images/translated-background.png"
       },
       "features": [
        { "id": 2, "description": "space translated" },
        { "id": 1, "description": "location translated" }
       ]
    }
  """)

val featureConfig = TransformationConfig().add(copyField("description"))

val config = TransformationConfig()
  .add(copyField("title" -> "name"))
  .add(copyField("media.backgroundImage" -> "images.background"))
  .add(copyArrayOfObjects(fromTo = "features", config = featureConfig, idField = "id"))

json.translate(translation, config)
result: com.wix.peninsula.Json =
{
  "id": 1,
  "slug": "raw-metal",
  "name":"Metalinis Gymas",
  "images": {
    "top": "//images/top.jpg",
    "background":"//images/translated-background.png"
  },
  "features": [
    { "id": 1, "description": "location translated" },
    { "id": 2, "description": "space translated" }
  ]
}

Fields Filtering

Define which fields you want to be included into the resulting json and filter all the others out. This might get in handy for implementation of restful json endpoints.

import com.wix.peninsula.Json

val json = Json.parse("""{"id": 1, "name": "John", "office": "Wix Townhall", "role": "Engineer"}""")

json.only(Set("id", "role"))
result: com.wix.peninsula.Json =
{
  "id" : 1,
  "role" : "Engineer"
}

API reference

There are different methods, which allow you to extract values and structures from JSON.

Methods described below look for element in specified path and return element's value in case it exists, is not null and has appropriate type. In all other cases methods throw an exception:

  • JsonPathDoesntExistException if path doesn't exist
  • JsonElementIsNullException if element's value is null
  • UnexpectedJsonElementException if element's value has incorrect type
json.extract[T](path: String): T
json.extractBoolean(path: String): Boolean
json.extractInt(path: String): Int
json.extractBigInt(path: String): BigInt
json.extractLong(path: String): Long
json.extractDouble(path: String): Double
json.extractBigDecimal(path: String): BigDecimal
json.extractString(path: String): String

There are also extraction methods, which basically have the same functionality except returning Failure instead of throwing exceptions.

json.extractTry[T](path: String): Try[T]
json.extractBooleanTry(path: String): Try[Boolean]
json.extractIntTry(path: String): Try[Int]
json.extractBigIntTry(path: String): Try[BigInt]
json.extractLongTry(path: String): Try[Long]
json.extractDoubleTry(path: String): Try[Double]
json.extractBigDecimalTry(path: String): Try[BigDecimal]
json.extractStringTry(path: String): Try[String]

The second group of extraction methods called 'extractAs*' try to either extract value if it exists and has appropriate type or losslessly convert to requested type. If mission fails 'extractAs*' methods throw exception, 'extractAs*Try' methods contrariwise return Failure.

json.extractAsBoolean(path: String): Boolean
json.extractAsInt(path: String): Int
json.extractAsBigInt(path: String): BigInt
json.extractAsLong(path: String): Long
json.extractAsDouble(path: String): Double
json.extractAsBigDecimal(path: String): BigDecimal
json.extractAsString(path: String): String

json.extractAsBooleanTry(path: String): Try[Boolean]
json.extractAsIntTry(path: String): Try[Int]
json.extractAsBigIntTry(path: String): Try[BigInt]
json.extractAsLongTry(path: String): Try[Long]
json.extractAsDoubleTry(path: String): Try[Double]
json.extractAsBigDecimalTry(path: String): Try[BigDecimal]
json.extractAsStringTry(path: String): Try[String]

About

Json manipulation lib inspired by coast-to-coast design

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages