Skip to content

Commit

Permalink
creating new circular-json safe stringify and replacing one call (#6772)
Browse files Browse the repository at this point in the history
(cherry picked from commit 11a7ad0)
  • Loading branch information
mrmcduff authored and xtinec committed Feb 5, 2019
1 parent 8ea805e commit 845c7aa
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 1 deletion.
110 changes: 110 additions & 0 deletions superset/assets/spec/javascripts/utils/safeStringify_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { safeStringify } from '../../../src/utils/safeStringify';

class Noise {
public next: Noise;
}

describe('Stringify utility testing', () => {
it('correctly parses a simple object just like JSON', () => {
const noncircular = {
b: 'foo',
c: 'bar',
d: [
{
e: 'hello',
f: ['world'],
},
{
e: 'hello',
f: ['darkness', 'my', 'old', 'friend'],
},
],
};
expect(safeStringify(noncircular)).toEqual(JSON.stringify(noncircular));
// Checking that it works with quick-deepish-copies as well.
expect(JSON.parse(safeStringify(noncircular))).toEqual(JSON.parse(JSON.stringify(noncircular)));
});

it('handles simple circular json as expected', () => {
const ping = new Noise();
const pong = new Noise();
const pang = new Noise();
ping.next = pong;
pong.next = ping;

// ping.next is pong (the circular reference) now
const safeString = safeStringify(ping);
ping.next = pang;

// ping.next is pang now, which has no circular reference, so it's safe to use JSON.stringify
const ordinaryString = JSON.stringify(ping);
expect(safeString).toEqual(ordinaryString);
});

it('creates a parseable object even when the input is circular', () => {
const ping = new Noise();
const pong = new Noise();
ping.next = pong;
pong.next = ping;

const newNoise: Noise = JSON.parse(safeStringify(ping));
expect(newNoise).toBeTruthy();
expect(newNoise.next).toEqual({});
});

it('does not remove noncircular duplicates', () => {
const a = {
foo: 'bar',
};

const repeating = {
first: a,
second: a,
third: a,
};

expect(safeStringify(repeating)).toEqual(JSON.stringify(repeating));
});

it('does not remove nodes with empty objects', () => {
const emptyObjectValues = {
a: {},
b: 'foo',
c: {
d: 'good data here',
e: {},
},
};
expect(safeStringify(emptyObjectValues)).toEqual(JSON.stringify(emptyObjectValues));
});

it('does not remove nested same keys', () => {
const nestedKeys = {
a: 'b',
c: {
a: 'd',
x: 'y',
},
};

expect(safeStringify(nestedKeys)).toEqual(JSON.stringify(nestedKeys));
});
});
3 changes: 2 additions & 1 deletion superset/assets/src/explore/exploreUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
/* eslint camelcase: 0 */
import URI from 'urijs';
import { availableDomains } from '../utils/hostNamesConfig';
import { safeStringify } from '../utils/safeStringify';

const MAX_URL_LENGTH = 8000;

Expand Down Expand Up @@ -71,7 +72,7 @@ export function getExploreLongUrl(formData, endpointType, allowOverflow = true,
Object.keys(extraSearch).forEach((key) => {
search[key] = extraSearch[key];
});
search.form_data = JSON.stringify(formData);
search.form_data = safeStringify(formData);
if (endpointType === 'standalone') {
search.standalone = 'true';
}
Expand Down
45 changes: 45 additions & 0 deletions superset/assets/src/utils/safeStringify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

/**
* A Stringify function that will not crash when it runs into circular JSON references,
* unlike JSON.stringify. Any circular references are simply omitted, as if there had
* been no data present
* @param object any JSON object to be stringified
*/
export function safeStringify(object: any): string {
const cache = new Set();
return JSON.stringify(object, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (cache.has(value)) {
// We've seen this object before
try {
// Quick deep copy to duplicate if this is a repeat rather than a circle.
return JSON.parse(JSON.stringify(value));
} catch (err) {
// Discard key if value cannot be duplicated.
return;
}
}
// Store the value in our cache.
cache.add(value);
}
return value;
});
}

0 comments on commit 845c7aa

Please sign in to comment.