Lodash isEqual fails because of constructor defined by angular - javascript

I am using Lodash _.isEqual to deep-compare a local javascript object with another javascript object retrieved through angular $get.
This code says that the objects are different:
$get({...}, function (data) {
cleanupAngularProps(data);
if (_.isEqual(data, {name: 'Someone'}) {
...
}
});
but chaging it a little as follows it now says that they are equal (as expected):
$get({...}, function (data) {
cleanupAngularProps(data);
if (_.isEqual(JSON.parse(JSON.stringify(data)), {name: 'Someone'}) {
...
}
});
I debugged into the Lodash code and it seems to fail because both objects have different constructors.
How can I solve this without cloning the data?

I know this question is three years old at this point, but I have found what I believe is a more acceptable answer. Simply exclude the prototype comparison between the two objects:
var result = _.isEqual(
_.omit(data, ['__proto__']),
_.omit({name: 'Someone'}, ['__proto__'])
);

I am not sure if this is a good practice but I solved it by resetting the prototype, hence the constructor.
$get({...}, function (data) {
cleanupAngularProps(data);
data.__proto__ = Object.prototype;
if (_.isEqual(data, {name: 'Someone'}) {
...
}
});

If you need deep comparison and ignore constructors, then you can use this code:
const customizer = (a: any, b: any, key: any) => {
if (_.isObject(a) && _.isObject(b)) {
// #ts-ignore
const aProto = a.__proto__.constructor.name
// #ts-ignore
const bProto = b.__proto__.constructor.name
if (aProto != bProto) {
return _.isEqualWith(_.omit(a, ['__proto__']), _.omit(b, ['__proto__']), customizer)
}
}
return undefined
}
_.isEqualWith({foo:1}, {foo:1}, customizer)

Related

async-await function: Error message not passed to the catch block [duplicate]

Reproducing the problem
I'm running into an issue when trying to pass error messages around using web sockets. I can replicate the issue I am facing using JSON.stringify to cater to a wider audience:
// node v0.10.15
> var error = new Error('simple error message');
undefined
> error
[Error: simple error message]
> Object.getOwnPropertyNames(error);
[ 'stack', 'arguments', 'type', 'message' ]
> JSON.stringify(error);
'{}'
The problem is that I end up with an empty object.
What I've tried
Browsers
I first tried leaving node.js and running it in various browsers. Chrome version 28 gives me the same result, and interestingly enough, Firefox at least makes an attempt but left out the message:
>>> JSON.stringify(error); // Firebug, Firefox 23
{"fileName":"debug eval code","lineNumber":1,"stack":"#debug eval code:1\n"}
Replacer function
I then looked at the Error.prototype. It shows that the prototype contains methods such as toString and toSource. Knowing that functions can't be stringified, I included a replacer function when calling JSON.stringify to remove all functions, but then realized that it too had some weird behavior:
var error = new Error('simple error message');
JSON.stringify(error, function(key, value) {
console.log(key === ''); // true (?)
console.log(value === error); // true (?)
});
It doesn't seem to loop over the object as it normally would, and therefore I can't check if the key is a function and ignore it.
The Question
Is there any way to stringify native Error messages with JSON.stringify? If not, why does this behavior occur?
Methods of getting around this
Stick with simple string-based error messages, or create personal error objects and don't rely on the native Error object.
Pull properties: JSON.stringify({ message: error.message, stack: error.stack })
Updates
#Ray Toal Suggested in a comment that I take a look at the property descriptors. It is clear now why it does not work:
var error = new Error('simple error message');
var propertyNames = Object.getOwnPropertyNames(error);
var descriptor;
for (var property, i = 0, len = propertyNames.length; i < len; ++i) {
property = propertyNames[i];
descriptor = Object.getOwnPropertyDescriptor(error, property);
console.log(property, descriptor);
}
Output:
stack { get: [Function],
set: [Function],
enumerable: false,
configurable: true }
arguments { value: undefined,
writable: true,
enumerable: false,
configurable: true }
type { value: undefined,
writable: true,
enumerable: false,
configurable: true }
message { value: 'simple error message',
writable: true,
enumerable: false,
configurable: true }
Key: enumerable: false.
Accepted answer provides a workaround for this problem.
JSON.stringify(err, Object.getOwnPropertyNames(err))
seems to work
[from a comment by /u/ub3rgeek on /r/javascript] and felixfbecker's comment below
You can define a Error.prototype.toJSON to retrieve a plain Object representing the Error:
if (!('toJSON' in Error.prototype))
Object.defineProperty(Error.prototype, 'toJSON', {
value: function () {
var alt = {};
Object.getOwnPropertyNames(this).forEach(function (key) {
alt[key] = this[key];
}, this);
return alt;
},
configurable: true,
writable: true
});
var error = new Error('testing');
error.detail = 'foo bar';
console.log(JSON.stringify(error));
// {"message":"testing","detail":"foo bar"}
Using Object.defineProperty() adds toJSON without it being an enumerable property itself.
Regarding modifying Error.prototype, while toJSON() may not be defined for Errors specifically, the method is still standardized for objects in general (ref: step 3). So, the risk of collisions or conflicts is minimal.
Though, to still avoid it completely, JSON.stringify()'s replacer parameter can be used instead:
function replaceErrors(key, value) {
if (value instanceof Error) {
var error = {};
Object.getOwnPropertyNames(value).forEach(function (propName) {
error[propName] = value[propName];
});
return error;
}
return value;
}
var error = new Error('testing');
error.detail = 'foo bar';
console.log(JSON.stringify(error, replaceErrors));
As no one is talking about the why part, I'm gonna answer it.
Why this JSON.stringify returns an empty object?
> JSON.stringify(error);
'{}'
Answer
From the document of JSON.stringify(),
For all the other Object instances (including Map, Set, WeakMap and WeakSet), only their enumerable properties will be serialized.
and Error object doesn't have its enumerable properties, that's why it prints an empty object.
There is a great Node.js package for that: serialize-error.
npm install serialize-error
It handles well even nested Error objects.
import {serializeError} from 'serialize-error';
const stringifiedError = serializeError(error);
Docs: https://www.npmjs.com/package/serialize-error
Modifying Jonathan's great answer to avoid monkey patching:
var stringifyError = function(err, filter, space) {
var plainObject = {};
Object.getOwnPropertyNames(err).forEach(function(key) {
plainObject[key] = err[key];
});
return JSON.stringify(plainObject, filter, space);
};
var error = new Error('testing');
error.detail = 'foo bar';
console.log(stringifyError(error, null, '\t'));
We needed to serialise an arbitrary object hierarchy, where the root or any of the nested properties in the hierarchy could be instances of Error.
Our solution was to use the replacer param of JSON.stringify(), e.g.:
function jsonFriendlyErrorReplacer(key, value) {
if (value instanceof Error) {
return {
// Pull all enumerable properties, supporting properties on custom Errors
...value,
// Explicitly pull Error's non-enumerable properties
name: value.name,
message: value.message,
stack: value.stack,
}
}
return value
}
let obj = {
error: new Error('nested error message')
}
console.log('Result WITHOUT custom replacer:', JSON.stringify(obj))
console.log('Result WITH custom replacer:', JSON.stringify(obj, jsonFriendlyErrorReplacer))
I was working on a JSON format for log appenders and ended up here trying to solve a similar problem. After a while, I realized I could just make Node do the work:
const util = require("util");
...
return JSON.stringify(obj, (name, value) => {
if (value instanceof Error) {
return util.format(value);
} else {
return value;
}
}
You can also just redefine those non-enumerable properties to be enumerable.
Object.defineProperty(Error.prototype, 'message', {
configurable: true,
enumerable: true
});
and maybe stack property too.
If using nodejs there is better reliable way by using native nodejs inspect. As well you can specify to print objects to unlimited depth.
Typescript example:
import { inspect } from "util";
const myObject = new Error("This is error");
console.log(JSON.stringify(myObject)); // Will print {}
console.log(myObject); // Will print full error object
console.log(inspect(myObject, {depth: null})); // Same output as console.log plus it works as well for objects with many nested properties.
Link to documentation, link to example usage.
And as well discussed in the topic How can I get the full object in Node.js's console.log(), rather than '[Object]'? here in stack overflow.
None of the answers above seemed to properly serialize properties which are on the prototype of Error (because getOwnPropertyNames() does not include inherited properties). I was also not able to redefine the properties like one of the answers suggested.
This is the solution I came up with - it uses lodash but you could replace lodash with generic versions of those functions.
function recursivePropertyFinder(obj){
if( obj === Object.prototype){
return {};
}else{
return _.reduce(Object.getOwnPropertyNames(obj),
function copy(result, value, key) {
if( !_.isFunction(obj[value])){
if( _.isObject(obj[value])){
result[value] = recursivePropertyFinder(obj[value]);
}else{
result[value] = obj[value];
}
}
return result;
}, recursivePropertyFinder(Object.getPrototypeOf(obj)));
}
}
Error.prototype.toJSON = function(){
return recursivePropertyFinder(this);
}
Here's the test I did in Chrome:
var myError = Error('hello');
myError.causedBy = Error('error2');
myError.causedBy.causedBy = Error('error3');
myError.causedBy.causedBy.displayed = true;
JSON.stringify(myError);
{"name":"Error","message":"hello","stack":"Error: hello\n at <anonymous>:66:15","causedBy":{"name":"Error","message":"error2","stack":"Error: error2\n at <anonymous>:67:20","causedBy":{"name":"Error","message":"error3","stack":"Error: error3\n at <anonymous>:68:29","displayed":true}}}
Just convert to a regular object
// example error
let err = new Error('I errored')
// one liner converting Error into regular object that can be stringified
err = Object.getOwnPropertyNames(err).reduce((acc, key) => { acc[key] = err[key]; return acc; }, {})
If you want to send this object from child process, worker or though the network there's no need to stringify. It will be automatically stringified and parsed like any other normal object
String constructor should be able to stringify error
try {
throw new Error("MY ERROR MSG")
} catch (e) {
String(e) // returns 'Error: MY ERROR MSG'
}
I've extended this answer: Is it not possible to stringify an Error using JSON.stringify?
serializeError.ts
export function serializeError(err: unknown) {
return JSON.parse(JSON.stringify(err, Object.getOwnPropertyNames(err)))
}
And I can use it like this:
import { serializeError } from '../helpers/serializeError'; // Change to your path
try {
const res = await create(data);
return { status: 201 };
} catch (err) {
return { status: 400, error: serializeError(err) };
}
You can solve this with a one-liner( errStringified ) in plain javascript:
var error = new Error('simple error message');
var errStringified = (err => JSON.stringify(Object.getOwnPropertyNames(Object.getPrototypeOf(err)).reduce(function(accumulator, currentValue) { return accumulator[currentValue] = err[currentValue], accumulator}, {})))(error);
console.log(errStringified);
It works with DOMExceptions as well.

DeepCopy Object in JavaScript using immer

I am using immer to transform react/redux state. Can I also use immer to just deep copy an object without transforming it?
import produce, {nothing} from "immer"
const state = {
hello: "world"
}
produce(state, draft => {})
produce(state, draft => undefined)
// Both return the original state: { hello: "world"}
This is from the official immer README. Does this mean that passing an empty function to produce returns a deep copy of the original state or actually the original state?
Thank you very much for your help :)!
This is easily testable with
import { produce } from 'immer'
const state = {
hello: 'world',
}
const nextState = produce(state, draft => {})
nextState.hello = 'new world'
console.log(state, nextState)
which outputs
Object { hello: "new world" }
Object { hello: "new world" }
which means that it does NOT create a deep copy of an object.
UPDATE:
So I got interested and tested out the library a lot and here are my findings.
The code snippet I wrote above is simply an optimisation in the library which returns the old state if no changes are made. However, if you make some changes, then the library starts functioning as intended and the mutation later is made impossible. That is,
const state = {
hello: 'world',
}
const nextState = produce(state, draft => {
draft.hello = 'new world';
})
nextState.hello = 'newer world';
console.log(state, nextState)
will result in an error: TypeError: "world" is read-only
Which means that your newState is immutable and you can no longer perform mutations on it.
Another rather interesting thing I found is that immer fails when using class instances. That is,
class Cls {
prop = 10;
}
const instance = new Cls();
const obj = {
r: instance,
};
const newObj = produce(obj, draft => {
draft.r.prop = 15;
});
console.log(obj, newObj);
results in
r: Object { prop: 15 }
r: Object { prop: 15 }
So to get back to the initial question, can you get a deep copy of the initial Object by changing nothing in the draft. No you cannot, and even if you did (by changing a property created just to fool immer perhaps), the resultant cloned object will be immutable and not really helpful.
Solution :
The Immer's produce only provides a new deep cloned object on updation.
you can create your own produce function that behaves just like that of immer's produce but gives a cloned object everytime using loadash
import _ from 'lodash';
export default function produceClone(object, modifyfunction) {
let objectClone = _.cloneDeep(object);
if (!modifyfunction) return objectClone;
modifyfunction(objectClone);
return objectClone;
}
This will give you a deepCloned(or deep copied) object everytime, irrespective of whether you modify the object or not.

ES6 object cloning using spread operator is modifying input too

I have a fairly deep interface declared that looks something like this:
export interface Job {
JobId: JobId; // type JobId = string
UserId: UserId; // type UserId = string
JobName: string;
AudioFile: JobAudioFile; // this is an interface
Status: JobStatus; // this is an enum
Tracks: JobTracks[]; // 'JobTracks' is an enum
Results: JobResults; // this is an interface
Timestamps: JobTimestamps // interface
}
Most of the members of this interface are themselves interfaces, with the general architecture following this pattern of using enums, strings, arrays and more interfaces. All code is written as TypeScript, transpiled down to JS and uploaded to AWS as JS. (Node 8.10 is running on AWS)
At one point in the code, I need to make a deep copy of a Job instantiation which was passed in as a function parameter:
export const StartPipeline: Handler = async (
event: PipelineEvent
): Promise<PipelineEvent> => {
console.log('StartPipeline Event: %o', event);
const newBucket = await copyToJobsBucket$(event.Job);
await deleteFromOriginalBucket$(event.Job);
console.log(`Job [${event.Job.JobId}] moved to Jobs bucket: ${newBucket}`);
event.Job.AudioFile.Bucket = newBucket;
event.Job.Status = Types.JobStatus.Processing;
// update the job status
// VVV PROBLEM OCCURS HERE VVV
const msg: Types.JobUpdatedMessage = new Types.JobUpdatedMessage({ Job: Object.assign({}, event.Job) });
await Send.to$(event.Job.UserId, msg);
return { ...event };
};
The definition of the JobUpdatedMessage:
export class JobUpdatedMessage extends BaseMessage {
constructor(payload: { Job: Types.Job }) {
console.log('Incoming: %o', payload);
const copy: object = { ...payload.Job };
// VVV PROBLEM ON NEXT LINE VVV
const filtered = JobUtils.FilterJobProperties(copy as Types.Job);
super(MessageTypes.JobUpdated, filtered);
}
}
The problem is after the call to JobUtils.FilterJobProperties, payload.Job has also been mutated in an undesirable and unexpected way.
Here's the implementation of JobUtils.FilterJobProperties:
export const FilterJobProperties = (from: Types.Job): Types.Job => {
const fieldsToRemove: string[] = [
'Transcripts.GSTT',
'Transcripts.WSTT',
'Transcripts.ASTT',
'TranscriptTracks',
'Transcripts.Stream.File',
'Transcripts.Stream.State',
'AudioFile.Bucket',
'AudioFile.S3Key',
];
let job: Types.Job = { ...from }; // LINE ONE
fieldsToRemove.forEach(field => _.unset(job, field)); // LINE TWO
return job;
};
(I'm using the lodash library here)
The line market 'LINE TWO' is also mutating the from function parameter, even though on 'LINE ONE' I'm doing what I think is a deep clone of from.
I know that this is the case because if I change 'LINE ONE' to:
// super hard core deep cloning
let job: Types.Job = JSON.parse(JSON.stringify(from));
... everything works as expected. from is not mutated, the resulting JobUpdatedMessage is as expected, and StartPipeline's event parameter doesn't have a bunch of properties removed from event.Job.
I struggled with hours on this, including relearning everything I believed I knew about cloning objects in Es6 using the spread operator.
Why was 'LINE ONE' mutating the input as well?
Spread operator does shallow cloning same as Object.assign()
Shallow-cloning (excluding prototype) or merging of objects is now
possible using a shorter syntax than Object.assign().
Spread operator
An example to understand spread operator and shallow cloning.
let obj = { 'a': { 'b' : 1 },'c': 2}
let copy = {...obj}
copy.c = 'changes only in copy' //shallow-cloned
copy.a.b = 'changed' // still reference
console.log('original\n',obj)
console.log('\ncopy',copy)
Using spread operator object is shallow cloned so all the first level properties will become a copy while all the deeper level properties will still remain the references.
so as you see in example c property doesn't affect the original object since it is one first level depth, on the other hand b property changes affect the parent properties because it is at deep level and is still reference.

Flow: Object type incompatible with Array<mixed>

I don’t understand the flow error I’m currently getting. I have a Javascript object of objects (dataObject) that I want to convert to an array of objects, so I do so using Object.values(dataObject). Then, I iterate through each object in the array with the following:
const dataObjectArray = Object.values(dataObject);
return dataObjectArray((data: DataObject) => {
const { typeA, typeB } = data;
return {
TYPE_A: typeA,
TYPE_B: typeB,
};
});
But I get the following flowtype error:
I’m not sure how to match up the types. Currently my DataObject flow type is
type DataObject = {
typeA: string,
typeB: string,
};
Any help would be appreciated. Thanks!
The type definition for the Object.values function has no way to know that the argument passed to it is an object where the values are all the same type. You could just as easily be doing Object.values({foo: 4, bar: "str"}). The type definition is
(any) => Array<mixed>
meaning that you are doing .map on a value of type Array<mixed>.
That means if you want to use it as object, your method will not work. Assuming your "object of objects" is typed as
type DataObjects = {
[string]: DataObject,
}
You'd likely be better off doing
function values(objs: DataObjects): Array<DataObject> {
return Object.keys(objs).map(key => objs[key]);
}
If you prefer to use Object.values() (probably more efficient) and have typing right, you can use a helper function like this:
function objectToValues<A, B>(obj:{[key: A]: B} ): Array<B> {
return ((Object.values(obj): any): Array<B>)
}

Typeof arguments object in TypeScript

I basically have this:
function foo(){
// literally pass the arguments object into the pragmatik.parse method
// the purpose of pragmatik.parse is to handle more complex variadic functions
const [a,b,c,d,e] = pragmatik.parse(arguments);
// now we have the correct arguments in the expected location, using array destructuring
}
so we have the pragmatik.parse method:
function parse(args){
// return parsed arguments
}
now I want to use TypeScript to define types, all I know is that arguments is an Object:
function parse(args: Object){
}
so my question is: does TypeScript give a definition or type for an arguments object in JS? Sorry this is a bit meta, but please bear with me, what I am asking about is sane.
My Webstorm IDE suggests that this might be IArguments, which is provided by: lib/es6/d.ts, which is somewhere out there. Maybe someone can verify this is correct, but I am fairly certain.
So the answer would be:
function parse(args: IArguments){
}
and the full signature would be:
function parse(args: IArguments) : Array<any> {
}
since the parse method returns a generic array
You can pick exact type of arguments with tsargs package from npm
Eg:
import { ArgsN } from 'tsargs';
function foo(a: boolean, b: number, c: string) {}
const argsABC: ArgsN<typeof foo> = [ true, 123, 'Hello' ];
In your case:
import { ArgsN } from 'tsargs';
function parse<T extends (...args: any[]) => any>(args: IArguments): ArgsN<T> {
// return parsed arguments
}
// ...
const args = parse<typeof foo>(arguments);
// args -> [ boolean, number, string ];

Categories

Resources