Graphql Cant Return Array - javascript

I am using Apollo-Server and trying to create a REST query against the IEX REST API which returns back data that looks like this:
{
"symbol": "AAPL",
"companyName": "Apple Inc.",
"exchange": "Nasdaq Global Select",
"industry": "Computer Hardware",
"website": "http://www.apple.com",
"description": "Apple Inc is an American multinational technology company. It designs, manufactures, and markets mobile communication and media devices, personal computers, and portable digital music players.",
"CEO": "Timothy D. Cook",
"issueType": "cs",
"sector": "Technology",
"tags": [
"Technology",
"Consumer Electronics",
"Computer Hardware"
]
}
I am using datasources. My typeDefs and resolvers look something like this:
const typeDefs = gql`
type Query{
stock(symbol:String): Stock
}
type Stock {
companyName: String
exchange: String
industry: String
tags: String!
}
`;
const resolvers = {
Query:{
stock: async(root, {symbol}, {dataSources}) =>{
return dataSources.myApi.getSomeData(symbol)
}
}
};
The Datasource file looks like this:
class MyApiextends RESTDataSource{
constructor(){
super();
this.baseURL = 'https://api.iextrading.com/1.0';
}
async getSomeData(symbol){
return this.get(`/stock/${symbol}/company`)
}
}
module.exports = MyApi
I can run a query and get data back, but it is not formatting in an array and is throwing an error when I run a query like so:
query{
stock(symbol:"aapl"){
tags
}
}
Error:
{
"data": {
"stock": null
},
"errors": [
{
"message": "String cannot represent value: [\"Technology\", \"Consumer Electronics\", \"Computer Hardware\"]",
"locations": [
{
"line": 3,
"column": 5
}
],
"path": [
"stock",
"tags"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"TypeError: String cannot represent value: [\"Technology\", \"Consumer Electronics\", \"Computer Hardware\"]",
The data I am expecting (technology, consumer electronics, and computer hardware) are correct, but not returning in an array. I tried to make a new type for tags, and set the it with a tag property, but the value just returns null.
I am very new to graphql, so any feedback is appreciated!

Inside your type definition for Stock, you're defining the type for the tags field as String!:
tags: String!
That tells GraphQL to expect a String value that will not be null. The actual data being returned by the REST endpoint, however, is not a String -- it's an array of Strings. So your definition should minimally look like this:
tags: [String]
If you want GraphQL to throw if the tags value is null, add an exclamation point to the end to make it non-nullable:
tags: [String]!
If you want GraphQL to throw if any of the values inside the array are null, add an exclamation point inside the brackets. You can also combine the two:
tags: [String!]!

Related

ERROR: invalid character ' ' in literal true (expecting 'e') - POST API JSON

I am trying to use the POST method to insert some data from a person with JSON. I am using the code from JS to construct, but when i start the transformation, it sends me "ERROR: invalid character ' ' in literal true (expecting 'e')". Does anyone know how to solve it?
const obj = {
"num_matricula": num_matricula,
"limit_date": "2022-05-20",
"admission_date": admission_date,
"cost_center": cost_center,
"pos_number": pos_number,
"role": role,
"department": department,
"pagamento": {
"vinculo": vinculo,
"valor": valor,
"recorrencia": recorrencia,
"contaBancaria": {
"banco": "001",
"carta": "c9160763-db6c-4e8c-a1ad-ad8709c99be2"
}
},
"deficiencia": deficiencia,
"jornada": jornada,
"profile": {
"name": name,
"email": email,
"mobile": mobile
},
"exame": {
"clinica": "6dc84ce4-7d9f-48ec-b9b1-a8a895a21fd4",
"data": "2022-05-15",
"hora": "14:00",
"obs": "Comparecer de manhã",
"guia": "e37dab24-c7a4-4b92-b9d1-32ed538b8300",
},
"docs": ["c9e26093-5e0c-4bd2-bea3-ac5182a6179f"],
"send_sms": true,
"send_email": true
};
const myJSON = JSON.stringify(obj);
Some columns are already provided with data from previous step (you can see in the images below), that is why i just repeated the column name in the JS code. Just to let you know, the boolean types of data are the columns: send_email, send_sms and deficiencia.
The problem is that JSON is a string. So in your first line you see this is not valid json: "num_matricula": num_matricula,
only numbers can be without double quotes: "num_matricula": 1234,

Generic way for filtering properties from arbitrary and deeply nested JSON (with arrays)

Goal
I want to develop a middleware in TypeScript that filters the response of a REST API and returns only defined properties.
It should work generically, i.e. independent of specific entities. Neither their properties nor the exact depth (e.g. with any number of relations) should be necessarily known.
Example
An author has any number of articles with any number of comments.
[
{
"name": "John Doe",
"email": "john#doe.com",
"articles": [
{
"title": "Lalilu 1",
"text:": "la li lu",
"comments": [
{
"author": "Bendthatdict Cumberstone",
"text": "Great article!"
},
{
"author": "Bendthatdict Cumberstone",
"text": "Great article!"
}
]
},
{
"title": "Lalilu 1",
"text:": "la li lu",
"comments": [
{
"author": "Bendthatdict Cumberstone",
"text": "Great article!"
},
{
"author": "Bendthatdict Cumberstone",
"text": "Great article!"
}
]
}
]
},
{
"name": "Jane Doe",
"email": "jane#doe.com",
"articles": [
{
"title": "Lalilu 1",
"text:": "la li lu",
"comments": [
{
"author": "Bendthatdict Cumberstone",
"text": "Great article!"
},
{
"author": "Bendthatdict Cumberstone",
"text": "Great article!"
}
]
},
{
"title": "Lalilu 1",
"text:": "la li lu",
"comments": [
{
"author": "Bendthatdict Cumberstone",
"text": "Great article!"
},
{
"author": "Bendthatdict Cumberstone",
"text": "Great article!"
}
]
}
]
}
]
Now I want to specify that it should return everything except the "text" of each article and the "author" of each comment.
Syntax could look like this with glob notation:
select("*,!articles.text,!articles.comments.author")
Approach
For objects and nested objects it is quite simple, e.g. with pick() and omit() of "lodash", but I fail when arrays step into the game.
I did some research and came across packages such as json-mask, node-glob or glob-object but none of them exactly met my needs and I was not able to combine them for success.
Question
What is the most efficient way to generically filter an arbitrarily nested JSON with any number of further objects / arrays?
Also, how could the TypeScripts type system be used to advantage?
I would be very grateful for general coding approaches or even tips for a package that can already do this!
In short I would break this up into functions. You could create helpers that do more or less what you want with a string/filter as you show however I'd work it in reverse. Get a nice way to iterate so any post processing can be done, then build your helpers as you wish against that. Here's what I mean:
Example
export interface IComment {
author: string;
text: string;
}
export interface IArticle {
title: string;
text: string;
comments: IComment[];
}
export interface IComposer {
name: string,
email: string,
articles: IArticle[];
}
// Remove items from list for brevity sake...
const authorList = [
{
"name": "John Doe",
"email": "john#doe.com",
"articles": [
{
"title": "Lalilu 1",
"text": "la li lu",
"comments": [
{
"author": "Bendthatdict Cumberstone",
"text": "Great article!"
}
]
}
]
}
] as IComposer[];
/**
* Accepts JSON string or array of type.
*
* #param arr a JSON string containing array of type or array of type.
*/
export function selectFrom<T extends Record<string, any>>(arr: string | T[]) {
// If you want to use this route I would suggest
// also a function to validate that the JSON is
// shaped correctly.
if (typeof arr === 'string')
arr = JSON.parse(arr);
const collection = arr as T[];
const api = {
filters: [],
register,
run
};
/**
* Register a search op.
* #param fn function returning whether or not to filter the result.
*/
function register(fn: (obj: T) => Partial<T>) {
if (typeof fn === 'function')
api.filters.push(fn);
return api;
}
/**
* Run registered ops and filter results.
*/
function run() {
return collection.reduce((results, obj) => {
let result = obj;
// Don't use reducer here as you can't break
// and would unnecessarily loop through filters
// that have no need to run, use for of instead.
for (const filter of api.filters) {
// if we set the result to null
// don't continue to run filters.
if (!result) break;
// Pipe in the previous result, we start with
// original object but it's shape could change
// so we keep iterating with the previous result.
const filtered = filter(result);
// update the result.
if (filtered)
result = filtered;
}
if (result)
results.push(result);
return results;
// If changing the object you're going to
// end up with partials of the original
// shape or interface.
}, [] as Partial<T>[]);
}
return api;
}
Usage
By making this function based at the core you have a lot more flexibility. From there you could make a simple helper that maps your Glob or SQL like string to the pre-defined filter functions. Let me know if you have further questions.
const filtered =
selectFrom(authorList)
.register((composer) => {
composer.articles = composer.articles.map(article => {
const { text, ...filteredArticle } = article;
filteredArticle.comments = filteredArticle.comments.map(comment => {
const { author, ...filteredComment } = comment;
return filteredComment as typeof comment;
});
// Note setting to type of IArticle here so typescript
// doesn't complain, this is because you are removing props
// above so the shape changes so you may want to consider
// setting the props you plan to strip as optional or make
// everything a partial etc. I'll leave that to you to decide.
return filteredArticle as typeof article;
});
return composer;
})
.run();
What's Next
From here to get where you want it's about string parsing. Keep in mind Lodash does support gets down into nested values in an array. You can see this here in the docs.
Given that you could leverage Lodash using both _.get _.omit... etc along with a little parsing using dot notation.
Done this very thing with permissions. As such I feel strongly you need to start with a simple api to process then from there make your map from either Glob like or SQL string to those helpers.

Unable to read objectValue data using "level" query

According to the smartsheet API Docs, I should be able to use "level" parameter in my options to get a complex object for Multi-Contact columns.
Unfortunately all I'm getting in return is value and displayValue.
Am I doing something wrong here?
var options = {
id: SHEET_ID, //Id of sheet
queryParameters = {
include: ["objectValue"],
level: 1
}
}
ss.sheets.getSheet(options)
.then(function (results) {
console.log(results.rows[args[0]].cells[6])
})
The above code returns:
{ columnId: 8746190272522116, displayValue: 'John Smith, Danny Doe' }
I've verified (using Postman) that Smartsheet API does indeed support the scenario you've described. i.e., if I submit this Get Sheet request:
https://api.smartsheet.com/2.0/sheets/5831916227192708?include=objectValue&level=1
...then the response does include the complex object for a multi-contact cell in my sheet:
{
"id": 5831916227192708,
...
"rows": [
{
"id": 5942480978372484,
...
"cells": [
{
"columnId": 3992195570132868,
"objectValue": {
"objectType": "MULTI_CONTACT",
"values": [
{
"objectType": "CONTACT",
"email": "john_doe#test.com",
"name": "John Doe"
},
{
"objectType": "CONTACT",
"email": "jane_doe#test.com",
"name": "Jane Doe"
}
]
},
"displayValue": "John Doe, Jane Doe"
},
...
]
},
...
]
}
However, it looks like the Smartsheet JavaScript SDK doesn't yet support this scenario.
It's not unusual for SDK updates to lag a bit behind the release of new API features. You might consider logging an issue in the JavaScript SDK repo to request that support for this scenario be added -- or better yet, submit a PR to that repo that adds support for this scenario. In the meantime, you'll need to implement this functionality yourself within your integration (i.e., since you can't rely on the out-of-the-box SDK functionality to provide it at this time).
You just need to remove the array notations from your options definition:
var options = {
id: SHEET_ID, //Id of sheet
queryParameters = {
include: "objectValue",
level: 1
}
}
ss.sheets.getSheet(options)
.then(function (results) {
console.log(results.rows[args[0]].cells[6])
})

Parsing input based on JSON schema

We're building a frontend project for a web app that communicates with a backend written by another team. Some of the developers work on both projects, and have better understanding of changes to the backend and response fields coming back.
Recently we had portions of frontend break as they made changes in parts of the app based on changes to the backend without updating the logic in all places. To mitigate this I want to put in place a concept of a mask/template that all response data would be curated through. That way the rest of the members on the team who're not as familiar with the backend can notice/address these bugs.
To do so, I'm considering using JSON Schema. Instead of simply validating, however, I want to parse the backend data through it (removing the fields not present in the schema). This way the developer making changes in the frontend in response to a backend change would also need to update this template, therefore triggering a test failure until all logic using this schema is updated (not just the logic he touched). I'm playing with https://www.npmjs.com/package/jsonschema, but it doesn't seem to have a way to remove excess fields, just test for them.
Within JSON Schema, I can also set additionalProperties flag. However, it has 2 problems with it:
It doesn't cause the validator to remove the fields, it simply dumps them to error array
It needs to be set individually at each nested level, therefore I need to traverse the entire JSON structure, at which point I basically end up writing my own parser/validator.
Perhaps validator is not the right tool for this, but that's all I'm finding when searching for JSON schema parsers. Can someone guide me in the right direction so that I don't reinvent the wheel? It sounds like this functionality is very similar to what a validator already does and I would rather do this processing in the same pass.
Found a validator that does what I want: https://github.com/acornejo/jjv. It has removalAdditional flag that I can set, here is a quick test I did:
var jjv = require('jjv')();
var addressSchema = {
"id": "address",
"type": "object",
"properties": {
"lines": {
"type": "array",
"items": {"type": "string"}
},
"zip": {"type": "string"},
"city": {"type": "string"},
"country": {"type": "string"}
},
"required": ["country"]
};
var schema = {
"id": "person",
"type": "object",
"properties": {
"name": {"type": "string"},
"address": {"$ref": "address"},
"votes": {"type": "integer", "minimum": 1}
}
};
var p = {
"name": "Barack Obama",
"address": {
"lines": [ "1600 Pennsylvania Avenue Northwest" ],
"zip": "DC 20500",
"city": "Washington",
"foobar": "baz",
"country": "USA"
},
"a": {
"b": 1,
"c": 2
},
"votes": "lots",
"stuff": "yes"
};
jjv.addSchema('address', addressSchema);
jjv.addSchema('schema', schema);
jjv.defaultOptions.checkRequired = true;
jjv.defaultOptions.removeAdditional = true;
console.log(jjv.validate('schema', p));
console.log(p);
And a response:
{ validation: { votes: { type: 'integer' } } }
{ name: 'Barack Obama',
address:
{ lines: [ '1600 Pennsylvania Avenue Northwest' ],
zip: 'DC 20500',
city: 'Washington',
country: 'USA' },
votes: 'lots' }

DocumentDB: How to filter document on array within array?

Let's say I have the following document:
{
"Id": "1",
"Properties": [
{
"Name": "Name1",
"PropertyTypes": [
"Type1"
]
},
{
"Name": "Name2",
"PropertyTypes": [
"Type1",
"Type2",
"Type3"
]
}
]
}
When I use the following SQL:
SELECT c.Id FROM c
JOIN p in c.Properties
WHERE ARRAY_CONTAINS(p.PropertyTypes,"Type1")
I get as return:
[
{
"Id": "1"
},
{
"Id": "1"
}
]
How do I change my query so that it only returns distinct documents?
As far as I know, Distinct hasn't supported by Azure Cosmos DB yet.
It seems that there is no way to remove the repeat data in the query SQL level.
You could handle with your query result set in the loop locally.
However, if your result data is large,I suggest you using a stored procedure to handle with result data in Azure Cosmos DB to release the pressure on your local server.
You could refer to the official tutorial about SP.

Categories

Resources