Replacing all JSON String values recursively with Circe - json

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))

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

How to read part of a nested json object in as a string in Scala

I want to read in a json object into a scala class that keeps part of the json object as a string without trying to parse it.
This is what the json looks like:
[
{
"contractType": "NullContract",
"contractDefinition": {
"column": "age",
"conditions": [
{
"conditionType": "equalsCondition",
"conditionDefinition": {
"column": "species",
"value": "person"
}
}
]
}
}
]
I am using the jackson library. This is my mapper:
val mapper = new ObjectMapper()
.registerModule(DefaultScalaModule)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.setSerializationInclusion(Include.NON_ABSENT)
This is my class:
case class ContractJson(contractType: String, contractDefinition: String)
, which is how I want my resulting object to look.
This is the code parsing it:
val contractJson: Array[ContractJson] = mapper.readValue(contractsJsonString, classOf[Array[ContractJson]])
Error message I'm getting: Can not deserialize instance of java.lang.String out of START_OBJECT token when it starts parsing the contractDefinition
If you are allowed to use other libraries and type of the contractDefinition field is not restricted to String then try jsoniter-scala's feature of extraction raw JSON values to byte arrays.
You will need to add dependencies:
libraryDependencies ++= Seq(
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % "1.1.0" % Compile,
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "1.1.0" % Provided // required only in compile-time
)
Then define types with codecs and parse the input:
import java.nio.charset.StandardCharsets.UTF_8
import com.github.plokhotnyuk.jsoniter_scala.macros._
import com.github.plokhotnyuk.jsoniter_scala.core._
import scala.util.hashing.MurmurHash3
object RawVal {
def apply(s: String) = new RawVal(s.getBytes)
implicit val codec: JsonValueCodec[RawVal] = new JsonValueCodec[RawVal] {
override def decodeValue(in: JsonReader, default: RawVal): RawVal = new RawVal(in.readRawValAsBytes())
override def encodeValue(x: RawVal, out: JsonWriter): Unit = out.writeRawVal(x.bs)
override val nullValue: RawVal = new RawVal(new Array[Byte](0))
}
}
case class RawVal private(bs: Array[Byte]) {
def this(s: String) = this(s.getBytes(UTF_8))
override lazy val hashCode: Int = MurmurHash3.arrayHash(bs)
override def equals(obj: Any): Boolean = obj match {
case that: RawVal => java.util.Arrays.equals(bs, that.bs)
case _ => false
}
override def toString: String = new String(bs, UTF_8)
}
case class ContractJson(contractType: String, contractDefinition: RawVal)
implicit val codec: JsonValueCodec[List[ContractJson]] = JsonCodecMaker.make(CodecMakerConfig)
val jsonBytes =
"""[
| {
| "contractType": "NullContract",
| "contractDefinition": {
| "column": "age",
| "conditions": [
| {
| "conditionType": "equalsCondition",
| "conditionDefinition": {
| "column": "species",
| "value": "person"
| }
| }
| ]
| }
| }
|]
|""".stripMargin.getBytes("UTF-8")
val contractJsons = readFromArray(jsonBytes)
println(contractJsons)
Printed result will be:
List(ContractJson(NullContract, {
"column": "age",
"conditions": [
{
"conditionType": "equalsCondition",
"conditionDefinition": {
"column": "species",
"value": "person"
}
}
]
}))

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");

extracting keys from json string using json4s

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)

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)