I'm trying to implement a service in my play2 app that uses elastic4s to get a document by Id.
My document in elasticsearch:
curl -XGET 'http://localhost:9200/test/venues/3659653'
{
"_index": "test",
"_type": "venues",
"_id": "3659653",
"_version": 1,
"found": true,
"_source": {
"id": 3659653,
"name": "Salong Anna och Jag",
"description": "",
"telephoneNumber": "0811111",
"postalCode": "16440",
"streetAddress": "Kistagången 12",
"city": "Kista",
"lastReview": null,
"location": {
"lat": 59.4045675,
"lon": 17.9502138
},
"pictures": [],
"employees": [],
"reviews": [],
"strongTags": [
"skönhet ",
"skönhet ",
"skönhetssalong"
],
"weakTags": [
"Frisörsalong",
"Frisörer"
],
"reviewCount": 0,
"averageGrade": 0,
"roundedGrade": 0,
"recoScore": 0
}
}
My Service:
#Singleton
class VenueSearchService extends ElasticSearchService[IndexableVenue] {
/**
* Elastic search conf
*/
override def path = "test/venues"
def getVenue(companyId: String) = {
val resp = client.execute(
get id companyId from path
).map { response =>
// transform response to IndexableVenue
response
}
resp
}
If I use getFields() on the response object I get an empty object. But if I call response.getSourceAsString I get the document as json:
{
"id": 3659653,
"name": "Salong Anna och Jag ",
"description": "",
"telephoneNumber": "0811111",
"postalCode": "16440",
"streetAddress": "Kistagången 12",
"city": "Kista",
"lastReview": null,
"location": {
"lat": 59.4045675,
"lon": 17.9502138
},
"pictures": [],
"employees": [],
"reviews": [],
"strongTags": [
"skönhet ",
"skönhet ",
"skönhetssalong"
],
"weakTags": [
"Frisörsalong",
"Frisörer"
],
"reviewCount": 0,
"averageGrade": 0,
"roundedGrade": 0,
"recoScore": 0
}
As you can se the get request omits info:
"_index": "test",
"_type": "venues",
"_id": "3659653",
"_version": 1,
"found": true,
"_source": {}
If I try to do a regular search:
def getVenue(companyId: String) = {
val resp = client.execute(
search in "test"->"venues" query s"id:${companyId}"
//get id companyId from path
).map { response =>
Logger.info("response: "+response.toString)
}
resp
}
I get:
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 1,
"hits": [
{
"_index": "test",
"_type": "venues",
"_id": "3659653",
"_score": 1,
"_source": {
"id": 3659653,
"name": "Salong Anna och Jag ",
"description": "",
"telephoneNumber": "0811111",
"postalCode": "16440",
"streetAddress": "Kistagången 12",
"city": "Kista",
"lastReview": null,
"location": {
"lat": 59.4045675,
"lon": 17.9502138
},
"pictures": [],
"employees": [],
"reviews": [],
"strongTags": [
"skönhet ",
"skönhet ",
"skönhetssalong"
],
"weakTags": [
"Frisörsalong",
"Frisörer"
],
"reviewCount": 0,
"averageGrade": 0,
"roundedGrade": 0,
"recoScore": 0
}
}
]
}
}
My Index Service:
trait ElasticIndexService [T <: ElasticDocument] {
val clientProvider: ElasticClientProvider
def path: String
def indexInto[T](document: T, id: String)(implicit writes: Writes[T]) : Future[IndexResponse] = {
Logger.debug(s"indexing into $path document: $document")
clientProvider.getClient.execute {
index into path doc JsonSource(document) id id
}
}
}
case class JsonSource[T](document: T)(implicit writes: Writes[T]) extends DocumentSource {
def json: String = {
val js = Json.toJson(document)
Json.stringify(js)
}
}
and indexing:
#Singleton
class VenueIndexService #Inject()(
stuff...) extends ElasticIndexService[IndexableVenue] {
def indexVenue(indexableVenue: IndexableVenue) = {
indexInto(indexableVenue, s"${indexableVenue.id.get}")
}
Why is getFields empty when doing get?
Why is query info left out when doing getSourceAsString in a get request?
Thank you!
What you're hitting in question 1 is that you're not specifying which fields to return. By default ES will return the source and not fields (other than type and _id). See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-fields.html
I've added a test to elastic4s to show how to retrieve fields, see:
https://github.com/sksamuel/elastic4s/blob/master/src%2Ftest%2Fscala%2Fcom%2Fsksamuel%2Felastic4s%2FSearchTest.scala
I am not sure on question 2.
The fields are empty because elasticsearch don't return it.
If you need fields, you must indicate in query what field you need:
this is you search query without field:
search in "test"->"venues" query s"id:${companyId}"
and in this query we indicate which field we want to, in this case 'name' and 'description':
search in "test"->"venues" fields ("name","description") query s"id:${companyId}"
now you can retrieve the fields:
for(x <- response.getHits.hits())
{
println(x.getFields.get("name").getValue)
You found a getSourceAsString in a get request because the parameter _source is to default 'on' and fields is to default 'off'.
I hope this will help you
Related
I have simple json:
{
"name": "John",
"placesVisited": [
{
"name": "Paris",
"data": {
"weather": "warm",
"date": "31/01/22"
}
},
{
"name": "New York",
"data": [
{
"weather": "warm",
"date": "31/01/21"
},
{
"weather": "cold",
"date": "28/01/21"
}
]
}
]
}
as you can see in this json there is placesVisited field, and if name is "New York" the "data" field is a List, and if the name is "Paris" its an object.
what I want to do is to pull the placesVisited object where "name": "New York" and then I will parse it to a case class I have, I can't use this case class for both objects in placesVisited cause they have diff types for the same name.
so what I thought is to do something like:
(myJson \ "placesVisited") and here I need to add something that will give me element where name is "New York", how can I do that?
my result should be this:
{
"name": "New York",
"data": [
{
"weather": "warm",
"date": "31/01/21"
},
{
"weather": "cold",
"date": "28/01/21"
}
]
}
something like this maybe can happen but its horrible haha:
(Json.parse(myjson) \ "placesVisited").as[List[JsObject]].find(item => {
item.value.get("name").toString.contains("New York")
}).getOrElse(throw Exception("could not find New York element")).as[NewYorkModel]
item.value.get("name").toString can slightly be simplified to (item \ "name").as[String] but otherwise there's not much to improve.
Another option is to use a case class Place(name: String, data: JsValue) and do it like this:
(Json.parse(myjson) \ "placesVisited")
.as[List[Place]]
.find(_.name == "New York")
When I issue this curl command:
curl http://localhost/refeq/v1/vehiclesequipements/?format=json -s | jq .
I get:
{
"objects":[
{
"vehicle_id": 9759,
"resource_uri": "/refeq/v1/vehiclesequipements/9759/",
"noArticle": "",
"name": "OBCIV_TFT2",
"mac": "84:7e:40:e9:f2:1e"
},
{
"vehicle_id": 9899,
"resource_uri": "/refeq/v1/vehiclesequipements/9899/",
"noArticle": "",
"name": "FILM_FRONT",
"mac": "00:40:9d:a2:36:fe"
},
{
"vehicle_id": 9899,
"resource_uri": "/refeq/v1/vehiclesequipements/9899/",
"noArticle": "",
"name": "OBCIV_TFT1",
"mac": "84:7e:40:ea:a4:36"
}
],
"meta": {
"total_count": 3,
"offset": 0,
"limit": 0
}
}
I would like to get two ressources about the vehicle_id: 9899
to get something like this:
{
"objects":[
{
"vehicle_id": 9899,
"resource_uri": "/refeq/v1/vehiclesequipements/9899/",
"noArticle": "",
"name": "FILM_FRONT",
"mac": "00:40:9d:a2:36:fe"
},
{
"vehicle_id": 9899,
"resource_uri": "/refeq/v1/vehiclesequipements/9899/",
"noArticle": "",
"name": "OBCIV_TFT1",
"mac": "84:7e:40:ea:a4:36"
}
],
"meta": {
"total_count": 2,
"offset": 0,
"limit": 0
}
}
the problem I'm facing is that
curl http://localhost/refeq/v1/vehiclesequipements/9899/?format=json -s
returns:
More than one resource is found at this URI.
How to return multiple ressources having ressource_uri ?
I looked at the doc but I don't understand how to achieve this ..
Here's my api.py
from tastypie.resources import ModelResource
from tastypie.serializers import Serializer
from tastypie.authorization import Authorization
from refeq.models import VehiclesEquipements
USE_LOCAL_TIME = True
class MyDateSerializer(Serializer):
def format_datetime(self, data):
return data.strftime("%Y-%m-%dT%H:%M:%S")
class VehiclesEquipementsResource(ModelResource):
class Meta:
queryset = VehiclesEquipements.objects.all()
resource_name = 'vehiclesequipements'
#filtering = {"vehicle_id":["exact","in"]}
authorization=Authorization()
limit = 0
max_limit = 0
if USE_LOCAL_TIME:
serializer = MyDateSerializer()
and models.py
class VehiclesEquipements(models.Model):
vehicle_id = models.IntegerField(primary_key=True)
noArticle = models.CharField(max_length=30, blank=True)
name = models.CharField(max_length=30, blank=True)
mac = models.CharField(max_length=30, blank=True)
class Meta:
db_table = 'vw_vehicles_equipements'
managed = False
Final note:
I cannot use uri filtering:
curl 'http://dope-apipc01/refeq/v1/vehiclesequipements/?format=json&vehicle_id__exact=9899'
I am newbie to Django and tastypie... Any help is appreciated.
It seems like to access a ressource it has to have a unique resource_uri otherwise exception MultipleObjectsReturned is returned.
Considering 2 sets of data as follows:
JSON1=> {
"data": [
{"id": "1-abc",
"model": "Agile",
"status":"open"
"configuration": {
"state": "running",
"rootVolumeSize": "0.00000",
"count": "2",
"type": "large",
"platform": "Linux"
}
"stateId":"123-567"
}
]}
JSON2=>{
"data": [
{"id": "1-abc",
"model": "Agile",
"configuration": {
"state": "running",
"diskSize": "0",
"type": "small",
"platform":"Windows"
}
}
]}
I need to compare JSON1 and JSON2 based on the 1st field id and if they match , I need to merge JSON1 with JSON 2 retaining the existing values in JSON2( only append fields not present).
I have coded the same as below:
private def merger(JSON1: Seq[JSON], JSON2: Seq[JSON]):Seq[JSON] = {
val abcKey = JSON1.groupBy(_.id) map { case (k, v) => (k, v.head)
val mergedRecords = for {
xyzJSON<- JSON2
} yield (
abcKey.get(xyzJSON.id) match {
case Some(JSON1) => xyzJSON.copy(status = JSON1.status,
stateId = JSON1.stateId)
case None => xyzJSON.copy(origin = "N/A")
}
)
I am not able to derive at a solution for reconciling the fields within the configurationMap.
Expected result set should be like:
{
"data": [
{"id": "1-abc",
"model": "Agile",
"status":"open"
"configuration": {
"state": "running",
"diskSize": "0",
"rootVolumeSize": "0.00000",
"count": "2",
"type": "small",
"platform": "Windows",
}
"stateId":"123-567"
}
]}
cat main.go:
```
package main
import (
"encoding/json"
"log"
"net"
"net/http"
"net/http/fcgi"
"os"
)
func main() {
//setup the config
configFile := "config.json"
fd, err := os.Open(configFile)
if err != nil {
log.Fatalf("Can't open config file: %v", configFile)
}
CFG := config{}
err = json.NewDecoder(fd).Decode(&CFG)
if err != nil {
log.Fatalf("parse config error: %v", err)
}
//init DB connection
db.InitConnectionInfo(CFG.Database.Host, CFG.Database.Port, CFG.Database.Database, CFG.Database.Username, CFG.Database.Password)
//register HTTP handler
sessionHandler := &handlers.SessionHandler{}
http.Handle("/sessions", sessionHandler)
http.Handle("/sessions/", sessionHandler)
userHandler := &handlers.UserHandler{
Facebook: &oa.OAuth{AppId: CFG.Facebook.Key, Secret: CFG.Facebook.Secret},
Sina: &oa.OAuth{AppId: CFG.Sina.Key, Secret: CFG.Sina.Secret},
Google: &oa.OAuth{AppId: CFG.Google.Key, Secret: CFG.Google.Secret},
Tencent: &oa.OAuth{AppId: CFG.Tencent.Key, Secret: CFG.Tencent.Secret},
Mixpanel: &hu.Share{Token: CFG.Mixpanel.Token},
FacebookShare: &hu.Share{Token: CFG.Facebook.Token},
SinaShare: &hu.Share{Token: CFG.Sina.Token},
GoogleShare: &hu.Share{Token: CFG.Google.Token},
TencentShare: &hu.Share{Token: CFG.Tencent.Token},
}
http.Handle("/users", userHandler)
http.Handle("/users/", userHandler)
//and so on ...
//run server
log.Println("start listen: ", CFG.FcgiAddr)
l, _ := net.Listen("tcp", CFG.FcgiAddr)
log.Fatalf("server error is %v", fcgi.Serve(l, nil))
//##select {}
log.Println("end listen")
}
```
build it and deploy behind nginx.
then client query /users/1234567/places, /users/1234567, and so on...
get the response is {blank data}, {normal user(1234567) data} or {normal user(1234567) data}, {normal user(1234567) data}.
same prefix of query, and same handler, looks like it is overload the response by subsequent HTTP request.
How can I do it?
May need me to give an example of a response:
correct response is :
{
"meta": {
"code": 200,
"text": "OK"
},
"data": {
"count": 21,
"place-tag-maps": [{
"id": "95842310160384",
"place-id": "95551731663150",
"tag-id": "95551579750669",
"ct": "2014-07-01T09:07:28Z"
}, {
"id": "95842310160385",
"place-id": "95551731663150",
"tag-id": "95551579750694",
"ct": "2015-01-15T17:41:23Z"
}, {
"id": "96262389694470",
"place-id": "95551731663150",
"tag-id": "95910120456455",
"ct": "2016-07-18T13:11:39Z"
}, ...],
"places": [{
"id": "95551731663150",
"name": "Kam Fung Restaurant",
"address": "G/F, 41 Spring Garden Ln",
"coordinate": {
"latitude": 22.275576,
"longitude": 114.172582
},
"telephone": "+852 2572 0526",
"city-id": "95530516807703",
"city": "Hong Kong",
"country": "Hong Kong",
"type": "4sq",
"ref-id": "4b1613f8f964a520cdb623e3",
"ct": "2016-02-23T07:42:43.565489Z",
"mt": "2017-02-22T09:35:48.302929Z",
"rating": 7.5,
"stats": {
"foursquare": {
"count": 111,
"value": 7.5
},
"spottly": {
"save-count": 12
}
},
"permanent-close": false,
"price": ""
}, ...],
"posts": [{
"collection-id": "95551746474003",
"coordinate": {
"latitude": 22.275576,
"longitude": 114.172582
},
"ct": "2017-02-22T09:35:47Z",
"facebook-tag-users": [],
"id": "97501586849795",
"medias": [],
"message": "",
"mt": "2017-02-22T09:35:47Z",
"owner-id": "96527264645120",
"place-id": "95551731663150",
"share-to": [],
"star": 5,
"status": "Done",
"tags": []
}, ...],
"users": [{
"id": "95551581323446",
"uid": "hk_epicurus",
"uid-ignore-case": "hk_epicurus",
"name": "Hk Epicurus",
"head": "https://d278wa0j9nq2mp.cloudfront.net/uploader/54aa335ddf4e63450002919a.jpeg",
"site": "www.hkepicurus.com",
"location": "Hong Kong",
"description": "Hong Kong Food \u0026 Travel Bear.\nGrew up in Aust, Malaysia, Tokyo \u0026 HK. \nInstagram: EpicurusHongKong\nFacebook, Spottly \u0026 Twitter: HK Epicurus \n微博: 香港美食-伊比\nFacebook Fans Page: http://on.fb.me/1qDyiIk",
"ct": "2013-10-13T19:12:41Z",
"mt": "2015-11-08T15:22:45Z"
}, ...]
}
}
and
{
"meta": {
"code": 200,
"text": "OK"
},
"data": {
"friends": [{
"id": "97331335725056",
"from-id": "97273770803200",
"to-id": "96527264645120",
"ct": "2017-01-23T07:58:41Z"
}],
"user": {
"ct": "2013-04-30T23:30:05Z",
"description": "Founder and Chief Everything Officer of Spottly. Loves to eat. Have a really bad memory. Wants to remember the best places and make travel research better",
"followers": {
"count": 41291
},
"followings": {
"count": 322
},
"head": "https://d278wa0j9nq2mp.cloudfront.net/uploader/525ffac8df4e6347870145ef.jpeg",
"id": "96527264645120",
"location": "Vancouver | Hong Kong | Beijing ",
"mt": "2015-10-27T09:47:12Z",
"name": "Edwyn Chan",
"site": "http://spottly.com/edwyn",
"uid": "edwyn",
"uid-ignore-case": "edwyn"
}
}
}
but mistake response is
{
"meta": {
"code": 200,
"text": "OK"
}
}
and
{
"meta": {
"code": 200,
"text": "OK"
},
"data": {
"friends": [{
"id": "97331335725056",
"from-id": "97273770803200",
"to-id": "96527264645120",
"ct": "2017-01-23T07:58:41Z"
}],
"user": {
"ct": "2013-04-30T23:30:05Z",
"description": "Founder and Chief Everything Officer of Spottly. Loves to eat. Have a really bad memory. Wants to remember the best places and make travel research better",
"followers": {
"count": 41291
},
"followings": {
"count": 322
},
"head": "https://d278wa0j9nq2mp.cloudfront.net/uploader/525ffac8df4e6347870145ef.jpeg",
"id": "96527264645120",
"location": "Vancouver | Hong Kong | Beijing ",
"mt": "2015-10-27T09:47:12Z",
"name": "Edwyn Chan",
"site": "http://spottly.com/edwyn",
"uid": "edwyn",
"uid-ignore-case": "edwyn"
}
}
}
the first response is error. or it's same as the second response.
sequence request is correct response, parallel request is incorrect response.
problem resolved。
the basic reason is this case:
var x = &{...} //init value
fillX(..., x) //fill the fields of x pointer
//here the fields of x pointer is not same to inner of fillX func
so, change to
var x = &{...}
x = fullX(..., x)
the problem resolved.
but why go pointer is the behavior?
Finally, this problem resolved.
Because the http.Handle register path-pattern and handler pair, the handler is construct by register time only-once, not for any request of path-pattern.
if record info within handler, It will been change by after request.
I want to extract JSON values usgin for-comprehensions
my code is this:
import net.liftweb.json._
val json = parse("""
{
"took": 212,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 4,
"max_score": 0.625,
"hits": [
{
"_index": "siteindex",
"_type": "posts",
"_id": "1",
"_score": 0.625,
"_source": {
"title": "title 1",
"content": "content 1"
},
"highlight": {
"title": [
"<b>title</b> 1"
]
}
},
{
"_index": "siteindex",
"_type": "posts",
"_id": "4",
"_score": 0.19178301,
"_source": {
"title": "title 4",
"content": "content 4"
},
"highlight": {
"title": [
"<b>title</b> 4"
]
}
},
{
"_index": "siteindex",
"_type": "posts",
"_id": "2",
"_score": 0.19178301,
"_source": {
"title": "title 2",
"content": "content 2"
},
"highlight": {
"title": [
"<b>title</b> 2"
]
}
},
{
"_index": "siteindex",
"_type": "posts",
"_id": "3",
"_score": 0.19178301,
"_source": {
"title": "title 3",
"content": "content 3"
},
"highlight": {
"title": [
"<b>title</b> 3"
]
}
}
]
}
}
""")
my "case class" is this:
case class Document(title:String, content:String)
my "for" is this:
val ret: List[Document] = for {
JObject(child) <- json
JField("title", JString(title)) <- child
JField("content", JString(content)) <- child
} yield (Document( title, content ))
and my "list" is this:
ret: List[Document] = List(Document(title 1,content 1), Document(title 4,content 4), Document(title 2,content 2), Document(title 3,content 3))
until here everything is fine!
but now i need something like this:
List(Document2(1,<b>title</b> 1,content 1), Document2(4,<b>title</b> 4,content 4), Document2(2,<b>title</b> 2,content 2), Document2(3,<b>title</b> 3,content 3))
i need the value of:
"highlight": {
"title": [
"<b>*</b> *"
]
}
and this:
"_id": "*",
in my list.
my "case class" is this:
case class Document2(_id:String, title:String, content:String)
i try this, but it does not work
val ret: List[Document2] = for {
JObject(child) <- json
JField("_id", JString(_id)) <- child
JField("title", JString(title)) <- child
JField("content", JString(content)) <- child
} yield (Document2( _id, title, content ))
i don't know, if there is a better way of data extraction for this json
but the result is this:
<console>:23: warning: `withFilter' method does not yet exist on net.liftweb.json.JValue, using `filter' method instead
JObject(child) <- json
ret: List[Document2] = List()
any suggestion please
thanks for your help
Here is an answer similar to the approach from #flav, but I'll give you the structure to map to the json and how to get your end result too. First, the case classes:
case class Document2(_id:String, title:String, content:String)
case class Results(hits:HitsList)
case class HitsList(hits:List[Hit])
case class Hit(_id:String, _source:Source, highlight:Highlight)
case class Source(title:String, content:String)
case class Highlight(title:List[String])
Then, the code for parsing it and converting it:
implicit val formats = DefaultFormats
val results = json.extract[Results]
val docs2 = results.hits.hits.map{ hit =>
Document2(hit._id, hit.highlight.title.head, hit._source.content)
}