This package provides utils to compute the diff between two Json based API documents - online demo
- Generate API changelog
- Identify breaking changes
- Ensure API versioning consistency
- OpenApi 3.0
- AsyncApi 2.1
- JsonSchema
- Swagger 2.0
- GraphQL via GraphApi
gRPC(Roadmap)
- Generate diff for supported specifications
- Generate merged spec with changes in metadata
- Classify all changes as breaking, non-breaking, annotation
- Human-readable change description (OpenApi only)
- Supports custom classification rules
- Supports custom match rules for array items and object keys
- Resolves all $ref pointers, including external and circular
- Typescript syntax support out of the box
- No dependencies, can be used in nodejs or browser
npm install api-smart-diff --save
import { apiDiff } from 'api-smart-diff'
const diffs = apiDiff(oldSpec, newSpec)
// {
// action: "add" | "remove" | "replace" | "rename",
// after: 'value in newSpec',
// before: 'value in oldSpec',
// path: ['path, 'in', 'array', 'format'],
// type: "annotation" | "breaking" | "non-breaking" | "unclassified" | "deprecated"
// }
const merged = apiMerge(oldSpec, newSpec)
A browser version of api-smart-diff
is also available via CDN:
<script src="https://cdn.jsdelivr.net/npm/api-smart-diff@latest/browser/api-smart-diff.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/api-smart-diff@latest/browser/api-smart-diff.js"></script>
Reference api-smart-diff.min.js
in your HTML and use the global variable ApiSmartDiff
.
<script>
var diffs = ApiSmartDiff.apiDiff(oldSpec, newSpec)
var merged = ApiSmartDiff.apiMerge(oldSpec, newSpec)
</script>
Package provides the following public functions:
apiDiff (before, after, options?: CompareOptions): Array<Diff>
Calculates the difference list between two objects and classify difference in accordinance with before document type: OpenApi3, AsyncApi2, JsonSchema.
apiDiffTree (before, after, options?: CompareOptions): object
Calculates the difference tree between two objects and classify difference in accordinance with before document type: OpenApi3, AsyncApi2, JsonSchema.
apiMerge (before, after, options?: MergeOptions): object
Merge two objects and inject difference as meta data.
The apiDiff function calculates the difference between two objects.
before: any
- the origin objectafter: any
- the object being compared structurally with the origin object\options: CompareOptions
[optional] - comparison options
type CompareOptions = {
rules?: Rules
trimStrings?: boolean
caseSensitive?: boolean
strictArrays?: boolean
externalRefs?: { [key: string]: any }
}
rules
- custom match and classification rulestrimString
- ignore spaces in matching, defaultfalse
caseSensitive
- ignore case in matching, defaultfalse
strictArrays
- use strict match algorithm for array items, defaultfalse
externalRefs
- object with external refs
Function returns array of differences:
type Diff = {
action: "add" | "remove" | "replace" | "rename"
path: Array<string | number>
before?: any
after?: any
type: "breaking" | "non-breaking" | "annotation" | "unclassified" | "deprecated"
}
const diffs = apiDiff(before, after)
if (diffs.length) {
// do something with the changes
}
The apiDiff function calculates the difference between two objects.
before: any
- the origin objectafter: any
- the object being compared structurally with the origin object\options: CompareOptions
[optional] - comparison options
Function returns object with $diff
key for all differences:
type Diff = {
action: "add" | "remove" | "replace" | "rename"
before?: any
after?: any
type: "breaking" | "non-breaking" | "annotation" | "unclassified" | "deprecated"
}
const diff = apiDiffTree(before, after)
// do something with the changes object
The apiDiff function calculates the difference between two objects.
before: any
- the origin objectafter: any
- the object being compared structurally with the origin object\options: MergeOptions
[optional] - comparison options
type MergeOptions<T> = CompareOptions & {
resolveUnchangedRefs?: boolean
arrayMeta?: boolean
formatMergedMeta?: (diff: T) => any
metaKey?: string | symbol
}
Additional to compare options:
arrayMeta
- inject meta to arrays for items changes, defaultfalse
resolveUnchangedRefs
- resolve refs even if no changes, defaultfalse
metaKey
- key for diff metadata, default$diff
formatMergedMeta
- custom formatting function for meta
Function returns merged object with metadata. Metadata includes merged keys and differences:
type MergedMeta = {
[key: string]: MergedKeyMeta | MergedArrayMeta
}
type MergedKeyMeta = {
type: DiffType
action: ActionType
replaced?: any
}
type MergedArrayMeta = {
array: { [key: number]: MergedArrayMeta }
}
const apiKey = Symbol("diff")
const merged = apiMerge(before, after, { apiKey })
// do something with merged object
import { apiDiff, changeDoc, changeDocOpenApiRules } from "api-smart-diff"
const diff = apiDiff(before, after, {
formatMergedMeta: (diff) => ({ ...diff, description: changeDoc(diff, before, after, changeDocOpenApiRules) })
})
Custom match and classification rules can be defined as object:
type Rules = {
// root property (or array item) rule
"/"?: Rule
// rule for all unspecified properties (or nested array items)
"/*"?: Rule | Rules | (before) => Rules
// rule for specified properties
[key: `/${string}`]?: Rule | Rules | (before) => Rules
// custom match function for object (or array)
"#"?: (before, after) => boolean
}
// Change classifier
type Rule = [
DiffType | (ctx: IChangeContext) => DiffType, // add
DiffType | (ctx: IChangeContext) => DiffType, // remove
DiffType | (ctx: IChangeContext) => DiffType // replace (rename)
]
// Current path Change context
interface IChageContext {
before: any // before value
after: any // after value
root: IChageContext // get root Change context
up: (n?: number) => IChageContext // get parent Change Context
}
Please check predefined rules in /src/rules
folder to get examples
When contributing, keep in mind that it is an objective of api-smart-diff
to have no package dependencies. This may change in the future, but for now, no-dependencies.
Please run the unit tests before submitting your PR: npm test
. Hopefully your PR includes additional unit tests to illustrate your change/modification!
MIT