Scala JSON: Collect and parse an array of arrays with lift - json

I have a JSON that has array of arrays. Like this:
{
"id": "532242",
"text": "Some text here. And Here",
"analysis": {
"exec": "true",
"rowID": "always",
"sentences": {
"next": null,
"data": [{
"sequence": "1",
"readability_score_lexical": null,
"readability_score_syntax": null,
"tokens": [{
"word": "Some",
"lemma": "Some"
},
{
"word": "text",
"lemma": "text"
}
]
},
{
"sequence": "3",
"readability_score_lexical": null,
"readability_score_syntax": null,
"tokens": [{
"word": "and",
"lemma": "And"
},
{
"word": "here",
"lemma": "here"
}
]
}
]
}
}
}
The structure is pretty complicated, but I cannot do anything on this side because is the response from an API.
What I need is to get a list "tokens" objects.
I did this with lift-web-json:
case class Token(word:String, lemma:String)
implicit val formats: Formats = net.liftweb.json.DefaultFormats
val jsonObj = net.liftweb.json.parse(json)
val tokens = (jsonObj \\ "tokens").children
for (el <- tokens) {
val m = el.extract[Token]
println(s"Word ${m.word} and ${m.lemma}")
}
but it says:
net.liftweb.json.MappingException: No usable value for word
Do not know how to convert JArray(List(JField(word,JString(Some)), JField(word,JString(text))))
[...]
Caused by: net.liftweb.json.MappingException: Do not know how to convert JArray(List(JField(word,JString(Some)), JField(word,JString(text)))) into class java.lang.String
And I don't understand how could I make it right.

You should get what you expect by replacing:
val tokens = (jsonObj \\ "tokens").children
for (el <- tokens) {
val m = el.extract[Token]
println(s"Word ${m.word} and ${m.lemma}")
}
with:
val tokens = for {
// tokenList are:
// JArray(List(JObject(List(JField(word,JString(Some)), JField(lemma,JString(Some)))), JObject(List(JField(word,JString(text)), JField(lemma,JString(text))))))
// JArray(List(JObject(List(JField(word,JString(and)), JField(lemma,JString(And)))), JObject(List(JField(word,JString(here)), JField(lemma,JString(here))))))
tokenList <- (jsonObj \\ "tokens").children
// subTokenList are:
// List(JObject(List(JField(word,JString(Some)), JField(lemma,JString(Some)))), JObject(List(JField(word,JString(text)), JField(lemma,JString(text)))))
// List(JObject(List(JField(word,JString(and)), JField(lemma,JString(And)))), JObject(List(JField(word,JString(here)), JField(lemma,JString(here)))))
JArray(subTokenList) <- tokenList
// liftToken are:
// JObject(List(JField(word,JString(Some)), JField(lemma,JString(Some))))
// JObject(List(JField(word,JString(text)), JField(lemma,JString(text))))
// JObject(List(JField(word,JString(and)), JField(lemma,JString(And))))
// JObject(List(JField(word,JString(here)), JField(lemma,JString(here))))
liftToken <- subTokenList
// token are:
// Token(Some,Some)
// Token(text,text)
// Token(and,And)
// Token(here,here)
token = liftToken.extract[Token]
} yield token

Related

Preparing JSON array of Objects from multiple array list

