I am working on converting Sequence of Custom Object into JSON format using Play Framework 2.5.6.
Below is my code.
import play.api.libs.json.Json
import play.api.libs.json.JsObject
case class Item (id:Long,name:String,price:Double)
object SerializingListIntoJSON {
def details(itemList: Seq[Item]) = {
**Json.arr(itemList.map(item => Json.obj("id" -> item.id,"name" -> item.name,"price" -> item.price)): _*)**
}
def main(args: Array[String]): Unit = {
val itemList = Seq(Item(1, "Programming Scala", 49.99), Item(2, "Scala in Action", 44.99))
println(details(itemList))
}
}
The code is giving me the compilation error in the bold line saying "type mismatch; found : Seq[play.api.libs.json.JsObject] required: Seq[play.api.libs.json.Json.JsValueWrapper]".
I am not sure what is wrong with my code. Any help on this is deeply appreciated.
The following
def details(itemList: Seq[Item]) = {
JsArray(itemList.map(item =>
Json.obj("id" -> item.id, "name" -> item.name, "price" -> item.price)))
}
produces a result of:
[
{
"id": 1,
"name": "Programming Scala",
"price": 49.99
},
...
]
if that is what you are after? If you haven't seen the Play Scala JSON documentation yet, then you might find discussion of Writes and Reads helpful, amongst other details and examples there.
Related
I am using the following code to call the NHS Retrieve Reference Data method
var result = await fhirClient.ReadAsync<CodeSystem>(url);
which returns the following Json (this is a snippet of the full json)
concept": [
{
"code": "BOOKED_CLINICAL_NEED",
"display": "Booked more urgently due to clinical need",
"property": [
{
"code": "effectiveFrom",
"valueDateTime": "2019-07-23T17:09:56.000Z"
},
{
"code": "commentIsMandatory",
"valueBoolean": true
},
{
"code": "canCancelAppointment",
"valueBoolean": false
}
]
}
I have used the GetExtensionValue method for other calls when the data is within an extension but I can't find a similar method for properties.
Is there a simple method or do I need to just cast into the required type manually?
Thanks in advance
There is no convenience method for this. However, the properties per concept are a list, so you could for example iterate over the concepts and select the properties with boolean values using regular list methods:
foreach (var c in myCodeSystem.Concept)
{
var booleanProperties = c.Property.Where(p => (p.Value.TypeName == "boolean"));
// do something with these properties
}
or find all concepts that have a boolean property:
var conceptsWithDateTimeProperties = myCodeSystem.Concept.Where(c => c.Property.Exists(p => (p.Value.TypeName == "dateTime")));
Of course you can make your selections as specific as you need.
I've created a basic Gatsby site with the default starter. I'm now trying to add some custom data (the people array) to gatsby-config.json like so:
module.exports = {
siteMetadata: {
title: `Gatsby Default Starter`,
description: `XXX`,
author: `#gatsbyjs`,
people : [
{ id : 1234, name : "Bill Smith", sales : 143, birthdate : "2233-03-22" },
{ id : 5678, name : "Roger Miller", sales : 281, birthdate : "2230-01-06" }
]
},
plugins: [
`gatsby-plugin-react-helmet`,
{ resolve: `gatsby-source-filesystem`,
options: { name: `images`, path: `${__dirname}/src/images`, }
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{ resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`, short_name: `starter`, start_url: `/`,
background_color: `#663399`, theme_color: `#663399`, display: `minimal-ui`,
icon: `src/images/gatsby-icon.png`
}
}
]
}
Then, in GraphiQL, what I'm trying to do is a query to get a list of people, but limit it to just those with sales above 200, that's my end goal. So first, I did a basic test:
{
site {
siteMetadata {
people {
name
}
}
}
}
That works, I get all people back. Then, I tried:
{
site {
siteMetadata {
people(sales: { gt: 200 }) {
name
}
}
}
}
That gets me an error "Unknown argument sales on field people of type SiteSiteMetadata". That kinda seems to be telling me that Sift underneath Gatsby doesn't have any notion of my custom fields in its schema, which would kind of make sense to me. So, as a test, I try this:
{
site {
siteMetadata(author: { eq: "none" }) {
author
title
}
}
}
My expectation is the query runs successfully but returns an empty result set since the author element's value isn't "none". But instead, I get the same basic error but now telling me "Unknown argument author on field siteMetadata of type Site" and now I'm confused because it seems like it should know about THOSE fields even if it doesn't know about arbitrary ones I add. Then again, maybe that query won't ever work because there's only a single siteMetaData object versus trying to query an array. I'm not sure.
So then I do some research and I see some reference to 'filter', so I try this now:
{
site {
siteMetadata(filter: { eq: "none" }) {
author
title
}
}
}
That gets me "Unknown argument filter on field siteMetadata of type Site."
And now I'm kind of out of ideas.
I did find one post that seemed to possibly imply that you can't query custom data elements like this at all, but some replies seem to imply you, in fact, can (and clearly that first test worked, so the data is found, I just can't get the filtering to work). Maybe I'm using the wrong syntax, but if so then I can't seem to find what the correct syntax looks like (and what's worse is that in the Gatsby docs, the ONE example that MIGHT provide me an answer is error'ing out in the playground and I can't see the code).
It seems like such a simple thing, but I'm at a loss. Any help would be greatly appreciated. Thanks!
EDIT: After I wrote this, I tried putting the data in a separate file that get loaded with the gatsby-transformer-json plugin and tried to query that. The data gets loaded, but I still can't filter the query. I can do:
{
testData {
people {
name
sales
}
}
}
...and that works, returns my data fine. But if I try:
{
testData {
people(sales:{gt:200}) {
name
sales
}
}
}
...or...
{
testData {
people(filter:{sales:{gt:200}}) {
name
sales
}
}
}
...I get the same types of errors. So, I think that at least proves this isn't an issue of querying it from siteMetaData specifically, but I still don't know how to make it do what I want.
For anyone who wants to reproduce this, just add the file data.json in the root of the project with this content:
{
"people" : [
{ "id" : 1234, "name" : "Bill Smith", "sales" : 143, "birthdate" : "2233-03-22" },
{ "id" : 5678, "name" : "Roger Miller", "sales" : 281, "birthdate" : "2230-01-06" }
]
}
Then, add this to the plugins array in gatsby-config.json:
{
resolve: `gatsby-transformer-json`,
options: { typeName: `testData` }
},
{
resolve: `gatsby-source-filesystem`,
options: { name: `data`, path: `${__dirname}/data.json` }
}
No other changes from the initially-generated project are needed. Then, just hop into GraphiQL and try to execute the queries above.
Or, to make things easier, I've created a codesandbox instance that demonstrates this:
https://codesandbox.io/s/gatsby-graphql-querying-json-issue-47km4
EDIT2: I had the thought that maybe this is an issue with GraphiQL itself. So, I created a simple component:
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
const Test = () => {
const testData = useStaticQuery(graphql`
query TestDateQuery {
testData(filter: {sales: {gte:200}}) {
people {
name
}
}
}
`)
console.log("testData", testData);
return (
<div />
)
}
export default Test
I then dropped that into my main Layout component. I get the same sort of error (about filter being an unknown argument) rather than seeing my data. If I remove the filter, I DO see ALL the data. So, again, I can't figure out why just filter isn't working, but that's what I've narrowed it down to.
It seems reasonable to expect one resolver to handle input for any combination of one or more of an object's values. I shouldn't have to write separate resolvers for 'title', 'published', 'author', etc., right?
Here's my example object:
let books = [
{
title: 'Clean Code',
published: 2008,
author: 'Robert Martin',
id: 'afa5b6f4-344d-11e9-a414-719c6709cf8e',
genres: ['refactoring'],
},
{
title: 'Agile software development',
published: 2002,
author: 'Robert Martin',
id: 'afa5b6f5-344d-11e9-a414-719c6709cf9e',
genres: ['agile', 'patterns', 'design'],
},
]
typeDefs:
const typeDefs = gql`
type Book {
title: String
published: Int
author: String
id: ID
genres: [String]
}
type Query {
bookCount: Int!
allBooks(title: String, author: String, genre: String): [Book]
findBooks(title: String!): Book
}
type Mutation {
addBook(
title: String!
published: Int
author: String!
genres: [String]
): Book
editBook(
id: ID
title: String
published: Int
author: String
genres: [String]
): Book
}
`
Here's the resolver I currently have:
Mutation: {
editBook: (_, args) => {
const book = books.find(b => b.id === args.id)
if (!book) {
return null
}
const updatedBook = {
...book,
title: args.title,
author: args.author,
published: args.published,
genres: [args.genres],
}
books = books.map(b => (
b.id === args.id ? updatedBook : b))
return updatedBook
},
}
Here's what is currently happening.
Original object:
"allBooks": [
{
"id": "afa5b6f4-344d-11e9-a414-719c6709cf8e",
"title": "Clean Code",
"author": "Robert Martin",
"published": 2008,
"genres": [
"refactoring"
]
},
{...}
]
Mutation query:
mutation {
editBook(id:"afa5b6f4-344d-11e9-a414-719c6709cf8e", title:"changed"){
id
title
author
published
genres
}
}
Returns this:
{
"data": {
"editBook": {
"id": "afa5b6f4-344d-11e9-a414-719c6709cf8e",
"title": "changed",
"author": null,
"published": null,
"genres": [
null
]
}
}
}
How do I write the resolver to change one or more of an object's values, without changing the unspecified values to 'null'?
My javascript skills are, I'll admit, rather shaky, and I'm guessing the answer lies with a more eloquent map function, but since the code runs inside a graphql schema module, it doesn't handle console.log so troubleshooting is problematic. Any recommendations to address that would be extremely helpful as well, so I can troubleshoot my own problems better.
Couple of points
There's no need to use map or to reassign books. Because the book variable is a reference to the object in your books array, you can mutate it (but not reassign it) and you will mutate the object in the array. See this question for additional details.
const book = books.find(b => b.id === args.id)
if (book) {
// you cannot reassign book and still have the original value change,
// but you can mutate it
book.title = args.title
book.author = args.author
book.published = args.published
book.genres = args.genres
}
return book
To change only the values present in args, you have some options:
You can use the logical OR operator (||) to provide another value to assign if the first value is falsey. Here, book.title will only be used if args.title is undefined, null, 0, NaN, "", or false.
book.title = args.title || book.title
You can use Object.assign, which copies the properties from one object to another. Since args will be missing those properties that aren't provided, you can just do something like the below snippet. This is much more elegant and concise, but will only work if your argument names match your property names.
Object.assign(book, args)
You can loop through the args object. This might be more desirable if you have one or more arguments that don't match the properties of book:
Object.keys(args).forEach(argName => {
const argValue = args[argName]
book[argName] = argValue
})
Putting it all together, we might do:
const book = books.find(b => b.id === args.id)
if (book) {
Object.assign(book, args)
}
return book
Following http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters-completion.html
How can I index/insert (I can do mapping) an object using Nest client library to be able to provide following options:
"input": ...,
"output": ...,
"payload" : ...,
"weight" : ...
I would like to be able to provide multiple values in 'input' option.
Can't find anyway of doing this using NEST.
Thank you
NEST provides the SuggestField type in order to assist in indexing completion suggestions. You don't necessarily need to use this type, you can provide your own that contains the expected completion fields (input, output, etc...), but the purpose of SuggestField is to make the whole process easier by already providing a baked in type.
Usage:
Add a suggest field to the document/type you are indexing:
public class MyType
{
public SuggestField Suggest { get; set; }
}
Your mapping should look something like:
client.Map<MyType>(m => m
.Properties(ps => ps
.Completion(c => c.Name(x => x.Suggest).Payloads(true))
)
);
Indexing example:
var myType = new MyType
{
Suggest = new SuggestField
{
Input = new [] { "Nevermind", "Nirvana" },
Output = "Nirvana - Nevermind",
Payload = new { id = 1234 },
Weight = 34
}
};
client.Index<MyType>(myType);
Hope that helps.
Using play 2.x, I have the following Form mapping:
val relocationDtoForm: Form[RelocationDto] = Form(
mapping(
"_type" -> text,
"sourceToken" -> text,
"exchange" -> optional(jodaDate("dd/MM/yyyy")),
"completion" -> optional(jodaDate("dd/MM/yyyy")),
"expectedMoveIn" -> optional(jodaDate("dd/MM/yyyy"))
)(RelocationDto.apply)(RelocationDto.unapply)
)
I would like to add validation so that if _type=="sale", then exchange is a required field, but if _type=="let" then expectedMoveIn is a required field. I cant seem to find a way to do this with the standard play validators, is there a way to do it?
Cheers
Nic
I used to work for Her Majesty's Revenue and Customs (UK) and looks like HMRC have open-sourced a very useful and easy to use library specialising in conditional Play mappings: https://github.com/hmrc/play-conditional-form-mapping
You can use the verifying method of Mapping. This will allow you to create constraints after the form has been successfully bound to a class, where you can access other fields. It accepts the error message and a boolean function, which will add an error to the form when false.
val relocationDtoForm: Form[RelocationDto] = Form(
mapping(
"_type" -> text,
"sourceToken" -> text,
"exchange" -> optional(jodaDate("dd/MM/yyyy")),
"completion" -> optional(jodaDate("dd/MM/yyyy")),
"expectedMoveIn" -> optional(jodaDate("dd/MM/yyyy"))
)(RelocationDto.apply)(RelocationDto.unapply)
.verifying(
"Exchange is required for sales.",
relocationRto => if(relocationRto._type == "sale") relocationRto.isDefined else true
).verifying(
"Expected move in is required for let?",
relocationRto => if(relocationRto._type == "let") expectedMoveIn.isDefined else true
)
)
My final solution at long last was:
def save(id: String) = Action { implicit request =>
//Runs some extra validation based on the _type value
def typeSpecificValidate(params: Map[String,Seq[String]]): Seq[FormError] = params("_type") match {
case Seq("sale") => {
Forms.tuple(
"exchange" -> jodaDate("dd/MM/yyyy"),
"completion" -> jodaDate("dd/MM/yyyy")
).bind(params.mapValues(seq => seq.head)) match {
case Left(errors) => errors //Bind returns a Left(List(FormErrors)) or a Right(boundTuple)
case _ => Nil
}
}
case Seq("let") => {
Forms.tuple(
"expectedMoveIn" -> jodaDate("dd/MM/yyyy")
).bind(params.mapValues(seq => seq.head)) match {
case Left(errors) => errors //Bind returns a Left(List(FormErrors)) or a Right(boundTuple)
case _ => Nil
}
}
}
val extraErrors = typeSpecificValidate(request.body.asFormUrlEncoded.get)
Logger.debug("Custom validator found: " + extraErrors)
val ff = relocationDtoForm.bindFromRequest
ff.copy(errors = ff.errors ++ extraErrors).fold(
formWithErrors => {
formWithErrors.errors.foreach(e => Logger.debug(e.toString))
BadRequest(views.html.relocations.detail(id, formWithErrors))
},
relocationDto => {
Logger.debug(relocationDto.toString)
Ok(views.html.relocations.detail(id, relocationDtoForm.fill(relocationDto)))
}
)
}