pattern match any into a list - json

I get the following as a result from using the scala json parse.
import scala.util.parsing.json.JSON._
val j: String = """["this",["a","b",["c","d"]]]"""
val parse_test=parseFull(j)
now from this I get a result of Option[Any]
I can use get to obtain the results (in this case I am not concerned about invalid json format, so this should be safe, right?)
parse_test.get
res26: Any = List(this, List(a, b, List(c, d)))
Now, how should I go about going from this Any to the List that I had expected? I assume I should use pattern matching, but I can't figure it out. Any help would be much appreciated

Here is my solution:
scala> val Some(xs # List(_*)) = parse_test
xs: List[Any] = List(this, List(a, b, List(c, d)))

What you could do is a fold with a pattern match and a cast:
test_result.fold[List[String]](Nil){
case _ :: list :: _ => list.asInstanceOf[List[String]]
case _ => Nil
}
Assuming you're trying to throw out of the first element and that the 2nd element is the list you wanted.
Edit:
Be aware that if the 2nd element isn't a list this cast would cause an exception. It's really horrible dealing with a List[Any] and trying to decode what's in there...

Related

Is there a elegant way to handle Either Monad in Scala?

I am starting up on Scala, doing a project with circe to handle JSON.
I am coming accross a lot of Either returns from functions, and I don't seem to find a elegant way to handle all of them.
For instance, for a single either, I do as in this snippet:
if (responseJson.isRight) {
//do something
} else {
//do something else
}
But what should I do when I have a lot of them in sequence, such as this example in which I just go straight for the right side and I feel I should be doing some extra validation:
ClassA(
someValue,
someValue,
someJson.hcursor.get[Double]("jsonKey1").right.get,
someJson.hcursor.get[Double]("jsonKey2").right.get,
someJson.hcursor.get[Double]("jsonKey3").right.get
)
How should/can I handle multiple Either objects (without ending up with a bunch of if-elses, or similar) when I want to get their contents if they are a Right, but not I am not sure they are always a Right ?
Lets say you have a case class,
case class Demo(i: Int, s: String)
and two eithers,
val intEither: Either[Throwable, Int] = ???
val stringEither: Either[Throwable, Int] = ???
So... lets start with the most basic and obvious one,
val demoEither: Either[Throwable, Demo] =
intEither.flatMap(i =>
stringEither.map(s => Demo(i, s))
)
Another way is to do the same as above is to use for-comprehensions,
val demoEither: Either[Throwable, Demo] =
for {
i <- intEither
s <- stringEither
} yield Demo(i, s)
But, monads are sequential, which means that if the first Either is a Left then you will not even look at the second Either and just get a Left. This is mostly undesirable for validations because you don't want to loose the validation information of all components, so what you actually want is an Applicative.
And Either is not an Applicative, you will have to use cats or scalaz or implement your own applicative for this.
cats provides the Validated applicative for this express purpose which lets you validate and keep all error information of the validated components.
import cats.data._
import cats.implicits._
val intValidated: ValidatedNec[Throwable, Int] =
intEither.toValidatedNec
val stringValidated: ValidatedNec[Throwable, String] =
stringEither.toValidatedNec
val demoValidated: ValidatedNec[Throwable, Demo] =
(intValidated, stringValidated).mapN(Demo)
val demoEither: Either[List[Throwable], Demo] =
demoValidated.leftMap(errorNec => errorNec.toList)
Or, if you are doing this just once and don't want to depend on cats, you can just use pattern-matching which is very versatile
val demoEither: Either[List[Throwable], Demo] =
(intEither, stringEither) match {
case (Right(i), Right(s)) => Right(Demo(i, s))
case (Left(ti), Left(ts)) => Left(List(ti, ts))
case (Left(ti), _) => Left(List(ti))
case (_, Left(ts)) => Left(List(ts))
}
How should/can I handle multiple Either objects (without ending up with a bunch of if-elses, or similar) when I want to get their contents if they are a Right, but not I am not sure they are always a Right?
So you have some Either instances, all with the same type signature.
val ea :Either[Throwable,String] = Right("good")
val eb :Either[Throwable,String] = Left(new Error("bad"))
val ec :Either[Throwable,String] = Right("enough")
And you want all the Right values, ignoring any Left values.
List(ea, eb, ec).collect{case Right(x) => x}
//res0: List[String] = List(good, enough)
You don't know which Either contains which String but I think that's what you asked for.

How to read value of property depending on an argument

How can I get the value of a property given a string argument.
I have a Object CsvProvider.Row which has attributes a,b,c.
I want to get the attribute value depending on property given as a string argument.
I tried something like this:
let getValue (tuple, name: string) =
snd tuple |> Seq.averageBy (fun (y: CsvProvider<"s.csv">.Row) -> y.```name```)
but it gives me the following error:
Unexpected reserved keyword in lambda expression. Expected incomplete
structured construct at or before this point or other token.
Simple invocation of function should look like this:
getValue(tuple, "a")
and it should be equivalent to the following function:
let getValue (tuple) =
snd tuple |> Seq.averageBy (fun (y: CsvProvider<"s.csv">.Row) -> y.a)
Is something like this is even possible?
Thanks for any help!
The CSV type provider is great if you are accessing data by column names statically, because you get nice auto-completion with type inference and checking.
However, for a dynamic access, it might be easier to use the underlying CsvFile (also a part of F# Data) directly, rather than using the type provider:
// Read the given file
let file = CsvFile.Load("c:/test.csv")
// Look at the parsed headers and find the index of column "A"
let aIdx = file.Headers.Value |> Seq.findIndex (fun k -> k = "A")
// Iterate over rows and print A values
for r in file.Rows do
printfn "%A" (r.Item(aIdx))
The only unfortunate thing is that the items are accessed by index, so you need to build some lookup table if you want to easily access them by their name.

How to use camelizeKeys with hyphens

I am parsing a json response from a web service which has some fields defined using hyphens. I want to convert these names to mixed case names in my scala case class. I thought to use the camelizeKeys stipulation but it doesn't seem to work. So for example say I have a json response like:
{"offset":0,"total":359,"per-page":20}
to be converted to:
case class Response(offset: Int, total: Int, perPage: Int)
and I do:
parse(str).camelizeKeys.extract[Response]
I get the error:
Ex: org.json4s.package$MappingException: No usable value for perPage
Did not find value which can be converted into int
A work around is to replace all occurrences of the hyphen with an underscore before parsing the text. So one could do the following:
parse(str.replaceAll("([a-z])-([a-z])", "$1_$2")).camelizeKeys.extract[Response]
But this solution is limited as with complicated data structures it may not work; you could end up corrupting the data returned if any field values contains such a pattern. So a more complete solution was to write my own replaceAll function:
def replaceAll(str: String, regex: String)(f: String => String) = {
val pattern = regex.r
val (s, i) = (("", 0) /: pattern.findAllMatchIn(str)) { case ((s, i), m) =>
(s + str.substring(i, m.start) + f(m.toString), m.end)
}
s + str.substring(i)
}
and apply it to my json string thus:
replaceAll(str, "\"[0-9a-z\\-]*\":")(_.replace('-', '_'))
You can replace the hyphens by underscores as below.
def replaceHyphens(value: JValue): JValue = {
value.transformField {
case JField(name, x) => JField(name.replaceAll("-", "_"), x)
}
}
This function is implemented by referring to rewriteJsonAST in json4s
replaceHyphens(parse(str)).camelizeKeys.extract[Response]

Left truncate Scala function - list from integer

I am totally new in Scala, could you help me with the simple function -
input parameter is an integer, the function should return a list of integers with the first entry is input integer , and the rest are gotten by omitting the left most digit one by one. For example,
if input is 0, it returns List(0), input =5678 returns List(5678,678,78,8).
def leftTrunc(input:Int):List[Int]
Thanks a lot in advance
5678.toString.tails.toList.init.map(_.toInt)
//> res0: List[Int] = List(5678, 678, 78, 8)
Convert the number to a String. Then tails does exactly what you want. Except it's an iterator, so convert it to a List, and also it has an empty string at the end, so use init to return all but the last element. But they're strings, so use map to convert them all to Int again
But I'm pretty sure your instructor is expecting you to do it numerically :)
Here's a numerical version, in this case deliberately uncommented so you can work out for yourself how it works
val n = 5678
val digits = n.toString.size
List.iterate(10, digits)(10*) map { n % _}
EDIT: As requested in the comment, the other way around just uses inits instead of tails (and a reverse to get the requested ordering)
5678.toString.inits.toList.init.reverse.map(_.toInt)
//> res0: List[Int] = List(5, 56, 567, 5678)
And the numerical one is easier this way around
List.iterate(n, digits)(_/10).reverse
It's a lot of fun if you share your attempt, and we all can give our attempts too. :) I hope you've got some and here's a couple of ideas form my end...
"5678".foldRight(Seq[String](""))((c, s) => s"$c${s.head}" +: s).dropRight(1)
"5678".foldRight(Seq[String]())((c, s) => s"$c${s.headOption.getOrElse("")}" +: s)

How to Conditionally Produce JSON Using JSON4S

I am using JSON4S to produce some JSON.
If a condition is met, I would like to produce the following:
{"fld1":"always", "fld2":"sometimes"}
If the condition is not met, I would like to produce:
{"fld1":"always"}
What I have tried so far is:
val fld1 = "fld1" -> "always"
val json = if(condition) ("fld2" -> "sometimes") ~ fld1 else fld1
compact(render(json))
However, this gives me a type mismatch in the render "Found: Product with Serializable. Required: org.json4s.package.JValue".
The interesting this is that render(("fld2" -> "sometimes") ~ fld1) works, and so does render(fld1). The problem seems to be with the type inferred for json.
How could I fix this?
While both of the current answers give suitable workarounds, neither explains what's going on here. The problem is that if we have two types, like this:
trait Foo
trait Bar
And an implicit conversion (or view) from one to the other:
implicit def foo2bar(foo: Foo): Bar = new Bar {}
A conditional with a Foo for its then clause and a Bar for its else clause will still be typed as the least upper bound of Foo and Bar (in this case Object, in your case Product with Serializable).
This is because the type inference system isn't going to step in and say, well, we could view that Foo as a Bar, so I'll just type the whole thing as a Bar.
This makes sense if you think about it—for example, if it were willing to handle conditionals like that, what would it do if we had implicit conversions both ways?
In your case, the then clause is typed as a JObject, and the else clause is a (String, String). We have a view from the latter to the former, but it won't be used here unless you indicate that you want the whole thing to end up as a JObject, either by explicitly declaring the type, or by using the expression in a context where it has to be a JObject.
Given all of that, the simplest workaround may just be to make sure that both clauses are appropriately typed from the beginning, by providing a type annotation like this for fld1:
val fld1: JObject = "fld1" -> "always"
Now your conditional will be appropriately typed as it is, without the type annotation.
Not the nicest way I can think of, but declaring the type yourself should work:
val json: JObject =
if(condition) ("fld2" -> "sometimes") ~ fld1 else fld1
compact(render(json))
Also, note that you can get type inference to help itself out: If you can render in one go:
compact(render(
if(condition) fld1 ~ ("fld2" -> "sometimes") else fld1
))
Another approach is to box conditional values to Options.
val json = fld1 ~ ("fld2" -> (if (condition) Some("sometimes") else None))
compact(render(json))