I am very new to the Groovy scripts and would like to build a JSON output from the below JSON input. Kindly help!
My JSON input looks like this:
{
"id":"1222",
"storageNode": {
"uuid": "22255566336",
"properties": {
"BuinessUnit": [
"Light",
"Fan",
"Watch"
],
"Contact": [
"abc#gmail.com",
"fhh#gmail.com"
],
"Location": [
"Banglore",
"Surat",
"Pune"
]
}
}
}
Expected Output:
[
{
"BuinessUnit": "Light",
"Contact": "abc#gmail.com",
"Location": "Banglore"
},
{
"BuinessUnit": "Fan",
"Contact": "fhh#gmail.com",
"Location": "Surat"
},
{
"BuinessUnit": "Watch",
"Contact": "",
"Location": "Pune"
}
]
Please note that in case any array is not matching the value count that will always be the last one and in that case, a blank value ("") has to be populated. The "BusinessUnit" object can be referred for array size validation.
My code looks like this:
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
import groovy.json.*;
def Message processData(Message message) {
//Body
def body = message.getBody(String.class);
def jsonSlurper = new JsonSlurper()
def list = jsonSlurper.parseText(body)
String temp
def BU = list.storageNode.properties.get("BusinessUnit")
def builder = new JsonBuilder(
BU.collect {
[
BusinessUnit: it
]
}
)
message.setBody(builder.toPrettyString())
return message
}
It is only returning this:
[
{
"BusinessUnit": "Light"
},
{
"BusinessUnit": "Fan"
},
{
"BusinessUnit": "Watch"
}
]
Now how will I add other parts to it? Please help!
I have come up with the following solution that converts source JSON string to the target JSON string:
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
def json = '''
{
"id":"1222",
"storageNode": {
"uuid": "22255566336",
"properties": {
"BusinessUnit": [
"Light",
"Fan",
"Watch"
],
"Contact": [
"abc#gmail.com",
"fhh#gmail.com"
],
"Location": [
"Banglore",
"Surat",
"Pune"
]
}
}
}
'''
println convert(json)
String convert(String json) {
def list = new JsonSlurper().parseText(json)
List<String> units = list.storageNode.properties.BusinessUnit
List<String> contacts = list.storageNode.properties.Contact
List<String> locations = list.storageNode.properties.Location
def result = []
units.eachWithIndex { unit, int index ->
result << [
BusinessUnit: unit,
Contact : contacts.size() > index ? contacts[index] : '',
Location : locations.size() > index ? locations[index] : '',
]
}
return new JsonBuilder(result).toPrettyString()
}
I've omitted the logic of getting string from the message and packaging transformed JSON into message.
I hope it will help you to move forward. Please let me know if you need further assistance here.
You can use the built-in Groovy facilities, like transpose():
import groovy.json.*
def json = new JsonSlurper().parseText '''{ "id":"1222", "storageNode": { "uuid": "22255566336", "properties": {
"BuinessUnit": [ "Light", "Fan", "Watch" ],
"Contact": [ "abc#gmail.com", "fhh#gmail.com" ],
"Location": [ "Banglore", "Surat", "Pune" ] } } }'''
def names = json.storageNode.properties*.key
def values = json.storageNode.properties*.value
int maxSize = values*.size().max()
// pad lists with trainiling spaces
values.each{ v -> ( maxSize - v.size() ).times{ v << '' } }
def result = values.transpose().collect{ tuple -> [ names, tuple ].transpose().collectEntries{ it } }
assert result.toString() == '[[BuinessUnit:Light, Contact:abc#gmail.com, Location:Banglore], [BuinessUnit:Fan, Contact:fhh#gmail.com, Location:Surat], [BuinessUnit:Watch, Contact:, Location:Pune]]'
This piece of code can process everything under storageNode.properties.

Deep remove specific empty json array in circe scala

I want to remove deep empty json array from my json before/during processing by circe.
Incoming JSON
{
"config": {
"newFiles": [{
"type": "audio",
"value": "welcome1.mp3"
}],
"oldFiles": [],
"channel": "BC"
}
}
or
{
"config": {
"newFiles": [],
"oldFiles": [{
"type": "audio",
"value": "welcome1.mp3"
}],
"channel": "BC"
}
}
Resulted Json should look like
{
"config": {
"newFiles": [{
"type": "audio",
"value": "welcome1.mp3"
}],
"channel": "BC"
}
}
or
{
"config": {
"oldFiles": [{
"type": "audio",
"value": "welcome1.mp3"
}],
"channel": "BC"
}
}
What i understand that this can be done before decoding config as well as during decoding config.
The idea here is i want to handle only one of files (either new or old) at my case class level.
Method 1: Tried at config decoding level which works well.
case class File(`type`: String, value: String)
case class Config(files: List[File],
channel: String = "BC")
object Config{
implicit final val FileDecoder: Decoder[File] = deriveDecoder[File]
implicit val ConfigDecoder: Decoder[Config] = (h:HCursor) =>
for {
oldFiles <- h.get[List[File]]("oldFiles")
files <- if (oldFiles.isEmpty) h.get[List[File]]("newFiles") else h.get[List[File]]("oldFiles")
channel <- h.downField("channel").as[String]
}yield{
Config(files, channel)
}
}
case class Inventory(config: Config)
object Inventory {
implicit val InventoryDecoder: Decoder[Inventory] = deriveDecoder[Inventory]
}
Method 2: Tried before feeding into decoding which didn't worked out
Let me know what could be the elegant approach to handle it.
PS: I have multiple similar config decoders and if i handle this at config decoding level then there will be a lot of boiler plate code.
I have simplified the problem a little bit again, but combining this with the previous answer should be simple.
I also took advantage of cats.data.NonEmptyList
final case class Config(files: NonEmptyList[String], channel: String = "BC")
object Config {
implicit final val ConfigDecoder: Decoder[Config] =
(
(Decoder[NonEmptyList[String]].at(field = "newFiles") or Decoder[NonEmptyList[String]].at(field = "oldFiles")),
Decoder[String].at(field = "channel")
).mapN(Config.apply).at(field = "config")
}
This can be used like this:
val data =
"""[
{
"config": {
"newFiles": ["Foo", "Bar", "Baz"],
"oldFiles": [],
"channel": "BC"
}
},
{
"config": {
"newFiles": [],
"oldFiles": ["Quax"],
"channel": "BC"
}
}
]"""
parser.decode[List[Config]](data)
// res: Either[io.circe.Error, List[Config]] =
// Right(List(
// Config(NonEmptyList("Foo", "Bar", "Baz"), "BC"),
// Config(NonEmptyList("Quax"), "BC")
// ))
Note: I am assuming that at least one of the two lists must be non-empty and give priority to the new one.
You can see the code running here.

