Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added Object key sort prior to JSON.stringify comparison (fix #5908) #5993

Closed
wants to merge 3 commits into from

Conversation

drewsmith
Copy link

#5908

What kind of change does this PR introduce? (check at least one)

  • Bugfix
  • Feature
  • Code style update
  • Refactor
  • Build-related changes
  • Other, please describe: Improvement

Does this PR introduce a breaking change? (check one)

  • Yes
  • No

If yes, please describe the impact and migration path for existing applications:

The PR fulfills these requirements:

If adding a new feature, the PR's description includes:

  • A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it)

Other information:

@jkzing
Copy link
Member

jkzing commented Jun 28, 2017

This maybe not working because AFAIK it's JSON.stringify that has no guarantee on the order of properties serialized by it. Will it be useful by sorting the keys before serializing? 🤔

@drewsmith
Copy link
Author

Definitely a somewhat trivial edge case. There are two ways to ensure this AFAIK. This approach has some reusability or you can pass sorted keys to stringify JSON.stringify(obj, Object.keys(obj).sort())

it('Sorts an unsorted Object, which are equal', () => {
const unsortedTestObj = unsortedObject()
const sortedObject = keySort(unsortedTestObj)
expect(looseEqual(sortedObject, sortedObject)).toBe(true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either one should be unsortedTestObj here.

@znck
Copy link
Member

znck commented Jun 28, 2017

@drewsmith Is this right approach?

If I compare key by key in two objects, it would take O(N) comparisons.
If I sort then compare stringified JSON, it would take O(N logN) comparisons + overhead of stringify.

@drewsmith
Copy link
Author

@znck Thanks for the comment. Are you referring to passing sorted keys to stringify or sorting altogether?

The right approach is deep object equality IMO, which stringify is a low tech mechanism for doing so since it serializes the entire object including values. The fact the order is not deterministic requires some sort of explicit ordering or perhaps stringify should be replaced with something else altogether?

@znck
Copy link
Member

znck commented Jun 28, 2017

I want to say, sorting the keys beats the purpose of looseEqual.

Also, looseEqual is used only in model.js for select options, I guess stringify serves the purpose there. @yyx990803 would know this better.

@drewsmith
Copy link
Author

I agree with you depending on semantics. Does loose mean implicit deep copy, or is it a synonym for shallow? stringify gets you a poor man's deep copy, but either way values need to be compared.

Copy link
Member

@HerringtonDarkholme HerringtonDarkholme left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bottom line is to keep current semantic the same.

As the name looseEqual suggest, this function will not behave as desire 100%. ECMA spec requires no insertion ordering on Object enumeration nor JSON.stringify. Though most JS engine will keep the order so sorting can improve comparison if users compare {a: 1, b: 2} with {b:2, a:1}, where key insertion order is different.

Of course, a full implementation like https://github.com/substack/json-stable-stringify is too heavy for looseEqual which is only used in v-model.

*/
export function keySort (obj: any): Object {
if (isObject(obj) === false) {
return {}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If obj isn't object, this function always return an empty object which makes such call like looseEqual(1, 2) returning true.

}
const sorted = {}
Object.keys(obj).sort().forEach(key => {
sorted[key] = obj[key]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only sorts the first level of an object. For nested object like {nested: {a: 1, b: 2}} and {nested: {b:2, a:1}}, it fails.

A recursive call should pay more attention to cyclic references.

* by succinct key order. Guaranteed to at least return an
* empty object.
*/
export function keySort (obj: any): Object {
Copy link
Member

@HerringtonDarkholme HerringtonDarkholme Jun 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is only used in this file, consider not exporting it. Also, obj is annotated as any which in this case, Object is a better choice, if you perform no recursive call.

})

it('returns Object for Array argument', () => {
expect(looseEqual(keySort([]), {})).toBe(true)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes looseEqual's original behavior.

@mitar
Copy link
Contributor

mitar commented Jul 13, 2017

Maybe check how this package does it.

@yyx990803
Copy link
Member

Thanks for the PR - however, cloning with sorted keys results in extra memory allocation and it's better to just compare in place. See a8ac129

@yyx990803 yyx990803 closed this Jul 21, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants