It may be simple question, but I am new to Scala and not able to find the proper solution
I am trying to create a JSON object from the Option values. Will check if the value is not empty then create the Json obj, if the value is None I don't want to create the json object. With out else, default else is Unit which will fail to create Json obj
Json.obj(if(position.nonEmpty) ("position" -> position.get),
if(place.nonEmpty) ("place" -> place.get),
if(country.nonEmpty) ("country" -> country.get))
Need to put the If condition so that the final json string to look like
{
"position": "M2",
"place": "place",
"country": "country"
}
val obj = for {
p <- position
o <- otherOption
...
} yield Json.obj(
"position" -> p,
"other" -> o)
Will only yield a Some of Json Object if all options are defined. Otherwise None
Option is a monad and there are few convenient ways for using it.
First, if you want to extract value you should use map or flatMap and getOrElse methods:
val res = position.map(value => Json.obj("position" -> value)).getOrElse(null)
Another way is to keep Option of another type and use it latter:
val jsonOption = position.map(value => Json.obj("position" -> value))
After you can use it in for comprehension with another options or perform another mutations without extracting:
for (positionJson <- jsonOption; xJson <- xJsonOption) yield positionJson.toString + xJson.toString
jsonOption.map(_.toString).foreach(print(_))
And always try to avoid pattern matching on monads.
Related
I have a value in a JsObject which I want to assign to a specific key in Map, and I want to ask if there is a better way to extract that value without using a case matcher.
I have access to a request variable which is a case class that has a parameter myData
myData is an Option[JsValue] which contains multiple fields and I want to return the boolean value of one specific field in there called “myField” in a string format. The below works, but I want to find a more succinct way of getting the value of "myField" without case matching.
val newMap =
Map(
“myNewKey” -> request.myData.map(_ match {
case JsObject(fields) => fields.getOrElse(“myField”, "unknown").toString
case _ => “unknown”})
The output would then be
"myField": "true"
or
"myField": "false"
or if it isn't true or false, i.e the field doesn't exist
"myField": "unknown"
Rather:
myMap ++ (request.myData \ "myField").validateOpt[String].asOpt.
toSeq.collect { // to map entries
case Some(str) => "keyInMap" -> str
}
Do not use .toString to convert a JSON value or pretty print it.
I'm writing some code to auto-gen JSON codecs for Elm data-structures. There is a point my code, where a "sub-structure/sub-type", has already been encoded to a Json.Encode.Value, and I need to add another key-value pair to it. Is there any way to "destructure" a Json.Encode.Value in Elm? Or combine two values of type Json.Encode.Value?
Here's some sample code:
type alias Entity record =
{ entityKey: (Key record)
, entityVal: record
}
jsonEncEntity : (record -> Value) -> Entity record -> Value
jsonEncEntity localEncoder_record val =
let
encodedRecord = localEncoder_record val.entityVal
in
-- NOTE: the following line won't compile, but this is essentially
-- what I'm looking for
Json.combine encodedRecord (Json.Encode.object [ ( "id", jsonEncKey val.entityKey ) ] )
You can decode the value into a list of key value pairs using D.keyValuePairs D.value and then append the new field. Here's how you'd do that:
module Main exposing (..)
import Json.Decode as D
import Json.Encode as E exposing (Value)
addKeyValue : String -> Value -> Value -> Value
addKeyValue key value input =
case D.decodeValue (D.keyValuePairs D.value) input of
Ok ok ->
E.object <| ( key, value ) :: ok
Err _ ->
input
> import Main
> import Json.Encode as E
> value = E.object [("a", E.int 1)]
{ a = 1 } : Json.Encode.Value
> value2 = Main.addKeyValue "b" E.null value
{ b = null, a = 1 } : Json.Encode.Value
If the input is not an object, this will return the input unchanged:
> Main.addKeyValue "b" E.null (E.int 1)
1 : Json.Encode.Value
If you want to do this, you need to use a decoder to unwrap the values by one level into a Dict String Value, then combine the dictionaries, and finally re-encode as a JSON value. You can unwrap like so:
unwrapObject : Value -> Result String (Dict String Value)
unwrapObject value =
Json.Decode.decodeValue (Json.Decode.dict Json.Decode.value) value
Notice that you have to work with Results from this point on because there's the possibility, as far as Elm is concerned, that your JSON value wasn't really an object (maybe it was a number or a string instead, for instance) and you have to handle that case. For that reason, it's not really best practice to do too much with JSON Values directly; if you can, keep things as Dicts or some other more informative type until the end of processing and then convert the whole result into a Value as the last step.
Let's suppose I have a simple JSON array like this:
[
{
"name": "Alex",
"age": 12
},
{
"name": "Peter"
}
]
Notice that the second object doesn't have an age field.
I'm using JSON4S to query JSON (using the for-comprehension style to extract values):
for {
JArray(persons) <- json
JObject(person) <- persons
JField("name", JString(name)) <- person
JField("age", JString(age)) <- person
} yield new Person(name, age)
The problem for me is that this expression will skip the second object (the one with the missing age field). I don't want to skip such objects; I need to get it as null or better as None.
This answer gives an example of how to deal with null values in JSON using custom extractors, but it works only if the field is present and if its value is null.
Deconstructing objects in json4s may lead to some inconvenience, as you no longer can use fancy \ and \\ queries.
I prefer to do something like that:
for {
JArray(persons) <- json
person#JObject(_) <- persons
JString(name) <- person \ "name"
age = (person \ "age").extractOpt[Int]
} yield (name, age)
res7: List[(String, Option[Int])] = List(("Alex", Some(12)), ("Peter", None))
This example also illustrates two alternatives how object fields can be extracted (you can also use name = (person \ "name").extract[String] instead).
I have code that parses json:
(Aeson.Object jsonObj) -> case (HashMap.lookup "one" jsonObj, HashMap.lookup "two" jsonObj, , HashMap.lookup "three" jsonObj) of
(Just one, Just two, Just three) -> -- what's next?
_ -> error "All three keys don't exist
"
How do I retrieve the actual values from "one", "two" and "three"? All the three are Data.Aeson.Value and its data type is defined as following but yet I can't figure out what to do next:
data Value Source
A JSON value represented as a Haskell value.
Constructors
Object !Object
Array !Array
String !Text
Number !Number
Bool !Bool
Null
You can check if the values are of the expected type right in the pattern matching like this:
case HashMap.lookup "one" jsonObj of
(Just (Aeson.String t)) -> -- do with string t what pleases you
_ -> error "key not present or not a string"
You could adapt this to the triple in your code or leave it as a separate function, whatever you see fit. But be aware that this style would really mean that you're not using the Parser monad Aeson offers, which is really nice to dissect arbitrary JSON. Assume in your example above, what you really want to achieve is something like this:
parseTriple :: ByteString -> Maybe (Text, Integer, Bool)
then, using parseEither, this can be expressed as simple as
parseTriple bs = decode bs >>= parseMaybe $ \o -> do
one <- o .: "one"
two <- o .: "two"
three <- o .: "three"
return (one, two, three)
I think this nicely expresses what you're doing with the JSON and is easy to follow and maintain. Also, if you don't use parseMaybe but parseEither, you even get kind-of-useful error messages for free.
I have some code that builds a JSON object in Scala in the Playframework context
def toJson(): JsObject = Json.obj(
"status" -> JsString(result.getOrElse("fail")),
"age" -> JsNumber(age.getOrElse(0))
)
Where result and age are wrapped in an Option. The getOrElse part in the age line indicates age is not available. That's what I would like to get around.
The resulting output is:
{
status: "fail",
age: 0
}
Question A: In the example, age is None so getOrElse returns a 0 which would have to be interpreted by the clients as some magic number with special meaning. I would like to return something like None but the play.api.libs.json.JsNumber expects a scala.BigDecimal.
Is there a way to get around this somehow?
Question B: A solution to Question A would be to leave out the age in case it is not available so the result looks like:
{
status: "fail"
}
I cannot mess around within the Json.obj(a, b, ...) construct...
So how would the code look like to achieve something like this?
See if something like this works for you:
val fields:Seq[Option[(String,JsValueWrapper)]] = Seq(
result.map(s => ("status", JsString(s))),
age.map(i => ("age", JsNumber(new BigDecimal(i))))
)
val finalFields = fields.flatten
Json.obj(finalFields:_*)
When the Seq gets flattened, the None types in it should be removed and thus will not be part of the resulting JsObject.