Scala Json parsing

This is the input json which I am getting which is nested json structure and I don't want to map directly to class, need custom parsing of some the objects as I have made the case classes
{
"uuid": "b547e13e-b32d-11ec-b909-0242ac120002",
"log": {
"Response": {
"info": {
"receivedTime": "2022-02-09T00:30:00Z",
"isSecure": "Yes",
"Data": [{
"id": "75641",
"type": "vendor",
"sourceId": "3",
"size": 53
}],
"Group": [{
"isActive": "yes",
"metadata": {
"owner": "owner1",
"compressionType": "gz",
"comments": "someComment",
"createdDate": "2022-01-11T11:00:00Z",
"updatedDate": "2022-01-12T14:17:55Z"
},
"setId": "1"
},
{
"isActive": "yes",
"metadata": {
"owner": "owner12",
"compressionType": "snappy",
"comments": "someComment",
"createdDate": "2022-01-11T11:00:00Z",
"updatedDate": "2022-01-12T14:17:55Z"
},
"setId": "2"
},
{
"isActive": "yes",
"metadata": {
"owner": "owner123",
"compressionType": "snappy",
"comments": "someComment",
"createdDate": "2022-01-11T11:00:00Z",
"updatedDate": "2022-01-12T14:17:55Z"
},
"setId": "4"
},
{
"isActive": "yes",
"metadata": {
"owner": "owner124",
"compressionType": "snappy",
"comments": "someComments",
"createdDate": "2022-01-11T11:00:00Z",
"updatedDate": "2022-01-12T14:17:55Z"
},
"setId": "4"
}
]
}
}
}
}
Code that I am trying play json also tried circe . Please help ..New to scala world
below is object and case class
case class DataCatalog(uuid: String, data: Seq[Data], metadata: Seq[Metadata])
object DataCatalog {
case class Data(id: String,
type: String,
sourceId: Option[Int],
size: Int)
case class Metadata(isActive: String,
owner: String,
compressionType: String,
comments: String,
createdDate: String,
updatedDate: String
)
def convertJson(inputjsonLine: String): Option[DataCatalog] = {
val result = Try {
//val doc: Json = parse(line).getOrElse(Json.Null)
//val cursor: HCursor = doc.hcursor
//val uuid: Decoder.Result[String] = cursor.downField("uuid").as[String]
val lat = (inputjsonLine \ "uuid").get
DataCatalog(uuid, data, group)
}
//do pattern matching
result match {
case Success(dataCatalog) => Some(dataCatalog)
case Failure(exception) =>
}
}
}
Any parsing api is fine.
If you use Scala Play, for each case class you should have an companion object which will help you a lot with read/write object in/from json:
object Data {
import play.api.libs.json._
implicit val read = Json.reads[Data ]
implicit val write = Json.writes[Data ]
def tupled = (Data.apply _).tupled
}
object Metadata {
import play.api.libs.json._
implicit val read = Json.reads[Metadata ]
implicit val write = Json.writes[Metadata ]
def tupled = (Metadata.apply _).tupled
}
Is required as each companion object to be in same file as the case class. For your json example, you need more case classes because you have a lot of nested objects there (log, Response, info, each of it)
or, you can read the field which you're interested in as:
(jsonData \ "fieldName").as[CaseClassName]
You can try to access the Data value as:
(jsonData \ "log" \ "Response" \ "info" \ "Data").as[Data]
same for Metadata

How to update nested JSON array in scala

