Given the following code:
type CustomTypeA = {
x: number,
y: number,
}
type CustomTypeB = CustomTypeA & {
z: number,
}
type CustomType = CustomTypeA | CustomTypeB
export const myFunction = (a: CustomType | number): number | void => {
if (typeof a === 'number') {
return a; // THIS WORKS WELL, it's considered number after the check, which is correct
} else if (typeof a === CustomType) {
const newX = a.x // ERR: Flow: Cannot get `a.x` because property `x` is missing in `Number`
const newZ = a.z // SAME ERR, Flow: Cannot get `a.z` because property `z` is missing in `Number`.
}
}
Also, the typeof a === CustomType check is highlighted as an error as well:
Flow: Cannot reference type `CustomType` from a value position.
This however doesn't happen for the typeof a === 'number' one.
It's like the check against the custom object type I created is not valid/recognized.
Can someone explain why and possibly how to escape this?
Thanks.
Flow custom types are not values, they do not exist, they vanish after transpilation, therefore you can not use them with a JS operator like typeof because it requires a value. So when you do typeof a === CustomType it will fail, because after compilation you will end with typeof a === , CustomType is just stripped out and you end with invalid JS.
This seems to be a flow limitation to be honest.
There is the %checks operator which allows you to build type guard functions.
One may think you can use this feature to build a type refinement for your custom types with a function that has the proper logic, but nothing on its documentation suggest that it can be used to refine custom types.
It also requires the body of the guard function to be very simple so flow can understand what do you mean. Some type guard function examples may look like this (from flow docs):
function truthy(a, b): boolean %checks {
return !!a && !!b;
}
function isString(y): %checks {
return typeof y === "string";
}
function isNumber(y): %checks {
return typeof y === "number";
}
However when you try a more complex check, for example checking that something is an object, but it is not an array or a date, flow fails to understand your intention and the predicate function will not work. Something like this:
function isObject(obj: mixed): boolean %checks {
return Object.prototype.toString.call(obj) === '[object Object]'
}
Will fail because flow doesn't understand that as a type refinement for object. For that particular case, there is a workaround suggested on a github issue that consist on declaring the function on the type level asserting that it checks for the object type:
declare function isObject(obj: mixed): boolean %checks(obj instanceof Object)
But you can not use that either for your particular case, because you can not do instanceof on a custom type, because it is not a class.
So your options are either go verbose and check all the expected properties are present on a type check, like this:
if (typeof a.x === 'number' && typeof a.y === 'number' && typeof a.z === 'number') {
const {x: ax, y: ay, z: az} = a
// now you can safely use the extracted variables
Note you need to extract the props from the object because, any time you call a function flow will invalidate your type refinement and the next line that access a.x will fail.
You can declare your point as a Class, and use the type system checking for instances of that class.
Or you build a validation function that returns either the correct type or null, so flow can understand the type has been refined:
function isCustomType (p: mixed): CustomType | null {
const validated = ((p:any):CustomType)
if (typeof validated.x === 'number' && typeof validated.y === 'number') return validated
return null
}
const validA = isCustomType(a)
if (validA) {
const {x: ax, y: ay} = validA
// do stuff
This has the disadvantage that you need to allocate extra variables just to satisfy the type system, but I think that is a minor problem.
Also, it will not allow flow to validate the isCustomType function for you, because we are doing type casts to basically cheat flow. But given the surface is small and the objective is very focused it should be ok to be able to keep it manually correct.
I have the following mapping in a JSON file:
"mapping": [
{
"true": "some string"
},
{
"false": "some other string"
}
]
In such a mapping, the keys are always strings. That's why I need the keys to be strings, even though, in this case, they are string representations of the boolean values.
When I load this JSON file with require(myfile.json), the keys are somehow converted to actual boolean values.
Is that a bug in require()? Is there a workaround?
That is not a require thing. Javascript allows to use any item as object keys, but then under the hood calls toString when saving or retrieving the item. For instance:
const a = {};
a[true] = 'value from true';
a[{somekey: 'somevalue'}] = 'value from object';
Object.getKeys(a); // ["true", "[object Object]"]
So your problem is related to the way javascript handles object keys. There is no way to discriminate between true and 'true' when storing a value in an object key:
a = {};
a[true] = 'from plain true';
a["true"] = 'from stringified true';
a[true]; // "from stringified true". See how the second assignation messes with the first, even if we are using the boolean true value as key.
For reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects
Here is my scenario, i am parsing via javascript a webpage and then post the result to an restApi to store the json in a db. The code works fine as long as all fields i defined in my script are send. Problem is over time they website might change names for fields and that would cause my code to crash.
Originally i used code like this
const mySchool = new mls.School();
mySchool.highSchoolDistrict = data["HIGH SCHOOL DISTRICT"].trim();
mySchool.elementary = data.ELEMENTARY.trim();
mySchool.elementaryOther = data["ELEMENTARY OTHER"].trim();
mySchool.middleJrHigh = data["MIDDLE/JR HIGH"].trim();
mySchool.middleJrHighOther = data["MIDDLE/JR HIGH OTHER"].trim();
mySchool.highSchool = data["HIGH SCHOOL"].trim();
mySchool.highSchoolOther = data["HIGH SCHOOL OTHER"].trim();
newListing.school = mySchool;
but when the element does not exist it complains about that it can not use trim of undefined. So to fix this i came up with this
if (data["PATIO/PORCH"]) {
newExterior.patioPorch = data["PATIO/PORCH"].trim();
}
this works but i am wondering if there is a more global approach then to go and check each field if it is defined ?
You could leverage a sort of helper function to check first if the item is undefined, and if not, return a trim()-ed version of the string.
var data = Array();
data["HIGH SCHOOL DISTRICT"] = " 123 ";
function trimString(inputStr) {
return (inputStr != undefined && typeof inputStr == "string") ? inputStr.trim() : undefined;
}
console.log(trimString(data["HIGH SCHOOL DISTRICT"]));
console.log(trimString(data["ELEMENTARY OTHER"]));
Take this object:
x = {
"key1": "xxx",
"key2": function(){return this.key1}
}
If I do this:
y = JSON.parse( JSON.stringify(x) );
Then y will return { "key1": "xxx" }. Is there anything one could do to transfer functions via stringify? Creating an object with attached functions is possible with the "ye goode olde eval()", but whats with packing it?
json-stringify-function is a similar post to this one.
A snippet discovered via that post may be useful to anyone stumbling across this answer. It works by making use of the replacer parameter in JSON.stringify and the reviver parameter in JSON.parse.
More specifically, when a value happens to be of type function, .toString() is called on it via the replacer. When it comes time to parse, eval() is performed via the reviver when a function is present in string form.
var JSONfn;
if (!JSONfn) {
JSONfn = {};
}
(function () {
JSONfn.stringify = function(obj) {
return JSON.stringify(obj,function(key, value){
return (typeof value === 'function' ) ? value.toString() : value;
});
}
JSONfn.parse = function(str) {
return JSON.parse(str,function(key, value){
if(typeof value != 'string') return value;
return ( value.substring(0,8) == 'function') ? eval('('+value+')') : value;
});
}
}());
Code Snippet taken from Vadim Kiryukhin's JSONfn.js or see documentation at Home Page
I've had a similar requirement lately. To be clear, the output looks like JSON but in fact is just javascript.
JSON.stringify works well in most cases, but "fails" with functions.
I got it working with a few tricks:
make use of replacer (2nd parameter of JSON.stringify())
use func.toString() to get the JS code for a function
remember which functions have been stringified and replace them directly in the result
And here's how it looks like:
// our source data
const source = {
"aaa": 123,
"bbb": function (c) {
// do something
return c + 1;
}
};
// keep a list of serialized functions
const functions = [];
// json replacer - returns a placeholder for functions
const jsonReplacer = function (key, val) {
if (typeof val === 'function') {
functions.push(val.toString());
return "{func_" + (functions.length - 1) + "}";
}
return val;
};
// regex replacer - replaces placeholders with functions
const funcReplacer = function (match, id) {
return functions[id];
};
const result = JSON
.stringify(source, jsonReplacer) // generate json with placeholders
.replace(/"\{func_(\d+)\}"/g, funcReplacer); // replace placeholders with functions
// show the result
document.body.innerText = result;
body { white-space: pre-wrap; font-family: monospace; }
Important: Be careful about the placeholder format - make sure it's not too generic. If you change it, also change the regex as applicable.
Technically this is not JSON, I can also hardly imagine why would you want to do this, but try the following hack:
x.key2 = x.key2.toString();
JSON.stringify(x) //"{"key1":"xxx","key2":"function (){return this.key1}"}"
Of course the first line can be automated by iterating recursively over the object. Reverse operation is harder - function is only a string, eval will work, but you have to guess whether a given key contains a stringified function code or not.
You can't pack functions since the data they close over is not visible to any serializer.
Even Mozilla's uneval cannot pack closures properly.
Your best bet, is to use a reviver and a replacer.
https://yuilibrary.com/yui/docs/json/json-freeze-thaw.html
The reviver function passed to JSON.parse is applied to all key:value pairs in the raw parsed object from the deepest keys to the highest level. In our case, this means that the name and discovered properties will be passed through the reviver, and then the object containing those keys will be passed through.
This is what I did https://gist.github.com/Lepozepo/3275d686bc56e4fb5d11d27ef330a8ed
function stringifyWithFunctions(object) {
return JSON.stringify(object, (key, val) => {
if (typeof val === 'function') {
return `(${val})`; // make it a string, surround it by parenthesis to ensure we can revive it as an anonymous function
}
return val;
});
};
function parseWithFunctions(obj) {
return JSON.parse(obj, (k, v) => {
if (typeof v === 'string' && v.indexOf('function') >= 0) {
return eval(v);
}
return v;
});
};
The naughty but effective way would be to simply:
Function.prototype.toJSON = function() { return this.toString(); }
Though your real problem (aside from modifying the prototype of Function) would be deserialization without the use of eval.
I have come up with this solution which will take care of conversion of functions (no eval). All you have to do is put this code before you use JSON methods. Usage is exactly the same but right now it takes only one param value to convert to a JSON string, so if you pass remaning replacer and space params, they will be ignored.
void function () {
window.JSON = Object.create(JSON)
JSON.stringify = function (obj) {
return JSON.__proto__.stringify(obj, function (key, value) {
if (typeof value === 'function') {
return value.toString()
}
return value
})
}
JSON.parse = function (obj) {
return JSON.__proto__.parse(obj, function (key, value) {
if (typeof value === 'string' && value.slice(0, 8) == 'function') {
return Function('return ' + value)()
}
return value
})
}
}()
// YOUR CODE GOES BELOW HERE
x = {
"key1": "xxx",
"key2": function(){return this.key1}
}
const y = JSON.parse(JSON.stringify(x))
console.log(y.key2())
It is entirely possible to create functions from string without eval()
var obj = {a:function(a,b){
return a+b;
}};
var serialized = JSON.stringify(obj, function(k,v){
//special treatment for function types
if(typeof v === "function")
return v.toString();//we save the function as string
return v;
});
/*output:
"{"a":"function (a,b){\n return a+b;\n }"}"
*/
now some magic to turn string into function with this function
var compileFunction = function(str){
//find parameters
var pstart = str.indexOf('('), pend = str.indexOf(')');
var params = str.substring(pstart+1, pend);
params = params.trim();
//find function body
var bstart = str.indexOf('{'), bend = str.lastIndexOf('}');
var str = str.substring(bstart+1, bend);
return Function(params, str);
}
now use JSON.parse with reviver
var revivedObj = JSON.parse(serialized, function(k,v){
// there is probably a better way to determ if a value is a function string
if(typeof v === "string" && v.indexOf("function") !== -1)
return compileFunction(v);
return v;
});
//output:
revivedObj.a
function anonymous(a,b
/**/) {
return a+b;
}
revivedObj.a(1,2)
3
To my knowledge, there are no serialization libraries that persist functions - in any language. Serialization is what one does to preserve data. Compilation is what one does to preserve functions.
It seems that people landing here are dealing with structures that would be valid JSON if not for the fact that they contain functions. So how do we handle stringifying these structures?
I ran into the problem while writing a script to modify RequireJS configurations. This is how I did it. First, there's a bit of code earlier that makes sure that the placeholder used internally (">>>F<<<") does not show up as a value in the RequireJS configuration. Very unlikely to happen but better safe than sorry. The input configuration is read as a JavaScript Object, which may contain arrays, atomic values, other Objects and functions. It would be straightforwardly stringifiable as JSON if functions were not present. This configuration is the config object in the code that follows:
// Holds functions we encounter.
var functions = [];
var placeholder = ">>>F<<<";
// This handler just records a function object in `functions` and returns the
// placeholder as the value to insert into the JSON structure.
function handler(key, value) {
if (value instanceof Function) {
functions.push(value);
return placeholder;
}
return value;
}
// We stringify, using our custom handler.
var pre = JSON.stringify(config, handler, 4);
// Then we replace the placeholders in order they were encountered, with
// the functions we've recorded.
var post = pre.replace(new RegExp('"' + placeholder + '"', 'g'),
functions.shift.bind(functions));
The post variable contains the final value. This code relies on the fact that the order in which handler is called is the same as the order of the various pieces of data in the final JSON. I've checked the ECMAScript 5th edition, which defines the stringification algorithm and cannot find a case where there would be an ordering problem. If this algorithm were to change in a future edition the fix would be to use unique placholders for function and use these to refer back to the functions which would be stored in an associative array mapping unique placeholders to functions.
I don't understand why I am unable to check null? Here is my code,
Tag.find({name : 'foo'}).exec(function(tag){
console.log('tag : ' + tag); // It's a null
console.log('type : ' + typeof tag); // It's a object
if(!tag){console.log('true');}
else {console.log('false');}
});
I try a lot of way such as,
if(tag == null){console.log('true');}
else {console.log('false');}
In every way, I always got else condition though I got null from tag.
In JavaScript, typeof null === 'object' has always been the case. It's just one of those gotchas you have to remember. If you want to check for strictly null, do a strict equals check: tag === null. tag == null will return true if tag is null or if it is undefined.