I have a set of get functions in JS such as:
get UserName() {
return this.userModel.Name;
}
I want the ability to check if the function exist before I call it.
I tried:
if (this.UserName == 'function')...
but it's always false, since userModel.name is a string, typeof UserName returns 'string' type and not a 'function'.
any idea how I can accomplish this ?
One simple way to check that UserName exists (without calling the getter) is to use in:
if ('UserName' in this) {
// this.UserName is defined
}
If you need a stronger check where you directly access the getter function, use Object.getOwnPropertyDescriptor:
var userNameDesc = Object.getOwnPropertyDescriptor(this, 'UserName');
if (userNameDesc && userNameDesc.get) {
// this.UserName is definitely a getter and is defined
}
You can use Object.getOwnPropertyDescriptor() which returns basically the same data structure that is fed to Object.defineProperty() like this:
let descriptor = Object.getOwnPropertyDescriptor(this, "UserName");
if (descriptor && typeof descriptor.get === "function") {
// this.UserName is a getter function
}
Or, if you want more granular info, you can do this:
let descriptor = Object.getOwnPropertyDescriptor(this, "UserName");
if (!descriptor) {
// property doesn't exist
} else if (typeof descriptor.get === "function") {
// this.UserName is a getter function
} else if (typeof descriptor.value === "function") {
// property directly contains a function (that is just a regular function)
} else {
// property exists, but it does not have a getter function and
// is not a regular function
}
You can also test many other properties of the descriptor such as value, set, writable, configurable, enumerable as described here on MDN.
Related
I want to make sure the JSON I load to my typescript classes is valid.
Also, my classes have some logic, so I want want them to remain classes and not become interfaces.
I also need type checks and required/not required checks.
Right now I just load an object to my constructor and manually check each field.
Is there a way to do it by using the type information from typescript?
I tried looking at the generated javascript files but it's just javascript and the type information is already gone there.
Perhaps there's a way to use it in compile time? at that time, typescript knows the type.
I found a few libraries that do it, but none of them deals with the type of the property as well as required or not.
class MyObject {
constructor(json: any) {
if (typeof json.id == 'number') {
this.id = json.id;
} else {
throw new Error('ID is required');
}
if (json.name != undefined) {
if (typeof json.name == 'string') {
this.name = json.name;
} else {
throw new Error('Name exists, but it is not a string!');
}
}
}
id: number; // required
name?: string; // optional
}
try {
let m1: MyObject = {
id: 123
}; // works but m1 is not a class
let isMyObject = m1 instanceof MyObject ? '' : 'not ';
console.log(`m1 is ${isMyObject}instance of MyObject`);
let m2 = new MyObject({
"id": 123
});
let m3 = new MyObject({
"id": 123,
"name": "Mickey"
});
let m4 = new MyObject({
// this will throw an error
});
console.log('OK!');
} catch (error) {
console.log("Error: " + error.message);
}
We are using js-joda LocalDate to represent various dates in our model and are storing those dates in sessionStorage. Is there a generalized preferred way of storing those dates so that they can serialize/deserialize without adding special code to each object that contains them?
We have been using the standard JSON.stringify / JSON.parse to do this, but since LocalDate converts to an ISO string when stringified, we lose its LocalDate type when we parse it back.
As demonstrated here
Here's the summary:
const myObj = { a: "thing", d: LocalDate.parse('2019-01-20') };
const stringified = JSON.stringify(myObj);
const parsed = JSON.parse(stringified);
// this fails because d is no longer a LocalDate
console.log(parsed.d.year());
Our workaround now is that we have custom deserializers for any class that contains a LocalDate, but it seems a little kludgy.
Seeking a cleaner solution for this. Perhaps we could make a generalized serializer for LocalDate that outputs the same thing as the %o modifier in console.log?
mydate -> serialize -> "LocalDate { _year: 2019, _month: 1, _day: 20}"
Before we do that, I'm looking to see if this has already been done cleanly or if I'm just missing something obvious.
Answering my own question.
I'm surprised it hasn't come up, but the solution is right there in the definitions of JSON.stringify and JSON.parse.
This post pointed me to the solution when I needed to do the same thing with a Map.
JSON.parse(text[, reviver])
JSON.stringify(value[, replacer[, space]])
I needed to add replacers and revivers to do the custom serialization:
function myReviver(key: string, value: any) {
if (value === undefined) return undefined;
if (value === null) return null;
if (typeof value === 'object') {
switch (value.dataType) {
case 'LocalDate':
return LocalDate.parse(value.value);
case 'LocalTime':
return LocalTime.parse(value.value);
case 'LocalDateTime':
return LocalDateTime.parse(value.value);
case 'Period':
return Period.parse(value.value);
}
}
return value;
}
function myReplacer(key, value) {
const originalObject = this[key];
if (originalObject instanceof LocalDate) {
return {
dataType: 'LocalDate',
value: originalObject.toJSON()
};
} else if (originalObject instanceof LocalTime) {
return {
dataType: 'LocalTime',
value: originalObject.toJSON()
};
} else if (originalObject instanceof LocalDateTime) {
return {
dataType: 'LocalDateTime',
value: originalObject.toJSON()
};
} else if (originalObject instanceof Period) {
return {
dataType: 'Period',
value: originalObject.toJSON()
};
} else {
return value;
}
}
Whenever I call stringify or parse, I add the above functions as their replacer/revivers.
JSON.stringify(mystuff, myReplacer);
JSON.parse(mystuff, myReviver);
Is it possible to specify that a field in GraphQL should be a blackbox, similar to how Flow has an "any" type? I have a field in my schema that should be able to accept any arbitrary value, which could be a String, Boolean, Object, Array, etc.
I've come up with a middle-ground solution. Rather than trying to push this complexity onto GraphQL, I'm opting to just use the String type and JSON.stringifying my data before setting it on the field. So everything gets stringified, and later in my application when I need to consume this field, I JSON.parse the result to get back the desired object/array/boolean/ etc.
#mpen's answer is great, but I opted for a more compact solution:
const { GraphQLScalarType } = require('graphql')
const { Kind } = require('graphql/language')
const ObjectScalarType = new GraphQLScalarType({
name: 'Object',
description: 'Arbitrary object',
parseValue: (value) => {
return typeof value === 'object' ? value
: typeof value === 'string' ? JSON.parse(value)
: null
},
serialize: (value) => {
return typeof value === 'object' ? value
: typeof value === 'string' ? JSON.parse(value)
: null
},
parseLiteral: (ast) => {
switch (ast.kind) {
case Kind.STRING: return JSON.parse(ast.value)
case Kind.OBJECT: throw new Error(`Not sure what to do with OBJECT for ObjectScalarType`)
default: return null
}
}
})
Then my resolvers looks like:
{
Object: ObjectScalarType,
RootQuery: ...
RootMutation: ...
}
And my .gql looks like:
scalar Object
type Foo {
id: ID!
values: Object!
}
Yes. Just create a new GraphQLScalarType that allows anything.
Here's one I wrote that allows objects. You can extend it a bit to allow more root types.
import {GraphQLScalarType} from 'graphql';
import {Kind} from 'graphql/language';
import {log} from '../debug';
import Json5 from 'json5';
export default new GraphQLScalarType({
name: "Object",
description: "Represents an arbitrary object.",
parseValue: toObject,
serialize: toObject,
parseLiteral(ast) {
switch(ast.kind) {
case Kind.STRING:
return ast.value.charAt(0) === '{' ? Json5.parse(ast.value) : null;
case Kind.OBJECT:
return parseObject(ast);
}
return null;
}
});
function toObject(value) {
if(typeof value === 'object') {
return value;
}
if(typeof value === 'string' && value.charAt(0) === '{') {
return Json5.parse(value);
}
return null;
}
function parseObject(ast) {
const value = Object.create(null);
ast.fields.forEach((field) => {
value[field.name.value] = parseAst(field.value);
});
return value;
}
function parseAst(ast) {
switch (ast.kind) {
case Kind.STRING:
case Kind.BOOLEAN:
return ast.value;
case Kind.INT:
case Kind.FLOAT:
return parseFloat(ast.value);
case Kind.OBJECT:
return parseObject(ast);
case Kind.LIST:
return ast.values.map(parseAst);
default:
return null;
}
}
For most use cases, you can use a JSON scalar type to achieve this sort of functionality. There's a number of existing libraries you can just import rather than writing your own scalar -- for example, graphql-type-json.
If you need a more fine-tuned approach, than you'll want to write your own scalar type. Here's a simple example that you can start with:
const { GraphQLScalarType, Kind } = require('graphql')
const Anything = new GraphQLScalarType({
name: 'Anything',
description: 'Any value.',
parseValue: (value) => value,
parseLiteral,
serialize: (value) => value,
})
function parseLiteral (ast) {
switch (ast.kind) {
case Kind.BOOLEAN:
case Kind.STRING:
return ast.value
case Kind.INT:
case Kind.FLOAT:
return Number(ast.value)
case Kind.LIST:
return ast.values.map(parseLiteral)
case Kind.OBJECT:
return ast.fields.reduce((accumulator, field) => {
accumulator[field.name.value] = parseLiteral(field.value)
return accumulator
}, {})
case Kind.NULL:
return null
default:
throw new Error(`Unexpected kind in parseLiteral: ${ast.kind}`)
}
}
Note that scalars are used both as outputs (when returned in your response) and as inputs (when used as values for field arguments). The serialize method tells GraphQL how to serialize a value returned in a resolver into the data that's returned in the response. The parseLiteral method tells GraphQL what to do with a literal value that's passed to an argument (like "foo", or 4.2 or [12, 20]). The parseValue method tells GraphQL what to do with the value of a variable that's passed to an argument.
For parseValue and serialize we can just return the value we're given. Because parseLiteral is given an AST node object representing the literal value, we have to do a little bit of work to convert it into the appropriate format.
You can take the above scalar and customize it to your needs by adding validation logic as needed. In any of the three methods, you can throw an error to indicate an invalid value. For example, if we want to allow most values but don't want to serialize functions, we can do something like:
if (typeof value == 'function') {
throw new TypeError('Cannot serialize a function!')
}
return value
Using the above scalar in your schema is simple. If you're using vanilla GraphQL.js, then use it just like you would any of the other scalar types (GraphQLString, GraphQLInt, etc.) If you're using Apollo, you'll need to include the scalar in your resolver map as well as in your SDL:
const resolvers = {
...
// The property name here must match the name you specified in the constructor
Anything,
}
const typeDefs = `
# NOTE: The name here must match the name you specified in the constructor
scalar Anything
# the rest of your schema
`
Just send a stringified value via GraphQL and parse it on the other side, e.g. use this wrapper class.
export class Dynamic {
#Field(type => String)
private value: string;
getValue(): any {
return JSON.parse(this.value);
}
setValue(value: any) {
this.value = JSON.stringify(value);
}
}
For similar problem I've created schema like this:
"""`MetadataEntry` model"""
type MetadataEntry {
"""Key of the entry"""
key: String!
"""Value of the entry"""
value: String!
}
"""Object with metadata"""
type MyObjectWithMetadata {
"""
... rest of my object fields
"""
"""
Key-value entries that you can attach to an object. This can be useful for
storing additional information about the object in a structured format
"""
metadata: [MetadataEntry!]!
"""Returns value of `MetadataEntry` for given key if it exists"""
metadataValue(
"""`MetadataEntry` key"""
key: String!
): String
}
And my queries can look like this:
query {
listMyObjects {
# fetch meta values by key
meta1Value: metadataValue(key: "meta1")
meta2Value: metadataValue(key: "meta2")
# ... or list them all
metadata {
key
value
}
}
}
Angular.js has a handy built-in filter, json, which displays JavaScript objects as nicely formatted JSON.
However, it seems to filter out object properties that begin with $ by default:
Template:
<pre>{{ {'name':'value', 'special':'yes', '$reallyspecial':'Er...'} | json }}</pre>
Displayed:
{
"name": "value",
"special": "yes"
}
http://plnkr.co/edit/oem4HJ9utZMYGVbPkT6N?p=preview
Can I make properties beginning with $ be displayed like other properties?
Basically you can't. It is "hard-coded" into the filter's behaviour.
Nonetheless, it is quite easy to build a custom JSON filter that behaves identically with the Angular's one but not filtering out properties starting with '$'.
(Scroll further down for sample code and a short demo.)
If you take a look at the 1.2.15 version source code, you will find out that the json filter is defined like this:
function jsonFilter() {
return function(object) {
return toJson(object, true);
};
}
So, it uses the toJson() function (the second parameter (true) means: format my JSON nicely).
So, our next stop is the toJson() function, that looks like this:
function toJson(obj, pretty) {
if (typeof obj === 'undefined') return undefined;
return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null);
}
This function makes use of the "native" JSON.stringify() function, passing a custom replacer function (toJsonReplacer).
The toJsonReplacer() function handles some special cases: It checks if the key starts with $ and ignores it if it does (this is what we want to change) and it checks if the value is either a Window, a Document or a Scope object (in which case it converts it to a descriptive string in order to avoid "Converting circular structure to JSON" errors).
function toJsonReplacer(key, value) {
var val = value;
if (typeof key === 'string' && key.charAt(0) === '$') {
val = undefined;
} else if (isWindow(value)) {
val = '$WINDOW';
} else if (value && document === value) {
val = '$DOCUMENT';
} else if (isScope(value)) {
val = '$SCOPE';
}
return val;
}
For the sake of completeness, the two functions that check for Window and Scope look like this:
function isWindow(obj) {
return obj && obj.document && obj.location && obj.alert && obj.setInterval;
}
function isScope(obj) {
return obj && obj.$evalAsync && obj.$watch;
}
Finally, all we need to do is to create a custom filter that uses the exact same code, with the sole difference that our toJsonReplacer() won't filter out properties starting with $.
app.filter('customJson', function () {
function isWindow(obj) {
return obj &&
obj.document &&
obj.location &&
obj.alert &&
obj.setInterval;
}
function isScope(obj) {
return obj &&
obj.$evalAsync &&
obj.$watch;
}
function toJsonReplacer(key, value) {
var val = value;
if (isWindow(value)) {
val = '$WINDOW';
} else if (value && (document === value)) {
val = '$DOCUMENT';
} else if (isScope(value)) {
val = '$SCOPE';
}
return val;
}
function toJson(obj, pretty) {
if (typeof obj === 'undefined') { return undefined; }
return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null);
}
return function(object) {
return toJson(object, true);
};
});
See, also, this short demo.
* The downside is that your custom JSON filter will not benefit from further improvement/enhancement of Angular's json filter, so you'll have to re-define your's to incorporate changes. Of course, for such a basic and simple filter like this, one should'nt expect frequent or extensive changes, but that doesn't mean there aren't going to be any.
I'm using a Value Object which can receive an object when it is instantiated, so its default values can be updated directly when a new VO is created, like so:
public class SeatSettingsVO
{
public var globalPosition:Point = new Point(0, 0);
public var dealerChipOffset:Point = new Point(0, 0);
public var chipStackOffset:Point = new Point(0, 0);
public function SeatSettingsVO(obj:Object = null)
{
if (obj)
parseSettings(obj);
}
}
The parseSettings method uses a try/catch block in order to get only the existing properties in the object passed to the constructor (or at least, that would be the intention):
private function parseSettings(obj:Object):void
{
try
{
this.globalPosition = obj.globalPosition;
this.chipStackOffset = obj.chipStackOffset;
this.dealerChipOffset = obj.dealerChipOffset;
}
catch (error:Error)
{
}
}
Now consider this scenario: a new Value Object needs to be created, but with only one of the three properties defined:
new SeatSettingsVO({globalPosition:new Point(300, 277)})
The problem is that if obj does not contain a particular property (e.g. chipStackOffset), instead of maintaining the initial property value (Point(0,0)), the method overwrites it to null.
My guess is that accessing non-existent properties on an Object class instance, does not trigger an error, but rather, null is returned, which in turn causes the default value to be overwritten. Can anyone explain this behavior, and possibly suggest a solution ?
Thank you very much.
A slightly more succinct solution than the others:
this.globalPosition = obj.globalPosition || DEFAULT_GLOBAL_POSITION;
Like in Python, the || operator returns the first operand if that operand evaluates to something besides 0, null, false, NaN, "", or undefined. Otherwise, it returns the second operand as-is:
trace(new Point(3, 3) || "hi"); //(x=3, y=3)
trace(false || "hi"); //hi
trace("hi" || "bye"); //hi
trace(0 || null); //null
trace(NaN || 0); //0
trace("" || undefined); //undefined
trace(undefined || new Point(0.4, 0)); //(x=0.4, y=0)
trace(null || false); //false
As a result, you can use it to check whether a value is defined, use that value if so, and use a default value if not. I'm honestly not sure if it makes your code more or less readable, but it's an option.
Flex Objects have a hasOwnProperty() method that you might find useful. You can use this to check if a dynamic object has a parameter defined, and only pull it if it exists, instead of getting nulls.
if (obj.hasOwnProperty("globalPosition"))
this.globalPosition = obj.globalPosition;
//etc...
In this case, your object is dynamic so you don't get an exception if the property doesn't exist. You do, however, get undefined. undefined evaluates to null, so you can always say:
this.globalPosition = obj.globalPosition ? obj.globalPosition : default;
where default is whatever you want to put there... even this.globalPosition would work if you want to set it back to what it was.
You can also ask if the property exists:
if( "globalPosition" in obj)
private function parseSettings(obj:Object):void
{
try
{
this.globalPosition = obj.globalPosition;
this.chipStackOffset = obj.chipStackOffset;// when error occured here,
// this.chipStackOffset still waiting for a value to set and it sets to null.
// probably dealerChipOffset doesnt change by default value.
this.dealerChipOffset = obj.dealerChipOffset; // this is {0,0} point prob,didnt try it.
}
catch (error:Error)
{
}
}
I would use somthing like below. Hope it helps.
private function parseSettings(obj:Object):void
{
for(var name in obj){
this[name] = obj[name];
}
}