-
-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(new-tool): diff of two json objects
- Loading branch information
1 parent
61ece23
commit 362f2fa
Showing
12 changed files
with
751 additions
and
134 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import _ from 'lodash'; | ||
import { useCopy } from '@/composable/copy'; | ||
import type { Difference, ArrayDifference, ObjectDifference } from '../json-diff.types'; | ||
|
||
export const DiffRootViewer = ({ diff }: { diff: Difference }) => { | ||
return ( | ||
<div class={'diffs-viewer'}> | ||
<ul>{DiffViewer({ diff, showKeys: false })}</ul> | ||
</div> | ||
); | ||
}; | ||
|
||
const DiffViewer = ({ diff, showKeys = true }: { diff: Difference; showKeys?: boolean }) => { | ||
const { type, status } = diff; | ||
|
||
if (status === 'updated') { | ||
return ComparisonViewer({ diff, showKeys }); | ||
} | ||
|
||
if (type === 'array') { | ||
return ChildrenViewer({ diff, showKeys, showChildrenKeys: false, openTag: '[', closeTag: ']' }); | ||
} | ||
|
||
if (type === 'object') { | ||
return ChildrenViewer({ diff, showKeys, openTag: '{', closeTag: '}' }); | ||
} | ||
|
||
return LineDiffViewer({ diff, showKeys }); | ||
}; | ||
|
||
const LineDiffViewer = ({ diff, showKeys }: { diff: Difference; showKeys?: boolean }) => { | ||
const { value, key, status, oldValue } = diff; | ||
const valueToDisplay = status === 'removed' ? oldValue : value; | ||
|
||
return ( | ||
<li> | ||
<span class={[status, 'result']}> | ||
{showKeys && ( | ||
<> | ||
<span class={'key'}>{key}</span> | ||
{': '} | ||
</> | ||
)} | ||
{Value({ value: valueToDisplay, status })} | ||
</span> | ||
, | ||
</li> | ||
); | ||
}; | ||
|
||
const ComparisonViewer = ({ diff, showKeys }: { diff: Difference; showKeys?: boolean }) => { | ||
const { value, key, oldValue } = diff; | ||
|
||
return ( | ||
<li class={'updated-line'}> | ||
{showKeys && ( | ||
<> | ||
<span class={'key'}>{key}</span> | ||
{': '} | ||
</> | ||
)} | ||
{Value({ value: oldValue, status: 'removed' })} | ||
{Value({ value, status: 'added' })}, | ||
</li> | ||
); | ||
}; | ||
|
||
const ChildrenViewer = ({ | ||
diff, | ||
openTag, | ||
closeTag, | ||
showKeys, | ||
showChildrenKeys = true, | ||
}: { | ||
diff: ArrayDifference | ObjectDifference; | ||
showKeys: boolean; | ||
showChildrenKeys?: boolean; | ||
openTag: string; | ||
closeTag: string; | ||
}) => { | ||
const { children, key, status, type } = diff; | ||
|
||
return ( | ||
<li> | ||
<div class={[type, status]} style={{ display: 'inline-block' }}> | ||
{showKeys && ( | ||
<> | ||
<span class={'key'}>{key}</span> | ||
{': '} | ||
</> | ||
)} | ||
|
||
{openTag} | ||
{children.length > 0 && <ul>{children.map((diff) => DiffViewer({ diff, showKeys: showChildrenKeys }))}</ul>} | ||
{closeTag + ','} | ||
</div> | ||
</li> | ||
); | ||
}; | ||
|
||
function formatValue(value: unknown) { | ||
if (_.isNull(value)) { | ||
return 'null'; | ||
} | ||
|
||
return JSON.stringify(value); | ||
} | ||
|
||
const Value = ({ value, status }: { value: unknown; status: string }) => { | ||
const formatedValue = formatValue(value); | ||
|
||
const { copy } = useCopy({ source: formatedValue }); | ||
|
||
return ( | ||
<span class={['value', status]} onClick={copy}> | ||
{formatedValue} | ||
</span> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
<template> | ||
<div v-if="showResults"> | ||
<n-space justify="center"> | ||
<n-form-item label="Only show differences" label-placement="left"> | ||
<n-switch v-model:value="onlyShowDifferences" /> | ||
</n-form-item> | ||
</n-space> | ||
|
||
<c-card data-test-id="diff-result"> | ||
<n-text v-if="jsonAreTheSame" depth="3" block text-center italic> The provided JSONs are the same </n-text> | ||
<diff-root-viewer v-else :diff="result" /> | ||
</c-card> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts" setup> | ||
import { useAppTheme } from '@/ui/theme/themes'; | ||
import _ from 'lodash'; | ||
import { DiffRootViewer } from './diff-viewer.models'; | ||
import { diff } from '../json-diff.models'; | ||
const onlyShowDifferences = ref(false); | ||
const props = defineProps<{ leftJson: unknown; rightJson: unknown }>(); | ||
const { leftJson, rightJson } = toRefs(props); | ||
const appTheme = useAppTheme(); | ||
const result = computed(() => | ||
diff(leftJson.value, rightJson.value, { onlyShowDifferences: onlyShowDifferences.value }), | ||
); | ||
const jsonAreTheSame = computed(() => _.isEqual(leftJson.value, rightJson.value)); | ||
const showResults = computed(() => !_.isUndefined(leftJson.value) && !_.isUndefined(rightJson.value)); | ||
</script> | ||
|
||
<style lang="less" scoped> | ||
::v-deep(.diffs-viewer) { | ||
color: v-bind('appTheme.text.mutedColor'); | ||
& > ul { | ||
padding-left: 0 !important; | ||
} | ||
ul { | ||
list-style: none; | ||
padding-left: 20px; | ||
margin: 0; | ||
li { | ||
.updated-line { | ||
padding: 3px 0; | ||
} | ||
.result, | ||
.array, | ||
.object, | ||
.value { | ||
&:not(:last-child) { | ||
margin-right: 4px; | ||
} | ||
&.added { | ||
padding: 3px 5px; | ||
border-radius: 4px; | ||
background-color: v-bind('appTheme.success.colorFaded'); | ||
color: v-bind('appTheme.success.color'); | ||
.key { | ||
color: inherit; | ||
} | ||
} | ||
&.removed { | ||
padding: 3px 5px; | ||
border-radius: 4px; | ||
background-color: v-bind('appTheme.error.colorFaded'); | ||
color: v-bind('appTheme.error.color'); | ||
.key { | ||
color: inherit; | ||
} | ||
} | ||
} | ||
.value { | ||
cursor: pointer; | ||
border: 1px solid transparent; | ||
transition: border-color 0.2s ease-in-out; | ||
&.added:hover { | ||
border-color: v-bind('appTheme.success.color'); | ||
} | ||
&.removed:hover { | ||
border-color: v-bind('appTheme.error.color'); | ||
} | ||
} | ||
.added .added, | ||
.removed .removed { | ||
background-color: transparent; | ||
color: inherit; | ||
} | ||
.key { | ||
font-weight: 500; | ||
color: v-bind('appTheme.text.baseColor'); | ||
} | ||
} | ||
} | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { CompareArrowsRound } from '@vicons/material'; | ||
import { defineTool } from '../tool'; | ||
|
||
export const tool = defineTool({ | ||
name: 'JSON diff', | ||
path: '/json-diff', | ||
description: 'Compare two JSON objects and get the differences between them.', | ||
keywords: ['json', 'diff', 'compare', 'difference', 'object', 'data'], | ||
component: () => import('./json-diff.vue'), | ||
icon: CompareArrowsRound, | ||
createdAt: new Date('2023-04-20'), | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { test, expect } from '@playwright/test'; | ||
|
||
test.describe('Tool - JSON diff', () => { | ||
test.beforeEach(async ({ page }) => { | ||
await page.goto('/json-diff'); | ||
}); | ||
|
||
test('Has correct title', async ({ page }) => { | ||
await expect(page).toHaveTitle('JSON diff - IT Tools'); | ||
}); | ||
|
||
test('Identical JSONs have a custom result message', async ({ page }) => { | ||
await page.getByTestId('leftJson').fill('{"foo":"bar"}'); | ||
await page.getByTestId('rightJson').fill('{ "foo": "bar" } '); | ||
|
||
const result = await page.getByTestId('diff-result').innerText(); | ||
|
||
expect(result).toContain('The provided JSONs are the same'); | ||
}); | ||
|
||
test('Different JSONs have differences listed', async ({ page }) => { | ||
await page.getByTestId('leftJson').fill('{"foo":"bar"}'); | ||
await page.getByTestId('rightJson').fill('{"foo":"buz","baz":"qux"}'); | ||
|
||
const result = await page.getByTestId('diff-result').innerText(); | ||
|
||
expect(result).toContain(`{\nfoo: "bar""buz",\nbaz: "qux",\n},`); | ||
}); | ||
|
||
test('Different JSONs have only differences listed when "Only show differences" is checked', async ({ page }) => { | ||
await page.getByTestId('leftJson').fill('{"foo":"bar"}'); | ||
await page.getByTestId('rightJson').fill('{"foo":"bar","baz":"qux"}'); | ||
await page.getByRole('switch').click(); | ||
|
||
const result = await page.getByTestId('diff-result').innerText(); | ||
|
||
expect(result).toContain(`{\nbaz: "qux",\n},`); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { expect, describe, it } from 'vitest'; | ||
import { diff } from './json-diff.models'; | ||
|
||
describe('json-diff models', () => { | ||
describe('diff', () => { | ||
it('list object differences', () => { | ||
const obj = { a: 1, b: 2 }; | ||
const newObj = { a: 1, b: 2, c: 3 }; | ||
const result = diff(obj, newObj); | ||
|
||
expect(result).toEqual({ | ||
key: '', | ||
type: 'object', | ||
children: [ | ||
{ | ||
key: 'a', | ||
type: 'value', | ||
value: 1, | ||
oldValue: 1, | ||
status: 'unchanged', | ||
}, | ||
{ | ||
key: 'b', | ||
type: 'value', | ||
value: 2, | ||
oldValue: 2, | ||
status: 'unchanged', | ||
}, | ||
{ | ||
key: 'c', | ||
type: 'value', | ||
value: 3, | ||
oldValue: undefined, | ||
status: 'added', | ||
}, | ||
], | ||
oldValue: { a: 1, b: 2 }, | ||
value: { a: 1, b: 2, c: 3 }, | ||
status: 'children-updated', | ||
}); | ||
}); | ||
|
||
it('list array differences', () => { | ||
const obj = [1, 2]; | ||
const newObj = [1, 2, 3]; | ||
const result = diff(obj, newObj); | ||
|
||
expect(result).toEqual({ | ||
key: '', | ||
type: 'array', | ||
children: [ | ||
{ | ||
key: 0, | ||
type: 'value', | ||
value: 1, | ||
oldValue: 1, | ||
status: 'unchanged', | ||
}, | ||
{ | ||
key: 1, | ||
type: 'value', | ||
value: 2, | ||
oldValue: 2, | ||
status: 'unchanged', | ||
}, | ||
{ | ||
key: 2, | ||
type: 'value', | ||
value: 3, | ||
oldValue: undefined, | ||
status: 'added', | ||
}, | ||
], | ||
oldValue: [1, 2], | ||
value: [1, 2, 3], | ||
status: 'children-updated', | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.
362f2fa
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
it-tools – ./
it-tools-git-main-ctmsst.vercel.app
it-tools-ctmsst.vercel.app
it-tools.vercel.app
it-tools.tech