Scalaz validation with Argonaut - json

I have a case class and companion object:
case class Person private(name: String, age: Int)
object Person {
def validAge(age: Int) = {
if (age > 18) age.successNel else "Age is under 18".failureNel
}
def validName(name: String) = {
name.successNel
}
def create(name: String, age: Int) = (validAge(age) |#| validName(name))(Person.apply)
}
I want to use Argonaut to parse some JSON and return a Person OR some errors, as a list. So I need to:
Read the JSON from a string, and validate the string is correctly formed
Decode the JSON into a Person, or List of error strings.
I want to return errors in the form of something I can turn into some more JSON like:
{
errors: ["Error1", "Error2"]
}
I first tried using Argonauts decodeValidation method, which returns a Validation[String, X]. Unfortunately, I need a List of errors.
Any suggestions?

I'm adding this as an answer because it's how I'd solve the problem off the top of my head, but I haven't been keeping up closely with Argonaut development for a while, and I'd love to hear that there's a better way. First for the setup, which fixes a few little issues in yours, and adds a condition for validity on names to make the examples later more interesting:
import scalaz._, Scalaz._
case class Person private(name: String, age: Int)
object Person {
def validAge(age: Int): ValidationNel[String, Int] =
if (age > 18) age.successNel else "Age is under 18".failureNel
def validName(name: String): ValidationNel[String, String] =
if (name.size >= 3) name.successNel else "Name too short".failureNel
def create(name: String, age: Int) =
(validName(name) |#| validAge(age))(Person.apply)
}
And then I'd decode the JSON into a (String, Int) pair before creating the Person:
import argonaut._, Argonaut._
def decodePerson(in: String): ValidationNel[String, Person] =
Parse.decodeValidation(in)(
jdecode2L((a: String, b: Int) => (a, b)
)("name", "age")).toValidationNel.flatMap {
case (name, age) => Person.create(name, age)
}
And then:
scala> println(decodePerson("""{ "name": "", "age": 1 }"""))
Failure(NonEmptyList(Name too short, Age is under 18))
Note that this doesn't accumulate errors in more complex cases—e.g. if the value of the name field is a number and age is 1, you'll only get a single error (the name one). Making error accumulation work in cases like that would be considerably more complex.
Relatedly, you'll also see a deprecation warning about the flatMap on Validation, which you can think of as a reminder that accumulation won't happen across the bind. You can tell the compiler that you understand by importing scalaz.Validation.FlatMap._.

Related

How to know which field is missed while parsing json in Json4s

I have made a generic method which parses json to case class and it also works fine. But if tries to parse big json which have one or two mandatory field then I am not able to figure out which particular mandatory f ield is missing. I am only able to handle it with IllegalArgumentException. Is there a way to handle to know which is field is missing while parsing Json by using json4s.
Here is my code ->
object JsonHelper {
implicit val formats: DefaultFormats = DefaultFormats
def write[T <: AnyRef](value: T): String = jWrite(value)
def parse(value: String): JValue = jParser(value)
}
And this is the method I am using to parse Json and handle failed case ->
def parseJson[M](json: String)(implicit m: Manifest[M]): Either[ErrorResponse, M] = {
try
Right(JsonHelper.parse(json).extract[M])
catch {
case NonFatal(th) =>
th.getCause.getCause match {
case e: java.lang.IllegalArgumentException =>
error(s"Invalid JSON - $json", e)
Left(handle(exception = EmptyFieldException(e.getMessage.split(":").last)))
case _ =>
error(s"Invalid JSON - $json", th)
Left(handle(exception = new IllegalArgumentException("Invalid Json", th)))
}
}
}
Like for a Json ->
{
"name": "Json"
}
And case class ->
case class(name: String, profession: String)
if I try to parse above json into case class currently I am getting Invalid JSON - IllegalArgumentException. But is there a way that the exception tells which is field is missing like in above example "profession" is missing.
Is there a way to handle to know which is field is missing while parsing Json by using json4s.
Maybe you have more complicated setting, but for example for
import org.json4s._
import org.json4s.jackson.JsonMethods._
val str = """{
| "name": "Json"
|}""".stripMargin
val json = parse(str) // JObject(List((name,JString(Json))))
implicit val formats: Formats = DefaultFormats
case class MyClass(name: String, profession: String)
json.extract[MyClass]
it produces
org.json4s.MappingException: No usable value for profession
Did not find value which can be converted into java.lang.String
at org.json4s.reflect.package$.fail(package.scala:56)
at ...
with the name of missing field and if the class is just case class MyClass(name: String) then this produces MyClass(Json).
If the class is case class MyClass(name: String, profession: Option[String]) then this produces MyClass(Json,None).
So normally IllegalArgumentException should be followed by Caused by: org.json4s.MappingException with the field name. I guess now you're swallowing json4s MappingException somewhere. Maybe in th.getCause.getCause match .... It's hard to say without MCVE.

PlayJSON in Scala

I am trying to familiarize myself with the PlayJSON library. I have a JSON formatted file like this:
{
"Person": [
{
"name": "Jonathon",
"age": 24,
"job": "Accountant"
}
]
}
However, I'm having difficulty with parsing it properly due to the file having different types (name is a String but age is an Int). I could technically make it so the age is a String and call .toInt on it later but for my purposes, it is by default an integer.
I know how to parse some of it:
import play.api.libs.json.{JsValue, Json}
val parsed: JsValue = Json.parse(jsonFile) //assuming jsonFile is that entire JSON String as shown above
val person: List[Map[String, String]] = (parsed \ "Person").as[List[Map[String, String]]]
Creating that person value throws an error. I know Scala is a strongly-typed language but I'm sure there is something I am missing here. I feel like this is an obvious fix too but I'm not quite sure.
The error produced is:
JsResultException(errors:List(((0)/age,List(JsonValidationError(List(error.expected.jsstring),WrappedArray())))
The error you are having, as explained in the error you are getting, is in casting to the map of string to string. The data you provided does not align with it, because the age is a string. If you want to keep in with this approach, you need to parse it into a type that will handle both strings and ints. For example:
(parsed \ "Person").validate[List[Map[String, Any]]]
Having said that, as #Luis wrote in a comment, you can just use case class to parse it. Lets declare 2 case classes:
case class JsonParsingExample(Person: Seq[Person])
case class Person(name: String, age: Int, job: String)
Now we will create a formatter for each of them on their corresponding companion object:
object Person {
implicit val format: OFormat[Person] = Json.format[Person]
}
object JsonParsingExample {
implicit val format: OFormat[JsonParsingExample] = Json.format[JsonParsingExample]
}
Now we can just do:
Json.parse(jsonFile).validate[JsonParsingExample]
Code run at Scastie.

Parse JSON object into list of objects

I'm trying to use circe to decode a JSON object into a list of objects. I only want to use some of the fields of the JSON response to create the object, so I feel like I have to create a custom decoder.
The class I want to make a sequence of is defined as follows:
case class Review(Id: String, ProductId: String, Rating: Int)
I tried creating a custom decoder like this:
implicit val reviewDecoder: Decoder[Review] = Decoder.instance { c =>
val resultsC = c.downField("Results")
for {
id <- resultsC.downArray.get[String]("Id")
productId <- resultsC.downArray.get[String]("ProductId")
rating <- resultsC.downArray.get[Int]("Rating")
} yield Review(id, productId, rating)
}
reviewDecoder.decodeJson(json) seems to result in only doing the first result and not all of them.
I have a JSON response like this:
{
"Limit":2,
"Offset":0,
"TotalResults":31,
"Locale":"en_US",
"Results":
[
{"Id":"14518388",
"CID":"21a9436b",
"ProductId":"Product11",
"AuthorId":"jcknwekjcnwjk",
"Rating":3
},
{"Id":"14518035",
"CID":"8d67b6f5",
"ProductId":"Product11",
"AuthorId":"fnkjwernfk",
"Rating":3
}
],
"Includes":{},
"HasErrors":false,
"Errors":[]}
I want to be able to parse this JSON object using circe to create a Seq[Review], but I'm stumped how.
****Edit** Luis' answer does answer this question but say I have a more complicated class I want to create a sequence of:
case class User(id: Int)
case class Review(user: User, ProductId: String, Rating: Int)
How would I be able to create a sequence of Reviews in this case?
I would just use the cursor to getting the Array and then use the generic decoder.
The following code was tested on ammonite, where json is a string containing your sample input.
import $ivy.`io.circe::circe-core:0.11.1`
import $ivy.`io.circe::circe-generic:0.11.1`
import $ivy.`io.circe::circe-parser:0.11.1`
import io.circe.{Decoder, Jsom}
import io.circe.parser.parse
final case class Review(Id: String, ProductId: String, Rating: Int)
implicit val reviewDecoder: Decoder[Review] = io.circe.generic.semiauto.deriveDecoder
parse(json).getOrElse(Json.Null).hcursor.downField("Results").as[List[Review]]
// res: io.circe.Decoder.Result[List[Review]] = Right(List(Review("14518388", "Product11", 3), Review("14518035", "Product11", 3)))

Simplify 2 similar json helper functions

I have two functions that are similar. One returns a String when successful and the second returns an Option[String] when successful. How can write this in a more elegant way, perhaps calling the first function in the second function? Thanks!
private def readString(j: JsValue, key: String): Validation[String, String] = {
j \ key match {
case j: JsString =>
Success(j.value)
case j: JsUndefined =>
Failure(s"Missing field $key")
case j: JsValue =>
Failure(s"Field $key value is not a string")
}
}
private def readStringOpt(j: JsValue, key: String): Validation[String, Option[String]] = {
(j \ key).as[JsValue] match {
case j: JsString =>
j.value.some.success
case j: JsUndefined =>
None.success
case x =>
Failure(s"Field $key value is not a string")
}
}
First, the .as[JsValue] in your second method isn't really necessary—all it does is look up the Reads instance for JsValue, which just passes through its argument and never fails.
If you want to go the route you suggest above (defining the Opt-less version in terms of the other), Scalaz provides some slightly more concise syntax:
def readString(j: JsValue, key: String): Validation[String, String] =
readStringOpt(j, key).flatMap(_.toSuccess(s"Missing field $key"))
This will give you a deprecation warning in recent versions of Scalaz, however, since Validation does not have a monad instance and its flatMap is kind of a lie. This means you have two options (apart from ignoring the deprecation warning): you can switch to \/, which is monadic, or you can use a fold instead:
def readString(j: JsValue, key: String): Validation[String, String] =
readStringOpt(j, key).fold(_.failure, _.toSuccess(s"Missing field $key"))
Which is a little more verbose, but it puts you on the right side of the validation-is-not-a-monad gods.

Deserializing Json array into Scala object

I have been having major problems trying to deserialize a JSON array to a Scala object
[{"name":"Cool","city":"College Park","address":"806","id":1},{"name":"Mars ","city":"Durham","address":"12","id":2},{"name":"Something","city":"Raleigh
","address":"","id":3},{"name":"test","city":"","address":"","id":5}]
I have tried gson, jerkson(jackson scala wrapper), sjson, flexjson. None of them have worked. What I have here is a List of Customers. List[Customer].
This is the closest I've got:
val array = new JsonParser().parse( customers ).getAsJsonArray()
This gave me an 4 arrays. It obviously didn't give me a customer object though. I tried Jerkson.
val array = parse[List[Customer]](customers)
But I get this.
GenericSignatureFormatError occured : null
I'm just trying to find a simple way like I would in Java.
Here is my Scala class.
case class Customer(
id : Pk[ Int ],
name : String,
address : Option[ String ],
city : Option[ String ],
state : Option[ String ],
user_id : Int )
object Customer extends Magic[ Customer ]( Option( "Customer" ) ) {
def apply( name : String, address : String, city : String, state : String, user_id : Int ) = {
new Customer( NotAssigned, name, Some( address ), Some( city ), Some( state ), user_id )
}
def delete( id : Int ) = {
SQL( "DELETE from Customer where id = {id}" ).onParams( id ).executeUpdate()
}
}
Thanks for any help.
With gson, you could write your own json reader:
case class Customer(id: Int, name: String, address: Option[String],
city: Option[String], state: Option[String], user_id: Int)
object CustomerJsonReader {
def read(in: Reader) = readCustomers(new JsonReader(in))
def readCustomers(reader: JsonReader) = {
val customers = new ListBuffer[Customer]
reader.beginArray()
while (reader.hasNext()) {
customers += readCustomer(reader)
}
reader.endArray()
customers toList
}
def readCustomer(reader: JsonReader): Customer = {
var id = 0
var customerName = ""
var address: Option[String] = None
var city: Option[String] = None
var state: Option[String] = None
var userId = 0
reader.beginObject()
while (reader.hasNext()) {
val name = reader.nextName()
name match {
case "id" => id = reader.nextInt()
case "address" => address = Some(reader.nextString())
case "state" => state = Some(reader.nextString())
case "user_id" => userId = reader.nextInt()
case "name" => customerName = reader.nextString()
case "city" => city = Some(reader.nextString())
case _ => reader.skipValue()
}
}
reader.endObject()
Customer(id, customerName, address, city, state, userId)
}
}
val json =
"""
[{"name":"Cool","city":"College Park","address":"806","id":1},
{"name":"Mars ","city":"Durham","address":"12","id":2},
{"name":"Something","city":"Raleigh ","address":"","id":3},
{"name":"test","city":"","address":"","id":5}]
"""
val customers: List[Customer] =
CustomerJsonReader.read(new StringReader(json))
I know that with gson, you would need Array instead of a scala.List. I would suggest giving that a shot. You should use that with gson.fromJson, I think.
You can also try Jerkson = Jackson + Scala
Quite easy to use even if I had problems with special JSON fields containing "-"
A small tuto I saw on twitter recently: http://logician.free.fr/index.php/2011/09/16/play-scala-and-json/
I've been driven insane by this now and went through trying GSON, Lift-Json, Sjson and finally Jerkson, and found peace with that one.
Here's how I use it in combination with Play:
http://logician.eu/index.php/2011/09/16/play-scala-and-json/
http://logician.eu/index.php/2011/11/01/writing-custom-deserializers-for-jerkson/
I use Lift's json library for this purpose, it easily lets you parse JSON and extract values into case classes. It's packaged as a separate jar so you don't need the whole lift framework to use it.
import net.liftweb.json._
import net.liftweb.json.JsonDSL._
implicit val formats = DefaultFormats
val json: String = "[{...."
val parsed: JValue = parse(json)
val customers: List[Customer] = parsed.extract[List[Customer]]
Just make sure any optional fields are defined in the case class using Option. I noticed in your code the objects are missing the user_id field, which would cause a parse error if the user_id field is declared as Int instead of Option[Int].
Aside from trying to make Jerkson (which is a great library to use from what I have heard), you could also try Jackson's Scala module -- modules are the official way Jackson is extended to deal with 3rd party datatypes as well as native datatypes and constructs of other JVM languages.
(this is not to say this is more official than Jerkson, just that there are many useful Jackson extension modules that many developers are not familiar with)
Issues with Scala module are discussed on main Jackson mailing lists (user#jackson.codehaus.org); you may have found an edge case that could be fixed.
I have written a parser/validator dsl, which allows you to explicitly resolve any type erasure issue. Out of the box it handles case classes, tuples, Option, Either, List, Map, joda DatetTime, piping to functions, multiple key mapping and key name remapping.
It uses the Jackson parser
https://github.com/HigherState/jameson
Its pretty simple to with scala and with play libraries and the code
import play.api.libs.json.{Format, Json}
case class Customer(name:String, city:String, address:String, id:Int)
object Customer {
implicit val jsonFormat: Format[Customer] = Json.format(Customer)
}
val jsonDef = Json.parse(<json-string>)
val customers = jsonDef.as[List[Customer]]
customers is list of Customer objects.