[json4s]:Extracting Array of different objects - scala

I am using the facebook graph API and the responses look similar to this:
{
"data": [
{
"id": "311620272349920_311718615673419",
"from": {
"id": "1456046457993048",
"name": "Richard Ettinson"
},
"to": {
"data": [
{
"id": "311620272349920",
"name": "Barbara Fallerman"
}
]
},
"with_tags": {
"data": [
{
"id": "311620272349920",
"name": "Barbara Fallerman"
}
]
},
"message": "I was gong out with her",
"actions": [
{
"name": "Comment",
"link": "https://www.facebook.com/311620272349920/posts/311718615673419"
},
{
"name": "Like",
"link": "https://www.facebook.com/311620272349920/posts/311718615673419"
}
]
}
I managed to for example extract the from field through
val extracted = (json \ "data" \"from").extract[PostFrom]
But I worry that if I use this technique I will need to pass over the Json multiple times to extract all the values I need which could lead to a bad performance.
How exactly could I extract these fields into case classes from the array of non similar objects?
I tried with the following case classes:
abstract class BaseResponse()
case class Data(list:List[Post])
case class Post(id: String, post: PostFrom) extends BaseResponse
case class PostFrom(id: String, name:String)
Which always lead to an empty List, is there a way to get back a Data class which has a list of certain classes which I am interested in? (As example the top level id, from and with_tags)

A possibility I found was to use more case classes instead of inheritance:
case class Root[T](data:Option[T])
case class Post(id: String, from: From, message: String)
case class From(id: String, name:String)
Basically there has to be a root object which takes some kind of graphs response object, additionally it is optional so that it won't throw an exception if there was a problem with the parsing of the response.
I then used it in the following way:
val body = r.entity.asString
val json = parse(r.entity.asString)
val root = json.extract[Root[Post]]
root.data match {
case Some(post) =>
val tagger = Tagger(post.from.id, post.from.name, post.id, post.message)
log.info(s"We received $tagger")
originalSender ! RetrievedTagger(tagger)
case None => originalSender ! NoTaggerFound
}

Related

use JsLookup for multiple level array using play Json for lists with objects and simple lists

i have a function that exctract from a json list of elements based on the path i give it.
the func looks like this:
def findAllValuesAtPath(jsValue: JsObject, path: String): List[JsValue] = {
val jsPath = JsPath(path
.split("\\[\\*]\\.")
.flatMap(s => s.split("\\.")
.map(RecursiveSearch)
).toList)
jsPath(jsValue)
}
for example:
{
"person": {
"kids": [
{
"name": "josh",
"age": 5
},
{
"name": "julia",
"age": 13
}
]
}
}
now if i give the path - "person.kids[*].name" I will get list of their names List("josh", "julia") which its what i want.
but if the list of kids were just simple list like:
{
"person": {
"kids": [
"josh",
"julia"
]
}
}
and i will give the path - "person.kids[*]" I will get empty list List() and I want to get this as List("josh", "julia") (without the brakets)
do you see any way to improve my func to handle both cases?

Serialize Dart Map

What is the best way to unmarshall this json in dart:
{
"Ace-spades-2": {
"rank": { "shortName": "A", "longName": "Ace" },
"suit": { "name": "spades" },
"id": "Ace-spades-2"
},
"Two-spades-2": {
"rank": { "shortName": "2", "longName": "Two" },
"suit": { "name": "spades" },
"id": "Two-spades-2"
},
"Three-spades-2": {
"rank": { "shortName": "3", "longName": "Three" },
"suit": { "name": "spades" },
"id": "Three-spades-2"
},
{....a bunch more cards}
}
Here are my classes that I want to unmarshall into. Specifically I want to make the data Map<String,Card>. How can I do this?
class Card {
Rank rank;
Suit suit;
String id;
Card({this.rank,this.suit,this.id})
}
class Rank {
String shortName;
String longName;
Rank({this.shortName, this.longName});
}
class Suit {
String name;
Suit({this.name});
}
In Go, I would just do
cards := map[string]Card{}
json.Unmarshal(<some_json>, &cards).
What is the best way to do this in dart?
For serialization in Flutter (where reflection is not available) is very common to use the json_serializable package. A good tutorial about this package is also in the flutter doc: https://flutter.dev/docs/development/data-and-backend/json#serializing-json-using-code-generation-libraries
When you have annotated yours classes (and you have completed the others installation instructions - also remember the annotation on the library declaration, which is mandatory), you can take advantage of the Card.fromJson() factory constructor. You can convert your json raw String in a Map<String, dynamic> using the jsonDecode() function (of the dart:convert package), then iterate on the Map and convert every entry in the Map with Card.fromJson.
For example:
Map<String, Card> cardMap = (jsonDecode(jsonString) as Map<String, dynamic>).map((key, cardString) => MapEntry(key, Card.fromJson(cardString)));
A very important note: when you have a class (Card and Rank, in your example) with fields whose type is not primitive, but instead represents another class annotated with JsonSerializable, the JsonSerializable annotation must pass explicitToJson: true (i.e. #JsonSerializable(explicitToJson: true) class Card).
The process to serialize class/deserialize json the first time isn't as fast as in Go, but flutter doesn't have reflection.

Decode a nested array with circe-optics

I have JSON like this:
"data": {
"project": {
"activityChildren": [
{
"id": 2,
"parents": [
{
"id": 1
}
]
},
]
}
}
I'd like to decode this to List[(Long, List[Long])] with circe-optics. I got as far as:
val activityParents: Map[Long, List[Long]] = root.data.activityChildren.each.json
.getAll(json)
.flatMap { activity =>
root.id.long.getOption(activity).map(_ -> root.parents.each.long.getAll(activity))
}
.toMap
I wonder whether it's possible to define a single lens for this that just turns the JSON into the desired map without explicitly mapping over the intermediate array. If so, how?

Scala - Extract a list efficiently from a map

I have a large json object: myNestedObject
{
"size": 2,
"values": [{
"name": "mullock",
"upstatus": "Green",
"details": {
"key": "rupture farms",
"server": "mudos",
"owner": "magog_cartel",
"type": "NORMAL",
"links": {
"self": [{
"address": "https://mudos.com:port/access"
}]
}
}
},{
"name": "tassadar",
"upstatus": "Orange",
"details": {
"key": "archon",
"server": "protoss",
"owner": "aspp67",
"type": "NORMAL",
"links": {
"self": [{
"address": "https://aiur.com:port/access"
}]
}
}
}],
"limit": 100
}
This is what the case classes look like as follows:
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
case class Result(size: Int, limit: Int, values: Seq[Values])
case class Values(name: String, upstatus: String, details: ValuesDetails)
case class ValuesDetails(key: String, server: String, owner: String, `type`: String, links: ValuesDetailsLinks)
case class ValuesDetailsLinks(self: Seq[ValuesDetailsLinksAddress])
case class ValuesDetailsLinksAddress(address: String)
object Foo {
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
def test(Json): Unit
val return = mapper.readValue[Result](Json)
myNestedObject.size
is a field that gives the length of the array
myNestedObject.values(i)
Trying to extract the below values into a List[String]
myNestedObject.values(i).name
I've been using a crude for loop to extract and the code works.
val selectNames: List[String] = (
for (i <- 0 to myNestedObject.size-1 toList) yield
myNestedObject.values(i).name
)
Refactoring my code and trying to use a .map (unsuccessfully) to do the same thing, two attempts:
myNestedObject.map(_ => values(i).name)
myNestedObject.(values(i).name).asInstanceOfList
Disclaimer: I'm a complete novice at this.
SOLUTION: the values list can be accessed without specifying index
myNestedObject.values.map(_.name)
I think you could do this:
myNestedObject.values.map(_.name)
Dylan Grald's answer is correct, but you could also use an equivalent for-comprehension
for (x <- myNestedObject.values)
yield (x.name)
This desugars to the version using the map method. For simple cases like this I prefer just calling the map method directly, but I thought I would mention the for version as an alternative.

Elasticsearch nested filtering (elastic4s, scala)

I am using Elasticsearch 1.7 and elastic4s DSL.
My problem is I can't add and & or filter on a nested document.
For example, here is a JSON representation of my instance of case class Candidate:
{
"name": "Samy"
"interviews": [
{
"clientId": 0,
"stateId": "CANCELED",
},
{
"clientId": 1,
"stateId": "APPROVED"
}
]
Here is my filter :
def filtering(interviewAndCandidates: IntCand)(implicit user: PublicUser): Seq[FilterDefinition] = {
nestedFilter("interviews").filter(termFilter("clientId", user.id)) ::
List(or(interviewAndCandidates.interviews.map(state ⇒ nestedFilter("interviews").filter(termFilter("stateId", state)))))
}
Then I build the query:
var request: SearchDefinition = search in "myIndex" -> "candidate" query {
filteredQuery query {
matchAllQuery
} filter {
and(filters)
}
}
With:
case class IntCand(interviews: List[String])
case class Candidate(name: String, interviews: List[Interview])
case class Interview(clientId: Long, stateId: String)
The problem is when I filter on IntCand(List("CANCELED")) and clientId=1, the response show me the Candidate (I want filter on clientId AND interviews)
I succeed by denormalizing the data