I'm a newbie to scala/play and I've been trying to update (add a new id to) the query array in the sections[1] of the JSON but I've had no success traversing the JSON as I have little knowledge of transformers and how to use it.
"definitions": [
{
"sections": [
{
"priority": 1,
"content": {
"title": "Driver",
"links": [
{
"url": "https://blabla.com",
"text": "See all"
}
]
},
"SearchQuery": {
"options": {
"aggregate": true,
"size": 20,
},
"query": "{\"id\":{\"include\":[\"0wxZ4Nr2\", \"0wxZbNr2\", \"6WZOPMw1\"}}"
}
},
{
"priority": 2,
"content": {
"title": "Deliver",
"links": [
{
"url": "https://blabla.com",
"text": "See all"
}
]
},
"SearchQuery": {
"options": {
"aggregate": true,
"size": 20,
},
"query": "{\"id\":{\"include\":[\"2W12Q2wq\", \"Nwq09lW3\", \"QweNN2d9\"]}}"
}
}
]
}
Any suggestions on how I can achieve this. My goal is to put values inside the specific fields of JSON array. I am using play JSON library throughout my application?
As you noticed if you use PlayJSON you can use Json Transformers
Updating a field would work like this:
val queryUpdater = (__ \ "definitions" \ 1 \ "SearchQuery" \ "query").json.update(
of[JsString].map {
case JsString(value) =>
val newValue: String = ... // calculate new value
JsString(newValue)
}
)
json.transform(queryUpdater)
If you needed to update all queries it would be more like:
val updateQuery = (__ \ "SearchQuery" \ "query").json.update(
of[JsString].map {
case JsString(value) =>
val newValue: String = ... // calculate new value
JsString(newValue)
}
)
val updateQueries = (__ \ "definitions").json.update(
of[JsArray].map {
case JsArray(arr) =>
JsArray(arr.map(_.transform(updateQuery)))
}
)
json.transform(updateQueries)

How can i Parse JSON data values present at any level in GROOVY

Following is my Parsed JSON with json Surplur . i has been required for SOAPUI scripts to manupulate
{buildInfo={application=RepCatalog, buildDate=Thu Oct 13 17:01:48 IST 2016, version=1.0.0}, data={Reps=[{cascadeCount=0, catalogRep={RepId=48961, RepType=REPORT, initialCreation=10/13/2016 20:39:11, lastAccessed=10/13/2016 20:39:11, lastModified=10/13/2016 20:39:11, parentRep={RepId=48962, RepType=REPORT, initialCreation=10/13/2016 20:39:14, lastAccessed=10/13/2016 20:39:14, lastModified=10/13/2016 20:39:14, status=OPEN, title=REPORT1476371359504}, rights=[{availability=PUBLIC, isDefault=true}], status=OPEN, title=REPORT1476371357505, userTags=[PRIVATE1476371349835]}, status=success}]}, status=success, summary={apiName=Integration Service, partialRepSucceeded=0, totalRepFailed=0, totalRepProccessed=1, totalRepSucceeded=1}, time=6674}
Following is unparsed JSON
{
"summary": {
"apiName": "Integration Service",
"totalRepProccessed": 1,
"totalRepFailed": 0,
"totalRepSucceeded": 1,
"partialRepSucceeded": 0
},
"buildInfo": {
"application": "RepCatalog",
"version": "1.0.0",
"buildDate": "Thu Oct 13 17:01:48 IST 2016"
},
"status": "success",
"data": {"Reps": [ {
"status": "success",
"catalogRep": {
"RepId": 48961,
"RepType": "REPORT",
"title": "REPORT1476371357505",
"rights": [ {
"availability": "PUBLIC",
"isDefault": true
}],
"initialCreation": "10/13/2016 20:39:11",
"lastModified": "10/13/2016 20:39:11",
"lastAccessed": "10/13/2016 20:39:11",
"status": "OPEN",
"parentRep": {
"RepId": 48962,
"RepType": "REPORT",
"title": "REPORT1476371359504",
"status": "OPEN"
},
"userTags": ["PRIVATE1476371349835"]
},
"cascadeCount": 0
}]},
"time": 6674
}
I want to parse it to get values of All RepId in above in groovy SOAPUI
Given your input as a string variable called json, the following script:
def extractRepIds (def tree, def ids = []) {
switch (tree) {
case Map:
tree.each { k, v ->
if (k == "RepId") { ids << v }
extractRepIds(v, ids)
}
return ids
case Collection:
tree.each { e -> extractRepIds(e, ids) }
return ids
default :
return ids
}
}
def extractRepIdsFromJson(def jsonString) {
def tree = new JsonSlurper().parseText(jsonString)
extractRepIds(tree)
}
println extractRepIdsFromJson(json)
produces the following results:
[48961, 48962]
Alternate Solution
The extractRepIds method can be written somewhat more cleanly by using the inject() method:
def extractRepIds (def tree) {
switch (tree) {
case Map:
return tree.inject([]) { list, k, v ->
list + (k == "RepId" ? [v] : extractRepIds(v))
}
case Collection:
return tree.inject([]) { list, e ->
list + extractRepIds(e)
}
default :
return []
}
}
With all else being the same, this yields the same results.
If you want to find all occurrences in a file, you can use regular expressions. It may be useful in this case.
def pattern = ~'\"RepId\":\\s(\\d+)' //"RepId": 48961
def path = "/tmp/data.json"
def data = new File(path).text
def repIds = []
pattern.matcher(data).findAll{fullMatch,repId ->
repIds << repId
}