extracting keys from json string using json4s - json

can someone tell me how to extract keys from json using json4s.
My use case:
json stored as string in scala variable:
{
"key1" : "val1",
"key2" : ["12", "32"],
"key3" : {"keyN" : "valN"}
}
I'd like to transform this into a following Map[String, String]:
(key1 -> "val1", key2 -> "[\"12\",\"32\"]", key3 -> "{\"keyN\":\"valN\"}"
is there a simple way to achieve this with json4s?
Thanks in advance

val result: Map[String, String] = parse( """ {
| "key1" : "val1",
| "key2" : ["12", "32"],
| "key3" : {"keyN" : "valN"}
| }""".stripMargin).mapField(k => {
val v: String = k._2 match {
case s: JString => k._2.extract[String]
case _ => write(k._2)
}
(k._1, JString(v))
}).extract[Map[String, String]]
println(result)
You can use mapField map the JValue toString
if the value's type is String just extract as String
if the value's type is others, use the json4s to parse it to as JSON string
finally extract the JValue as Map[String, String].

implicit val formats = DefaultFormats
val a = parse(""" { "numbers" : [1, 2, 3, 4] } """)
println(a.extract[Map[String, Any]].keySet)

Related

Select a particular value from a JSON array

I have a JSON array containing the following details, I would like to extract the text Alignment value of Right and assign it to a val.
"data":[
{
"formatType": "text",
"value": "bgyufcie huis huids hufhsduhfsl hd"
},
{
"formatType": "text size",
"value": 12
},
{
"formatType": "text alignment",
"value" : "right"
}
]
Any thoughts?
Using the Gson library, you can map the json in a Java object.
So, you have to create a class like this:
public class MyObject{
private String formatType;
private String value;
//Constuctors, Getter and Setter...
//.....
//.....
}
After, using the method fromJson you can create an array of MyObject.
Gson gson = new Gson();
MyObject[] array = gson.fromJson(new FileReader("file.json"), MyObject[].class);
You can also use json4s library as shown next:
import org.json4s._
import org.json4s.jackson.JsonMethods._
val json = """{
"data":[
{
"formatType": "text",
"value": "bgyufcie huis huids hufhsduhfsl hd"
},
{
"formatType": "text size",
"value": 12
},
{
"formatType": "text alignment",
"value" : "right"
}
]
}"""
val parsed = parse(json)
val value = (parsed \ "data" \\ classOf[JObject]).filter(m => m("formatType") == "text alignment")(0)("value")
// value: Any = right
The filter (parsed \ "data" \\ classOf[JObject]) extracts all the items into a List of Map i.e:
List(
Map(formatType -> text, value -> bgyufcie huis huids hufhsduhfsl hd),
Map(formatType -> text size, value -> 12), Map(formatType -> text alignment, value -> right)
).
From those we apply the filter filter(m => m("formatType") == "text alignment") to retrieve the record that we really need.
Use Dijon FTW!
Here is a test that demonstrates how easily the "right" value can be found in samples like yours:
import com.github.pathikrit.dijon._
val json = parse(
"""{
|"data":[
| {
| "formatType": "text",
| "value": "bgyufcie huis huids hufhsduhfsl hd"
| },
| {
| "formatType": "text size",
| "value": 12
| },
| {
| "formatType": "text alignment",
| "value" : "right"
| }
|]
|}""".stripMargin)
assert(json.data.toSeq.collect {
case obj if obj.formatType == "text alignment" => obj.value
}.head == "right")
I would use the Jackson library, it is very helpful for parsing JSON. You can read the JSON using an ObjectMapper.
Here is a full tutorial to get you started: https://www.mkyong.com/java/jackson-how-to-parse-json/
create a multiline JSON string, then parse that string directly into a Scala object, use the net.liftweb package to solve this.
import net.liftweb.json._
object SarahEmailPluginConfigTest {
implicit val formats = DefaultFormats
case class Mailserver(url: String, username: String, password: String)
val json = parse(
"""
{
"url": "imap.yahoo.com",
"username": "myusername",
"password": "mypassword"
}
"""
)
def main(args: Array[String]) {
val m = json.extract[Mailserver]
println(m.url)
println(m.username)
println(m.password)
}
}
https://alvinalexander.com/scala/simple-scala-lift-json-example-lift-framework
Reference link

Replacing all JSON String values recursively with Circe

Using the CIRCE library & Cats, it would be incredibly useful to be able to transform all the string values of an arbitrary Json object such as
{
"topLevelStr" : "topLevelVal",
"topLevelInt" : 123,
"nested" : { "nestedStr" : "nestedVal" },
"array" : [
{ "insideArrayStr" : "insideArrayVal1", "insideArrayInt" : 123},
{ "insideArrayStr" : "insideArrayVal2", "insideArrayInt" : 123}
]
}
Is it possble to transform all string values (topLevelVal, nestedVal, insideArrayVal1, insideArrayVal2) to upper case (or any arbitrary string transformation for that matter)?
You can write recursive function by yourself. It should be something like that:
import io.circe.{Json, JsonObject}
import io.circe.parser._
def transform(js: Json, f: String => String): Json = js
.mapString(f)
.mapArray(_.map(transform(_, f)))
.mapObject(obj => {
val updatedObj = obj.toMap.map {
case (k, v) => f(k) -> transform(v, f)
}
JsonObject.apply(updatedObj.toSeq: _*)
})
val jsonString =
"""
|{
|"topLevelStr" : "topLevelVal",
|"topLevelInt" : 123,
| "nested" : { "nestedStr" : "nestedVal" },
| "array" : [
| {
| "insideArrayStr" : "insideArrayVal1",
| "insideArrayInt" : 123
| }
| ]
|}
""".stripMargin
val json: Json = parse(jsonString).right.get
println(transform(json, s => s.toUpperCase))

how to append a new item to an existing JSON variable?

Given the following json:
{
"status": "ok",
"user": {
"id": 39216,
"first_name": "naghmeh",
"username": "test",
}
}
I want to append new item to user JSON variable. and create a new json like:
{
"status": "ok",
"user": {
"id": 39216,
"first_name": "naghmeh",
"username": "test",
"point":10
}
}
You can achieve this by using Play Json API.
First you need to parse your json string to Play JsValue,
import play.api.libs.json._
val jsonString =
"""
|{
| "status": "ok",
| "user": {
| "id": 39216,
| "first_name": "naghmeh",
| "username": "test"
| }
|}
""".stripMargin
val jsValue = Json.parse(jsonString)
Now, you can add the values to your JsValue either by traversing the Json and then modifying it.
val result = jsValue match {
case jsObject: JsObject => (jsObject \ "user").as[JsObject] match {
case userJsObject: JsObject => jsObject ++ Json.obj(
"user" -> (userJsObject ++ Json.obj(
"point" -> 10
))
)
case _ => jsValue
}
case _ => jsValue
}
Or, by using Play Json's JsonTransformer API,
val jsonTransformer = (__ \ "user").json.update(
__.read[JsObject].map(jsObject => jsObject ++ Json.obj("point" -> 10))
)
val result2 = jsValue.transform(jsonTransformer) match {
case JsSuccess(jsObject, _) => jsObject
case _ => jsValue
}
Assuming from your question tags that you're using the PlayJSON library, you can simply use +:
val json: JsObject = ... // Your initial value here
val newField: JsValue = ... // Your new value here
val jsonWithAdditionalField = json + ("yourKey" -> newField)
For more information about how to create the JsValue, take a look a the official documentation
Append the list to a variable and then call put like so:
list.put("test");

How to unmarshall JSON object with multiple value types

Say I have an object:
{
"name": "joe",
"age": 60
}
How to unmarshall it into a Map[String, String] type?
The JsObject class has a fields parameter that is a Map[String, JsValue]. If you want a Map[String, String], use mapValues:
import spray.json._
import DefaultJsonProtocol._
val json =
"""{
"name": "joe",
"age": 60
}"""
val jsObj = json.parseJson.asJsObject // JsObject
val myMap: Map[String, String] = jsObj.fields.mapValues(_.toString)
println(myMap)
// Map(name -> "joe", age -> 60)

How to ignore an item when generating the json string if the value is None?

I'm trying to use Argonaut to generate JSON string from a Scala instance.
import argonaut._, Argonaut._
case class Person(name: Option[String], age: Int, things: List[String])
implicit def PersonCodecJson =
casecodec3(Person.apply, Person.unapply)("name", "age", "things")
val person = Person(Some("Freewind"), 2, List("club"))
val json: Json = person.asJson
val prettyprinted: String = json.spaces2
It will generate:
{
"name" : "Freewind",
"age" : 2,
"things" : [
"club"
]
}
And when the name is None:
val person = Person(None, 2, List("club"))
It will generate:
{
"name" : null,
"age" : 2,
"things" : [
"club"
]
}
But actually I want it to be:
{
"age" : 2,
"things" : [
"club"
]
}
How to do it?
Resolved, the key is to define custom EncodeJson rule and use ->?: and field.map:
implicit def PersonCodecJson: EncodeJson[Person] = EncodeJson((p: Person) =>
p.name.map("name" := _) ->?: ("age" := p.age) ->: ("things" := p.things) ->: jEmptyObject)