We have two potential values:
const value = `Hello World`;
and
const value = {message: 'Hello World'};
What I'm trying to do is a conditional where
if(is template literal) {
// Some code
} else {
// Some code
}
I've tried using
if(String.raw(value)){
} else {
}
But the object throws a type error. Does anyone have an easy reliable way to detect template literals in javascript?
Edit: Here is some further clarification for those who are asking.
const exampleFunc = (value) => {
// If I pass a string or template literal return as is
if (String.raw({raw: value})) return value;
const css = arr(styles).reduce((acc, obj) => {
// Passing an object handles a more complicated set of operations
}
}
Before I was using
if (String.raw(value)) return value;
And what was happening here is it was giving me a type error rather than handing me null. By passing {raw: value} to String.raw instead solved the problem and I can now detect whether its a string/template literal or an object.
The clarifications to why I'm doing it this way is a longer story that has to do with React & styled components.
There is no difference between using a string literal and a template literal (except, obviously, on the source code level).
But your first value is a string, while the second one is an object, and those are easy to distinguish:
if (typeof value == "string") {
console.log(value)
} else if (typeof value == "object" && typeof value.message == "string") {
console.log(value.message)
}
Related
I have a variable that can either be a string or object like this:
value?: string | { name: string, type: string }
Trying something below but I get a compile error:
console.log(value?.name || value)
console.log(value?.type)
How can use this variable if it can be either type?
In this case you can do:
console.log(typeof value === 'string' ? value : value?.name)
Typescript can narrow the type using type-guards, see here for more details.
So you have two options
console.log(typeof value === 'string' ? value : value.name);
But as you used ?: in definition for this value (allowing undefined) and as i think that console is only for simple example there
if (value === undefined) {
} else if (typeof value === 'string') {
} else {
}
Would be best.
This issue you found is mostly done because both values for value variable are objects and ts saw that the types are not compatible and asks you to narrow them by hand
So basically i want to check if my data (which is in JSON Format) has a value which is a primitive. So let's take an Example: I get data that looks like this: {name: Artikel, value: {"ArtNr": 1234}} and i want to check if 1234 is primitive or not. I also want to differentiate if the result is an Array with Primitives in it or an Object. Is that possible?
function isObjectContainingPrimitiveValues(test) {
let values = Object.values(test);
for (let i of values) {
console.log(i);
return (typeof i === 'string' || typeof i === 'number' || typeof i === 'boolean' || typeof i === null || typeof i === undefined);
}
}
UPDATE
So with the awesome help of MaxK i have built a isResultContainingPrimitiveValues() Function which checks my data for Primitive/ Primitive Arrays and or Objects. The following part is the trickiest at least with my understanding. The following Example will hopefully help you understand my problems better.
So my let namesSplit = treeNode.name.split('.'); variable splits the data it gets and has as a result of nameSplit : Artikel,Artnr. Next i defined a key variable let key = namesSplit[0]; which has key : Artikel as a result. Than i define a contextEntry variable let contextEntry = exprData.contextEntry.find(_x => _x.name === key); and has contextEntry : {"name":"Artikel","value":{"ArtNr":123}} as a result. Now i want to check: if there's another split namesSplit.length > 1 check isResultContainingPrimitiveValues(). If it is primitive, throw an error, if it is an object -> get values from it and if it is an array -> get values form there. I know it's a lot but from all the confusing stuff i can't seem to think clear, so i appreciate every help i can get.
You are returning from your function on the first iteration. You should only return false if you found an non-primitive and if you were able to loop over all values you can return true because all values are primitives:
function isObjectContainingPrimitiveValues(testObj) {
let values = Object.values(testObj);
for(let i of values){
if (typeof i === 'object') {
return false;
}
}
return true;
};
Update:
After reading your comment i changed the code to check for arrays with primitives as well. The idea is, to create a new function which only checks if a single value is a primitive.Now if we find an array, we can simply check - with the help
of the arrays some function - if some element, inside the array is not primitive. If so return false,otherwise we do the same checks as before:
function isObjectContainingPrimitiveValues(testObj) {
let values = Object.values(testObj);
for (let i of values) {
if (Array.isArray(i)) {
if (i.some(val => !isPrimitive(val)))
return false;
} else {
if (!isPrimitive(i))
return false;
}
}
return true;
};
function isPrimitive(test) {
return typeof test !== 'object'
}
Array and object types all return a 'typeof' 'object'. so you can check against an object instead of checking against multiple conditions.
So the return statement will be:
return (typeof i === 'object').
Number, string, undefined, null will all return false on the statement above.
I'm trying to learn how to use flow, but I'm stuck with an error message that makes no sense to me.
This is my code:
import moment from "moment-timezone";
import type MomentType from "moment-timezone";
type filtersType = {
page?: number,
ccaa?: string,
province?: string,
date_from?: MomentType,
date_to?: MomentType,
distance_from?: number,
distance_to?: number,
category?: Array<string>,
};
const _parseValue = (value: mixed): string => {
if (Array.isArray(value)) return value.join(",");
else if (moment.isMoment(value)) return value.toISOString();
else if (typeof value === "number") return value.toString();
else if (typeof value === "string") return value;
throw new Error("Filter value type is not valid!!");
};
const _generateQueryItem = (key: string, value: mixed): string => {
return `${key}=${_parseValue(value)}`;
};
const _generateQuery = (filters: filtersType): string => {
return Object.entries(filters)
.map(([key, value]) => _generateQueryItem(key, value))
.join("&");
};
It keeps complaining about this, on line 33:
Cannot call value.toISOString because property toISOString is missing in mixed [1].
[1] 30│ const _parseValue = (value: mixed): string => {
31│ console.log(moment.isDate(value));
32│ if (Array.isArray(value)) return value.join(",");
33│ else if (moment.isMoment(value)) return value.toISOString();
34│ else if (typeof value === "number") return value.toString();
35│ else if (typeof value === "string") return value;
36│ throw new Error("Filter value type is not valid!!");
I always though that mixed types includes everything so, it shouldn't complain about any method missing, I'm not sure on whats going on, I tried to change the args types to:
const _parseValue = (value: mixed | MomentType): string => {
and:
const _generateQueryItem = (key: string, value: mixed | MomentType): string => {
but I still get the same error.
I need a way to make flow know that value has toISOString method but I run out of ideas for today. How do I do that?
(I already have installed moment types with flow-typed btw)
Ah, type refinement.
So you're trying to refine the type of value. You're doing this:
if (typeof value === "number") return value.toString();
which checks if value is a number. Flow understands this and refines value to a number so you can call toString on it.
You're doing this:
if (Array.isArray(value)) return value.join(",");
which checks if value is an array. Flow understands this and refines value to an array so you call join on it.
You're doing this:
if (moment.isMoment(value)) return value.toISOString();
which checks if value is an instance of a moment object. Flow does not understand this as a refinement. Therefore, your type is not being refined so when you attempt to call toISOString flow tells you that your method doesn't exist on mixed.
Flow only supports a small but somewhat powerful set of refinement operations. It has no idea that isMoment is even intended as a refinement. The only values flow can refine from mixed are really plain old data values, numbers, strings, arrays, objects, it's impossible to refine from mixed to MomentType.
So first of all, let's stop relying on mixed. If you can only possibly receive a small subset of types as parseable values, a union will serve you better, and it will explicitly add MomentType as a possibility for refinement:
type Parseable = number | string | string[] | MomentType;
Then there's the problem of our refinement for a moment object. Well, we have two possibilities. If we are really only concerned with these types, meaning we are totally confident these are the only things we will ever get, then we don't even have to explicitly refine to a moment object:
const _parseValue = (value: Parseable): string => {
if (Array.isArray(value)) return value.join(",");
else if (typeof value === "number") return value.toString();
else if (typeof value === "string") return value;
return value.toISOString(); // by process of elimination, this is a moment
};
Flow is smart enough to know that we have already handled all the other cases, so if we reach the end of the function then by process of elimination value must be a moment.
So, should we rely on this behavior? Well, it depends. Is arbitrary data from an unreliable API going to be passed into _parseValue? Or well-typed data from reliable places in our flow-typed codebase? If we don't need to worry too much about spurious data, then sure, rely on the process of elimination. Otherwise, we should probably hold onto your original error case. But that means we'll have to explicitly refine to our moment type:
const _parseValue = (value: Parseable): string => {
if (Array.isArray(value)) return value.join(",");
else if (typeof value === "number") return value.toString();
else if (typeof value === "string") return value;
// let's be extra careful
else if (typeof value === 'object' && value.isMoment && value.toISOString) return value.toISOString();
throw new Error("Filter value type is not valid!!");
};
Flow would accept our check typeof value === 'object' as enough to distinguish value from the other entries in Parseable and ensure that it is a MomentType, but as our data source might be dubious, let's also check for value.isMoment (a property on all moment objects defined in the flow-typed libdef for moment). This should prove beyond a reasonable doubt that this is, in fact, a moment object. One strategy would be to just duck-type any object that we get as having value.toISOString and go ahead and call it.
The attribute(or the nested object) is selected dynamically based on conditions. It can be one of the 4 possibilities as follows:
var tempData = o.title ? o["properties"] || o["items"]["properties"] : o[k]["properties"] || o[k]["items"]["properties"];
Then I get this new data, I want to replace the above selected with.
var newData = //some new Object
I want to replace whatever above selected with the new data. I could do the following (go through the condition again and set the new data):
if(o.title){
if (o["properties"]) {
o["properties"] = newData;
} else if (o["items"]["properties"]) {
o["items"]["properties"] = newData;
}
}else{
if (o[k]["properties"]) {
o[k]["properties"] = newData;
} else if (o[k]["items"]["properties"]) {
o[k]["items"]["properties"] = newData;
}
}
But it doesn't look good. What is the more sophisticated way of achieving this?
It is unclear if you are generically attempting to replace any properties property with the newData, or if you are wanting it to specifically be one of the ones you have specified in your code. I have assumed that you are only wanting to replace the ones you specifically have shown in your code.
Note: The following assumes that it is not possible for the value of the properties property to evaluate to false. If it is possible for it to have a value that evaluates to false, this will fail.
As a first pass, I would do something like:
var p;
if (o.title) {
p=o;
} else {
p=o[k];
}
if (p.properties) {
p.properties = newData;
} else if (p.items.properties) {
p.items.properties = newData;
}
However, that relies on:
o is not null or undefined.
o.title does not evaluate to false, if you are trying to test for the existence of o.title.
k is valid/defined.
p (i.e. o[k]) is not null or undefined (i.e. is an Object)
p.properties does not evaluate to false, if you are testing for existence
p.items is not null or undefined (i.e. is an Object)
p.items.properties does not evaluate to false, if you are testing for existence
A more robust implementation would be:
if (typeof o === 'object' && o !== null) {
var p;
if (o.hasOwnProperty('title')) {
p = o;
} else {
p = o[k];
}
if (typeof p === 'object' && p !== null) {
if (p.hasOwnProperty('properties')) {
p.properties = newData;
} else if (typeof p.items === 'object' && p.items !== null
&& p.items.hasOwnProperty('properties')) {
p.items.properties = newData;
}
}
}
This still relies on:
k is valid/defined.
Basically, it is OK to use shortcuts like if(o.title) to test for existence, if you know that
the possible values for o can not include ones which might make your code throw an error (e.g o is null or undefined), and
the possible values for o.title do not evaluate to false when the property actually exists (e.g. o.title is null, undefined (yes, the property can exist, but have the value undefined), false, 0, '', etc.).
If you are going to perform the replacements in other areas of your code, or if you are going to use property keys other than hard coded items, and properties, then you should create a function. Assuming you are only performing this replacement in this section of your code, using a variable to hold the object in which you are looking for properties is faster/more efficient than creating a function.
Ok, from what i can understand here, it's like you are trying to replace the "properties" with the new data, and you want this to be able to be done dynamically, or maybe i can say, you need to do this regardless the structure.
lets see, if your objective is anything that end up with "properties", lets do it like this:
function recReplace(current,target,replacement){
for (var i in current){
if (i == target){
current[i] = replacement;
}
else{
recReplace(current[i],target,replacement);
}
}
}
And in the end you call
recReplace(o,"properties",newData);
But this will replace whole "properties" key with newData in DFS way, you can do additional conditional if you want to replace it only the first occurence
I have a string variable that is a string representation of an object. Like this:
{
"column": "'asdf'",
"sort": "true",
"search": "{\"asdf\":\"searchval\"}"
}
I would like to transform the above string into an object that looks like this:
{
column: 'asdf',
sort: 'true',
search: {
asdf: 'searchval'
}
}
I am planning on doing a search and replace regex expression for quotes and back slashes, and then using eval(), but that seems unsafe and innefficient. Is there an alternative method that would work better?
I am planning on assigning the object value of the string to an object variable.
Have you tried JSON.parse(); yet?
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
var myOriginalData = JSON.stringify({"column": "'asdf'","sort": "true","search": "{\"asdf\":\"searchval\"}"});
myNewData = JSON.parse(myOriginalData, function (key, value) {
var type;
if (value && typeof value === 'object') {
type = value.type;
if (typeof type === 'string' && typeof window[type] === 'function') {
return new (window[type])(value);
}
}
return value;
});
console.log('myNewData -----------------------------------------------------------');
console.log(myNewData);
Working Example: http://plnkr.co/edit/2jLGIxx3AVEceVQsOpxL?p=preview
Immediately after posting this, I found out there is a JSON.parse(); function. The only reason I don't delete the question, is it might save someone time later.
All you need to do is pass the string variable as a parameter and it will output an object.
If anyone sees any problems with this solution, or knows a better solution, please make an answer!