diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
new file mode 100644
index 0000000..e367da5
--- /dev/null
+++ b/.github/workflows/coverage.yml
@@ -0,0 +1,44 @@
+name: coverage instrument based
+
+on: [ push, pull_request ]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v2
+      - name: Install latest nightly
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: nightly
+          override: true
+          components: rustfmt, clippy, llvm-tools-preview
+
+      - name: Install lcov
+        run: sudo apt-get install lcov
+
+      - name: install grcov
+        run: cargo install grcov
+
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+
+      - name: Run grcov
+        env:
+          PROJECT_NAME: "json-diff"
+          RUSTDOCFLAGS: "-Cinstrument-coverage -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
+          RUSTFLAGS: "-Cinstrument-coverage -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
+          CARGO_INCREMENTAL: 0
+        run: |
+          cargo +nightly build --verbose --no-default-features
+          cargo +nightly test --verbose --no-default-features
+          grcov . -s . --binary-path ./target/debug/ -t lcov --llvm --branch --ignore-not-existing --ignore="/*" --ignore="target/*" --ignore="tests/*"  -o lcov.info
+
+      - name: Push grcov results to Coveralls via GitHub Action
+        uses: coverallsapp/github-action@v1.0.1
+        with:
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          path-to-lcov: "lcov.info"
diff --git a/.gitignore b/.gitignore
index 53eaa21..bb421d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
 /target
 **/*.rs.bk
+.idea
+Cargo.lock
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..7d355e2
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,64 @@
+{
+    // Verwendet IntelliSense zum Ermitteln möglicher Attribute.
+    // Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen.
+    // Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "type": "lldb",
+            "request": "launch",
+            "name": "Debug unit tests in library 'json_diff'",
+            "cargo": {
+                "args": [
+                    "test",
+                    "--no-run",
+                    "--lib",
+                    "--package=json_diff_ng"
+                ],
+                "filter": {
+                    "name": "json_diff",
+                    "kind": "lib"
+                }
+            },
+            "args": [],
+            "cwd": "${workspaceFolder}"
+        },
+        {
+            "type": "lldb",
+            "request": "launch",
+            "name": "Debug executable 'json_diff'",
+            "cargo": {
+                "args": [
+                    "build",
+                    "--bin=json_diff",
+                    "--package=json_diff_ng"
+                ],
+                "filter": {
+                    "name": "json_diff",
+                    "kind": "bin"
+                }
+            },
+            "args": [],
+            "cwd": "${workspaceFolder}"
+        },
+        {
+            "type": "lldb",
+            "request": "launch",
+            "name": "Debug unit tests in executable 'json_diff'",
+            "cargo": {
+                "args": [
+                    "test",
+                    "--no-run",
+                    "--bin=json_diff",
+                    "--package=json_diff_ng"
+                ],
+                "filter": {
+                    "name": "json_diff",
+                    "kind": "bin"
+                }
+            },
+            "args": [],
+            "cwd": "${workspaceFolder}"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
deleted file mode 100644
index caff2f6..0000000
--- a/Cargo.lock
+++ /dev/null
@@ -1,244 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-[[package]]
-name = "ansi_term"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "atty"
-version = "0.2.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "bitflags"
-version = "1.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "clap"
-version = "2.33.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "colored"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "heck"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "itoa"
-version = "0.4.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "json_diff"
-version = "0.1.2"
-dependencies = [
- "colored 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
- "structopt 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "lazy_static"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "libc"
-version = "0.2.65"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "maplit"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "proc-macro-error"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "ryu"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "serde"
-version = "1.0.102"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "serde_json"
-version = "1.0.41"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "strsim"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "structopt"
-version = "0.3.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "structopt-derive 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "structopt-derive"
-version = "0.3.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro-error 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "syn"
-version = "1.0.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "textwrap"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "unicode-segmentation"
-version = "1.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "unicode-width"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "unicode-xid"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "vec_map"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "winapi"
-version = "0.3.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[metadata]
-"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
-"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
-"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
-"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
-"checksum colored 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "433e7ac7d511768127ed85b0c4947f47a254131e37864b2dc13f52aa32cd37e5"
-"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
-"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
-"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
-"checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8"
-"checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
-"checksum proc-macro-error 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aeccfe4d5d8ea175d5f0e4a2ad0637e0f4121d63bd99d356fb1f39ab2e7c6097"
-"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27"
-"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
-"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
-"checksum serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4b39bd9b0b087684013a792c59e3e07a46a01d2322518d8a1104641a0b1be0"
-"checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2"
-"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
-"checksum structopt 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "30b3a3e93f5ad553c38b3301c8a0a0cec829a36783f6a0c467fc4bf553a5f5bf"
-"checksum structopt-derive 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea692d40005b3ceba90a9fe7a78fa8d4b82b0ce627eebbffc329aab850f3410e"
-"checksum syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "661641ea2aa15845cddeb97dad000d22070bb5c1fb456b96c1cba883ec691e92"
-"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
-"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
-"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20"
-"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
-"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
-"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
-"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/Cargo.toml b/Cargo.toml
index a124a1b..56b6355 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,29 +1,39 @@
 [package]
-name = "json_diff"
-version = "0.1.2"
-authors = ["ksceriath"]
-edition = "2018"
+name = "json_diff_ng"
+version = "0.6.0"
+authors = ["ChrisRega", "ksceriath"]
+edition = "2021"
 license = "Unlicense"
-description = "A small diff tool utility for comparing jsons"
+description = "A JSON diff library, featuring deep-sorting and key exclusion by regex. CLI is included."
 readme = "README.md"
-homepage = "https://github.com/ksceriath/json-diff"
-repository = "https://github.com/ksceriath/json-diff"
-keywords = ["cli", "diff", "json"]
-categories = ["command-line-utilities"]
+homepage = "https://github.com/ChrisRega/json-diff"
+repository = "https://github.com/ChrisRega/json-diff"
+categories = ["command-line-utilities", "development-tools"]
+keywords = ["json-structural-diff", "json-diff", "diff", "json", "cli"]
 
 [lib]
-name = "json_diff"
+name = "json_diff_ng"
 path = "src/lib.rs"
 crate-type = ["lib"]
 
 [[bin]]
-name = "json_diff"
+name = "json_diff_ng"
 path = "src/main.rs"
+required-features = ["CLI"]
+
+[features]
+default = ["CLI"]
+CLI = ["dep:clap"]
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-serde_json = "1.0.41"
-maplit = "1.0.2"
-colored = "1.9.0"
-structopt = "0.3.5"
+thiserror = "1.0"
+vg_errortools = "0.1"
+serde_json = { version = "1.0", features = ["preserve_order"] }
+diffs = "0.5"
+regex = "1.10"
+clap = { version = "4.5", features = ["derive"], optional = true }
+
+[dev-dependencies]
+maplit = "1.0"
diff --git a/README.md b/README.md
index 0cb5852..03f239a 100644
--- a/README.md
+++ b/README.md
@@ -1,31 +1,59 @@
-# json-diff
+# json-diff-ng
 
-json-diff is a command line utility to compare two jsons.  
+[![Crates.io](https://img.shields.io/crates/d/json_diff_ng?style=flat)](https://crates.io/crates/json_diff_ng)
+[![Documentation](https://docs.rs/json_diff_ng/badge.svg)](https://docs.rs/json_diff_ng)
+![CI](https://github.com/ChrisRega/json-diff/actions/workflows/rust.yml/badge.svg?branch=master "CI")
+[![Coverage Status](https://coveralls.io/repos/github/ChrisRega/json-diff/badge.svg?branch=master)](https://coveralls.io/github/ChrisRega/json-diff?branch=master)
+[![License](https://img.shields.io/github/license/ChrisRega/json-diff)](LICENSE)
 
-Input can be fed as inline strings or through files.  
-For readability, output is neatly differentiated into three categories: keys with different values, and keys not present in either of the objects.  
-Only missing or unequal keys are printed in output to reduce the verbosity.
+## Contributors:
+
+<a href="https://github.com/ChrisRega/json-diff/graphs/contributors">
+  <img src="https://contrib.rocks/image?repo=ChrisRega/json-diff"  alt="Contributors"/>
+</a>
+
+## Library
+
+json_diff_ng can be used to get diffs of json-serializable structures in rust.
+
+### Usage example
 
-## Screenshot of diff results
+```rust
+use json_diff::compare_strs;
+let data1 = r#"["a",{"c": ["d","f"] },"b"]"#;
+let data2 = r#"["b",{"c": ["e","d"] },"a"]"#;
+let diffs = compare_strs(data1, data2, true, & []).unwrap();
+assert!(!diffs.is_empty());
+let diffs = diffs.unequal_values.get_diffs();
+assert_eq!(diffs.len(), 1);
+assert_eq!(
+    diffs.first().unwrap().to_string(),
+    r#".[0].c.[1].("f" != "e")"#
+);
+```
 
-[![A screenshot of a sample diff with json_diff](https://github.com/ksceriath/json-diff/blob/master/Screenshot.png)](https://github.com/ksceriath/json-diff/blob/master/Screenshot.png)
+See [docs.rs](https://docs.rs/json_diff_ng) for more details.
+
+## CLI
+
+json-diff is a command line utility to compare two jsons.
+
+Input can be fed as inline strings or through files.  
+For readability, output is neatly differentiated into three categories: keys with different values, and keys not present
+in either of the objects.  
+Only missing or unequal keys are printed in output to reduce the verbosity.
 
 Usage Example:
 
-`$ json_diff f source1.json source2.json`  
-`$ json_diff d '{...}' '{...}'`
+`$ json_diff file source1.json source2.json`  
+`$ json_diff direct '{...}' '{...}'`
 
 Option:
 
-f   :   read input from json files  
-d   :   read input from command line
+file   :   read input from json files  
+direct   :   read input from command line
 
 ### Installation
 
-Currently, json-diff is available through crates.io (apart from building this repo directly). For crate installation,  
-* Install cargo, through rustup  
-`$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh`  
-* Install json-diff  
-`$ cargo install json_diff`
-
+`$ cargo install json_diff_ng`
 
diff --git a/src/constants.rs b/src/constants.rs
deleted file mode 100644
index 1074046..0000000
--- a/src/constants.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-use colored::*;
-use std::fmt;
-
-// PartialEq is added for the sake of Test case that uses assert_eq
-#[derive(Debug, PartialEq)]
-pub enum Message {
-    BadOption,
-    SOURCE1,
-    SOURCE2,
-    JSON1,
-    JSON2,
-    UnknownError,
-    NoMismatch,
-    RootMismatch,
-    LeftExtra,
-    RightExtra,
-    Mismatch,
-}
-
-impl fmt::Display for Message {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let message = match self {
-            Message::BadOption => "Invalid option.",
-            Message::SOURCE1 => "Could not read source1.",
-            Message::SOURCE2 => "Could not read source2.",
-            Message::JSON1 => "Could not parse source1.",
-            Message::JSON2 => "Could not parse source2.",
-            Message::UnknownError => "",
-            Message::NoMismatch => "No mismatch was found.",
-            Message::RootMismatch => "Mismatch at root.",
-            Message::LeftExtra => "Extra on left",
-            Message::RightExtra => "Extra on right",
-            Message::Mismatch => "Mismatched",
-        };
-
-        write!(f, "{}", message.bold())
-    }
-}
diff --git a/src/ds/key_node.rs b/src/ds/key_node.rs
deleted file mode 100644
index 20fb04c..0000000
--- a/src/ds/key_node.rs
+++ /dev/null
@@ -1,40 +0,0 @@
-use colored::*;
-use serde_json::Value;
-use std::collections::HashMap;
-
-#[derive(Debug, PartialEq)] // TODO check: do we need PartiaEq ?
-pub enum KeyNode {
-    Nil,
-    Value(Value, Value),
-    Node(HashMap<String, KeyNode>),
-}
-
-impl KeyNode {
-    pub fn absolute_keys(&self, keys: &mut Vec<String>, key_from_root: Option<String>) {
-        let val_key = |key: Option<String>| {
-            key.map(|mut s| {
-                s.push_str(" ->");
-                s
-            })
-            .unwrap_or(String::new())
-        };
-        let nil_key = |key: Option<String>| key.unwrap_or(String::new());
-        match self {
-            KeyNode::Nil => keys.push(nil_key(key_from_root)),
-            KeyNode::Value(a, b) => keys.push(format!(
-                "{} [ {} :: {} ]",
-                val_key(key_from_root),
-                a.to_string().blue().bold(),
-                b.to_string().cyan().bold()
-            )),
-            KeyNode::Node(map) => {
-                for (key, value) in map {
-                    value.absolute_keys(
-                        keys,
-                        Some(format!("{} {}", val_key(key_from_root.clone()), key)),
-                    )
-                }
-            }
-        }
-    }
-}
diff --git a/src/ds/mismatch.rs b/src/ds/mismatch.rs
deleted file mode 100644
index 649739f..0000000
--- a/src/ds/mismatch.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-use crate::ds::key_node::KeyNode;
-
-#[derive(Debug, PartialEq)]
-pub struct Mismatch {
-    pub left_only_keys: KeyNode,
-    pub right_only_keys: KeyNode,
-    pub keys_in_both: KeyNode,
-}
-
-impl Mismatch {
-    pub fn new(l: KeyNode, r: KeyNode, u: KeyNode) -> Mismatch {
-        Mismatch {
-            left_only_keys: l,
-            right_only_keys: r,
-            keys_in_both: u,
-        }
-    }
-}
diff --git a/src/ds/mod.rs b/src/ds/mod.rs
deleted file mode 100644
index c61babc..0000000
--- a/src/ds/mod.rs
+++ /dev/null
@@ -1,2 +0,0 @@
-pub mod key_node;
-pub mod mismatch;
diff --git a/src/enums.rs b/src/enums.rs
new file mode 100644
index 0000000..2e95d9e
--- /dev/null
+++ b/src/enums.rs
@@ -0,0 +1,196 @@
+use std::collections::HashMap;
+use std::fmt::{Display, Formatter};
+
+use serde_json::Value;
+use thiserror::Error;
+use vg_errortools::FatIOError;
+
+#[derive(Debug, Error)]
+pub enum Error {
+    #[error("Misc error: {0}")]
+    Misc(String),
+    #[error("Error opening file: {0}")]
+    IOError(#[from] FatIOError),
+    #[error("Error parsing first json: {0}")]
+    JSON(#[from] serde_json::Error),
+    #[error("Regex compilation error: {0}")]
+    Regex(#[from] regex::Error),
+}
+
+impl From<String> for Error {
+    fn from(value: String) -> Self {
+        Self::Misc(value)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum DiffTreeNode {
+    Null,
+    Value(Value, Value),
+    Node(HashMap<String, DiffTreeNode>),
+    Array(Vec<(usize, DiffTreeNode)>),
+}
+
+impl<'a> DiffTreeNode {
+    pub fn get_diffs(&'a self) -> Vec<DiffEntry<'a>> {
+        let mut buf = Vec::new();
+        self.follow_path(&mut buf, &[]);
+        buf
+    }
+
+    pub fn follow_path<'b>(
+        &'a self,
+        diffs: &mut Vec<DiffEntry<'a>>,
+        offset: &'b [PathElement<'a>],
+    ) {
+        match self {
+            DiffTreeNode::Null => {
+                let is_map_child = offset
+                    .last()
+                    .map(|o| matches!(o, PathElement::Object(_)))
+                    .unwrap_or_default();
+                if is_map_child {
+                    diffs.push(DiffEntry {
+                        path: offset.to_vec(),
+                        values: None,
+                    });
+                }
+            }
+            DiffTreeNode::Value(l, r) => diffs.push(DiffEntry {
+                path: offset.to_vec(),
+                values: Some((l, r)),
+            }),
+            DiffTreeNode::Node(o) => {
+                for (k, v) in o {
+                    let mut new_offset = offset.to_vec();
+                    new_offset.push(PathElement::Object(k));
+                    v.follow_path(diffs, &new_offset);
+                }
+            }
+            DiffTreeNode::Array(v) => {
+                for (l, k) in v {
+                    let mut new_offset = offset.to_vec();
+                    new_offset.push(PathElement::ArrayEntry(*l));
+                    k.follow_path(diffs, &new_offset);
+                }
+            }
+        }
+    }
+}
+
+#[derive(Debug)]
+pub enum DiffType {
+    RootMismatch,
+    LeftExtra,
+    RightExtra,
+    Mismatch,
+}
+
+impl Display for DiffType {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        let msg = match self {
+            DiffType::RootMismatch => "Mismatch at root.",
+            DiffType::LeftExtra => "Extra on left",
+            DiffType::RightExtra => "Extra on right",
+            DiffType::Mismatch => "Mismatched",
+        };
+        write!(f, "{}", msg)
+    }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum PathElement<'a> {
+    Object(&'a str),
+    ArrayEntry(usize),
+}
+
+impl<'a> PathElement<'a> {
+    pub fn resolve<'b>(&self, v: &'b serde_json::Value) -> Option<&'b serde_json::Value> {
+        match self {
+            PathElement::Object(o) => v.get(o),
+            PathElement::ArrayEntry(i) => v.get(*i),
+        }
+    }
+
+    pub fn resolve_mut<'b>(
+        &self,
+        v: &'b mut serde_json::Value,
+    ) -> Option<&'b mut serde_json::Value> {
+        match self {
+            PathElement::Object(o) => v.get_mut(o),
+            PathElement::ArrayEntry(i) => v.get_mut(*i),
+        }
+    }
+}
+
+/// A view on a single end-node of the [`DiffTreeNode`] tree.
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct DiffEntry<'a> {
+    pub path: Vec<PathElement<'a>>,
+    pub values: Option<(&'a serde_json::Value, &'a serde_json::Value)>,
+}
+
+impl<'a> DiffEntry<'a> {
+    pub fn resolve<'b>(&'a self, value: &'b serde_json::Value) -> Option<&'b serde_json::Value> {
+        let mut return_value = value;
+        for a in &self.path {
+            return_value = a.resolve(return_value)?;
+        }
+        Some(return_value)
+    }
+}
+
+impl Display for DiffEntry<'_> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        for element in &self.path {
+            write!(f, ".{element}")?;
+        }
+        if let Some((l, r)) = &self.values {
+            if l != r {
+                write!(f, ".({l} != {r})")?;
+            } else {
+                write!(f, ".({l})")?;
+            }
+        }
+        Ok(())
+    }
+}
+
+impl Display for PathElement<'_> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        match self {
+            PathElement::Object(o) => {
+                write!(f, "{o}")
+            }
+            PathElement::ArrayEntry(l) => {
+                write!(f, "[{l}]")
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use serde_json::json;
+
+    use crate::compare_serde_values;
+    use crate::sort::sort_value;
+
+    #[test]
+    fn test_resolve() {
+        let data1 = json! {["a",{"c": ["d","f"] },"b"]};
+        let data2 = json! {["b",{"c": ["e","d"] },"a"]};
+        let diffs = compare_serde_values(&data1, &data2, true, &[]).unwrap();
+        assert!(!diffs.is_empty());
+        let data1_sorted = sort_value(&data1, &[]);
+        let data2_sorted = sort_value(&data2, &[]);
+
+        let all_diffs = diffs.all_diffs();
+        assert_eq!(all_diffs.len(), 1);
+        let (_type, diff) = all_diffs.first().unwrap();
+        let val = diff.resolve(&data1_sorted);
+        assert_eq!(val.unwrap().as_str().unwrap(), "f");
+        let val = diff.resolve(&data2_sorted);
+        assert_eq!(val.unwrap().as_str().unwrap(), "e");
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 339800d..f181439 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,76 @@
-pub mod constants;
-pub mod ds;
+//! # Library for comparing JSON data structures
+//! ## Summary
+//! Main entry points are [`compare_strs`] to compare string slices and [`compare_serde_values`] to compare already parse [`serde_json::Value`]
+//! ## Example:
+//! ```rust
+//! use json_diff_ng::compare_strs;
+//! let data1 = r#"["a",{"c": ["d","f"] },"b"]"#;
+//! let data2 = r#"["b",{"c": ["e","d"] },"a"]"#;
+//! let diffs = compare_strs(data1, data2, true, &[]).unwrap();
+//! assert!(!diffs.is_empty());
+//! let diffs = diffs.unequal_values.get_diffs();
+//!
+//! assert_eq!(diffs.len(), 1);
+//! assert_eq!(
+//!   diffs.first().unwrap().to_string(),
+//!   r#".[0].c.[1].("f" != "e")"#
+//! );
+//! ```
+//! ## How to handle the results
+//! Results are returned in a triple of [`DiffTreeNode`] called [`Mismatch`].
+//! The triple consists of values only on the left side, values only on the right side and values on both sides that differ.
+//! Since tree traversal is not usually what you want to do on client side, [`DiffTreeNode`] offers [`DiffTreeNode::get_diffs`] to retrieve
+//! a flat list of [`DiffEntry`] which is more easily usable. The path in the json is collapsed into a vector of [`PathElement`] which can be used to follow the diff.
+//! Similarly, all diffs after an operation can be collected using [`Mismatch::all_diffs`].
+//!
+//! ### Just print everything
+//!
+//! ```rust
+//! use serde_json::json;
+//! use json_diff_ng::compare_serde_values;
+//! use json_diff_ng::sort::sort_value;
+//! let data1 = json! {["a",{"c": ["d","f"] },"b"]};
+//! let data2 = json! {["b",{"c": ["e","d"] },"a"]};
+//! let diffs = compare_serde_values(&data1, &data2, true, &[]).unwrap();
+//! for (d_type, d_path) in diffs.all_diffs() {
+//!   let _message = format!("{d_type}: {d_path}");
+//! }
+//! ```
+//!
+//! ### Traversing the diff result JSONs
+//! ```rust
+//! use serde_json::json;
+//! use json_diff_ng::compare_serde_values;
+//! use json_diff_ng::sort::sort_value;
+//! let data1 = json! {["a",{"c": ["d","f"] },"b"]};
+//! let data2 = json! {["b",{"c": ["e","d"] },"a"]};
+//! let diffs = compare_serde_values(&data1, &data2, true, &[]).unwrap();
+//! assert!(!diffs.is_empty());
+//! // since we sorted for comparison, if we want to resolve the path, we need a sorted result as well.
+//! let data1_sorted = sort_value(&data1, &[]);
+//! let data2_sorted = sort_value(&data2, &[]);
+//! let all_diffs = diffs.all_diffs();
+//! assert_eq!(all_diffs.len(), 1);
+//! let (_type, diff) = all_diffs.first().unwrap();
+//! let val = diff.resolve(&data1_sorted);
+//! assert_eq!(val.unwrap().as_str().unwrap(), "f");
+//! let val = diff.resolve(&data2_sorted);
+//! assert_eq!(val.unwrap().as_str().unwrap(), "e");
+//! ```
+//!
+
+pub use enums::DiffEntry;
+pub use enums::DiffTreeNode;
+pub use enums::DiffType;
+pub use enums::Error;
+pub use enums::PathElement;
+pub use mismatch::Mismatch;
+pub use process::compare_serde_values;
+pub use process::compare_strs;
+
+pub mod enums;
+pub mod mismatch;
 pub mod process;
+pub mod sort;
+
+pub type Result<T> = std::result::Result<T, Error>;
diff --git a/src/main.rs b/src/main.rs
index 135b5fc..31b0497 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,145 +1,70 @@
-use std::{
-    fmt, fs,
-    io::{self, Write},
-    process as proc,
-    str::FromStr,
-};
+use clap::Parser;
+use clap::Subcommand;
 
-use colored::*;
-use structopt::StructOpt;
+use json_diff_ng::{compare_strs, Mismatch, Result};
 
-use json_diff::{
-    constants::Message,
-    ds::{key_node::KeyNode, mismatch::Mismatch},
-    process::compare_jsons,
-};
-
-const HELP: &str = r#"
-Example:
-json_diff f source1.json source2.json
-json_diff d '{...}' '{...}'
-
-Option:
-f   :   read input from json files
-d   :   read input from command line"#;
-
-#[derive(Debug)]
-struct AppError {
-    message: Message,
-}
-impl fmt::Display for AppError {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "{}", self.message)
-    }
+#[derive(Subcommand, Clone)]
+/// Input selection
+enum Mode {
+    /// File input
+    #[clap(short_flag = 'f')]
+    File { file_1: String, file_2: String },
+    /// Read from CLI
+    #[clap(short_flag = 'd')]
+    Direct { json_1: String, json_2: String },
 }
 
-enum InputReadMode {
-    D,
-    F,
-}
-impl FromStr for InputReadMode {
-    type Err = AppError;
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        match s {
-            "d" => Ok(InputReadMode::D),
-            "f" => Ok(InputReadMode::F),
-            _ => Err(Self::Err {
-                message: Message::BadOption,
-            }),
-        }
-    }
-}
+#[derive(Parser)]
+struct Args {
+    #[command(subcommand)]
+    cmd: Mode,
 
-#[derive(StructOpt)]
-#[structopt(about = HELP)]
-struct Cli {
-    read_mode: InputReadMode,
-    source1: String,
-    source2: String,
-}
+    #[clap(short, long)]
+    /// deep-sort arrays before comparing
+    sort_arrays: bool,
 
-fn main() {
-    let args = Cli::from_args();
+    #[clap(short, long)]
+    /// Exclude a given list of keys by regex.
+    exclude_keys: Option<Vec<String>>,
+}
 
-    let (data1, data2) = match args.read_mode {
-        InputReadMode::D => (args.source1, args.source2),
-        InputReadMode::F => {
-            if let Ok(d1) = fs::read_to_string(args.source1) {
-                if let Ok(d2) = fs::read_to_string(args.source2) {
-                    (d1, d2)
-                } else {
-                    error_exit(Message::SOURCE2);
-                }
-            } else {
-                error_exit(Message::SOURCE1);
-            }
+fn main() -> Result<()> {
+    let args = Args::parse();
+    println!("Getting input");
+    let (json_1, json_2) = match args.cmd {
+        Mode::Direct { json_2, json_1 } => (json_1, json_2),
+        Mode::File { file_2, file_1 } => {
+            let d1 = vg_errortools::fat_io_wrap_std(file_1, &std::fs::read_to_string)?;
+            let d2 = vg_errortools::fat_io_wrap_std(file_2, &std::fs::read_to_string)?;
+            (d1, d2)
         }
     };
-    let mismatch = match compare_jsons(&data1, &data2) {
-        Ok(mismatch) => mismatch,
-        Err(err) => {
-            eprintln!("{}", err);
-            proc::exit(1)
-        }
-    };
-    match display_output(mismatch) {
-        Ok(_) => (),
-        Err(err) => eprintln!("{}", err),
-    };
-}
-
-fn error_exit(message: Message) -> ! {
-    eprintln!("{}", message);
-    proc::exit(1);
+    println!("Evaluation exclusion regex list");
+    let exclusion_keys = args
+        .exclude_keys
+        .as_ref()
+        .map(|v| {
+            v.iter()
+                .map(|k| regex::Regex::new(k).map_err(|e| e.into()))
+                .collect::<Result<Vec<regex::Regex>>>()
+                .unwrap_or_default()
+        })
+        .unwrap_or_default();
+    println!("Comparing");
+    let mismatch = compare_strs(&json_1, &json_2, args.sort_arrays, &exclusion_keys)?;
+    println!("Printing results");
+    let comparison_result = check_diffs(mismatch)?;
+    if !comparison_result {
+        std::process::exit(1);
+    }
+    Ok(())
 }
 
-pub fn display_output(result: Mismatch) -> Result<(), std::io::Error> {
-    let no_mismatch = Mismatch {
-        left_only_keys: KeyNode::Nil,
-        right_only_keys: KeyNode::Nil,
-        keys_in_both: KeyNode::Nil,
-    };
-
-    let stdout = io::stdout();
-    let mut handle = io::BufWriter::new(stdout.lock());
-    Ok(if no_mismatch == result {
-        writeln!(handle, "\n{}", Message::NoMismatch)?;
-    } else {
-        match result.keys_in_both {
-            KeyNode::Node(_) => {
-                let mut keys = Vec::new();
-                result.keys_in_both.absolute_keys(&mut keys, None);
-                writeln!(handle, "\n{}:", Message::Mismatch)?;
-                for key in keys {
-                    writeln!(handle, "{}", key)?;
-                }
-            }
-            KeyNode::Value(_, _) => writeln!(handle, "{}", Message::RootMismatch)?,
-            KeyNode::Nil => (),
-        }
-        match result.left_only_keys {
-            KeyNode::Node(_) => {
-                let mut keys = Vec::new();
-                result.left_only_keys.absolute_keys(&mut keys, None);
-                writeln!(handle, "\n{}:", Message::LeftExtra)?;
-                for key in keys {
-                    writeln!(handle, "{}", key.red().bold())?;
-                }
-            }
-            KeyNode::Value(_, _) => (),
-            KeyNode::Nil => (),
-        }
-        match result.right_only_keys {
-            KeyNode::Node(_) => {
-                let mut keys = Vec::new();
-                result.right_only_keys.absolute_keys(&mut keys, None);
-                writeln!(handle, "\n{}:", Message::RightExtra)?;
-                for key in keys {
-                    writeln!(handle, "{}", key.green().bold())?;
-                }
-            }
-            KeyNode::Value(_, _) => (),
-            KeyNode::Nil => (),
-        }
-    })
+pub fn check_diffs(result: Mismatch) -> Result<bool> {
+    let mismatches = result.all_diffs();
+    let is_good = mismatches.is_empty();
+    for (d_type, key) in mismatches {
+        println!("{d_type}: {key}");
+    }
+    Ok(is_good)
 }
diff --git a/src/mismatch.rs b/src/mismatch.rs
new file mode 100644
index 0000000..8d1da21
--- /dev/null
+++ b/src/mismatch.rs
@@ -0,0 +1,67 @@
+use crate::enums::{DiffEntry, DiffType};
+use crate::DiffTreeNode;
+
+/// Structure holding the differences after a compare operation.
+/// For more readable access use the [`Mismatch::all_diffs`] method that yields a [`DiffEntry`] per diff.
+#[derive(Debug, PartialEq)]
+pub struct Mismatch {
+    pub left_only: DiffTreeNode,
+    pub right_only: DiffTreeNode,
+    pub unequal_values: DiffTreeNode,
+}
+
+impl Mismatch {
+    pub fn new(l: DiffTreeNode, r: DiffTreeNode, u: DiffTreeNode) -> Mismatch {
+        Mismatch {
+            left_only: l,
+            right_only: r,
+            unequal_values: u,
+        }
+    }
+
+    pub fn empty() -> Self {
+        Mismatch {
+            left_only: DiffTreeNode::Null,
+            unequal_values: DiffTreeNode::Null,
+            right_only: DiffTreeNode::Null,
+        }
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.left_only == DiffTreeNode::Null
+            && self.unequal_values == DiffTreeNode::Null
+            && self.right_only == DiffTreeNode::Null
+    }
+
+    pub fn all_diffs(&self) -> Vec<(DiffType, DiffEntry)> {
+        let both = self
+            .unequal_values
+            .get_diffs()
+            .into_iter()
+            .map(|k| (DiffType::Mismatch, k));
+        let left = self
+            .left_only
+            .get_diffs()
+            .into_iter()
+            .map(|k| (DiffType::LeftExtra, k));
+        let right = self
+            .right_only
+            .get_diffs()
+            .into_iter()
+            .map(|k| (DiffType::RightExtra, k));
+
+        both.chain(left).chain(right).collect()
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn empty_diffs() {
+        let empty = Mismatch::empty();
+        let all_diffs = empty.all_diffs();
+        assert!(all_diffs.is_empty());
+    }
+}
diff --git a/src/process.rs b/src/process.rs
index c34e627..ee56f97 100644
--- a/src/process.rs
+++ b/src/process.rs
@@ -1,129 +1,529 @@
 use std::collections::HashMap;
 use std::collections::HashSet;
 
+use diffs::{Diff, myers, Replace};
+use regex::Regex;
 use serde_json::Map;
 use serde_json::Value;
 
-use crate::constants::Message;
-use crate::ds::key_node::KeyNode;
-use crate::ds::mismatch::Mismatch;
-
-pub fn compare_jsons(a: &str, b: &str) -> Result<Mismatch, Message> {
-    let value1 = match serde_json::from_str(a) {
-        Ok(val1) => val1,
-        Err(_) => return Err(Message::JSON1),
-    };
-    let value2 = match serde_json::from_str(b) {
-        Ok(val2) => val2,
-        Err(_) => return Err(Message::JSON2),
-    };
-    Ok(match_json(&value1, &value2))
+use crate::DiffTreeNode;
+use crate::Mismatch;
+use crate::Result;
+use crate::sort::preprocess_array;
+
+/// Compares two string slices containing serialized json with each other, returns an error or a [`Mismatch`] structure holding all differences.
+/// Internally this calls into [`compare_serde_values`] after deserializing the string slices into [`serde_json::Value`].
+/// Arguments are the string slices, a bool to trigger deep sorting of arrays and ignored_keys as a list of regex to match keys against.
+/// Ignoring a regex from comparison will also ignore the key from having an impact on sorting arrays.
+pub fn compare_strs(
+    a: &str,
+    b: &str,
+    sort_arrays: bool,
+    ignore_keys: &[Regex],
+) -> Result<Mismatch> {
+    let value1 = serde_json::from_str(a)?;
+    let value2 = serde_json::from_str(b)?;
+    compare_serde_values(&value1, &value2, sort_arrays, ignore_keys)
 }
 
-pub fn match_json(value1: &Value, value2: &Value) -> Mismatch {
-    match (value1, value2) {
-        (Value::Object(a), Value::Object(b)) => {
-            let (left_only_keys, right_only_keys, intersection_keys) = intersect_maps(&a, &b);
-
-            let mut unequal_keys = KeyNode::Nil;
-            let mut left_only_keys = get_map_of_keys(left_only_keys);
-            let mut right_only_keys = get_map_of_keys(right_only_keys);
-
-            if let Some(intersection_keys) = intersection_keys {
-                for key in intersection_keys {
-                    let Mismatch {
-                        left_only_keys: l,
-                        right_only_keys: r,
-                        keys_in_both: u,
-                    } = match_json(&a.get(&key).unwrap(), &b.get(&key).unwrap());
-                    left_only_keys = insert_child_key_map(left_only_keys, l, &key);
-                    right_only_keys = insert_child_key_map(right_only_keys, r, &key);
-                    unequal_keys = insert_child_key_map(unequal_keys, u, &key);
-                }
-            }
-            Mismatch::new(left_only_keys, right_only_keys, unequal_keys)
+/// Compares two [`serde_json::Value`] items with each other, returns an error or a [`Mismatch`] structure holding all differences.
+/// Arguments are the values, a bool to trigger deep sorting of arrays and ignored_keys as a list of regex to match keys against.
+/// Ignoring a regex from comparison will also ignore the key from having an impact on sorting arrays.
+pub fn compare_serde_values(
+    a: &Value,
+    b: &Value,
+    sort_arrays: bool,
+    ignore_keys: &[Regex],
+) -> Result<Mismatch> {
+    match_json(a, b, sort_arrays, ignore_keys)
+}
+
+fn values_to_node(vec: Vec<(usize, &Value)>) -> DiffTreeNode {
+    if vec.is_empty() {
+        DiffTreeNode::Null
+    } else {
+        DiffTreeNode::Array(
+            vec.into_iter()
+                .map(|(l, v)| (l, DiffTreeNode::Value(v.clone(), v.clone())))
+                .collect(),
+        )
+    }
+}
+
+struct ListDiffHandler<'a> {
+    replaced: &'a mut Vec<(usize, usize, usize, usize)>,
+    deletion: &'a mut Vec<(usize, usize)>,
+    insertion: &'a mut Vec<(usize, usize)>,
+}
+impl<'a> ListDiffHandler<'a> {
+    pub fn new(
+        replaced: &'a mut Vec<(usize, usize, usize, usize)>,
+        deletion: &'a mut Vec<(usize, usize)>,
+        insertion: &'a mut Vec<(usize, usize)>,
+    ) -> Self {
+        Self {
+            replaced,
+            deletion,
+            insertion,
         }
-        (a, b) => {
-            if a == b {
-                Mismatch::new(KeyNode::Nil, KeyNode::Nil, KeyNode::Nil)
-            } else {
-                Mismatch::new(
-                    KeyNode::Nil,
-                    KeyNode::Nil,
-                    KeyNode::Value(a.clone(), b.clone()),
-                )
-            }
+    }
+}
+impl<'a> Diff for ListDiffHandler<'a> {
+    type Error = ();
+    fn delete(&mut self, old: usize, len: usize, _new: usize) -> std::result::Result<(), ()> {
+        self.deletion.push((old, len));
+        Ok(())
+    }
+    fn insert(&mut self, _o: usize, new: usize, len: usize) -> std::result::Result<(), ()> {
+        self.insertion.push((new, len));
+        Ok(())
+    }
+    fn replace(
+        &mut self,
+        old: usize,
+        len: usize,
+        new: usize,
+        new_len: usize,
+    ) -> std::result::Result<(), ()> {
+        self.replaced.push((old, len, new, new_len));
+        Ok(())
+    }
+}
+
+fn match_json(
+    value1: &Value,
+    value2: &Value,
+    sort_arrays: bool,
+    ignore_keys: &[Regex],
+) -> Result<Mismatch> {
+    match (value1, value2) {
+        (Value::Object(a), Value::Object(b)) => process_objects(a, b, ignore_keys, sort_arrays),
+        (Value::Array(a), Value::Array(b)) => process_arrays(sort_arrays, a, ignore_keys, b),
+        (a, b) => process_values(a, b),
+    }
+}
+
+fn process_values(a: &Value, b: &Value) -> Result<Mismatch> {
+    if a == b {
+        Ok(Mismatch::empty())
+    } else {
+        Ok(Mismatch::new(
+            DiffTreeNode::Null,
+            DiffTreeNode::Null,
+            DiffTreeNode::Value(a.clone(), b.clone()),
+        ))
+    }
+}
+
+fn process_objects(
+    a: &Map<String, Value>,
+    b: &Map<String, Value>,
+    ignore_keys: &[Regex],
+    sort_arrays: bool,
+) -> Result<Mismatch> {
+    let diff = intersect_maps(a, b, ignore_keys);
+    let mut left_only_keys = get_map_of_keys(diff.left_only);
+    let mut right_only_keys = get_map_of_keys(diff.right_only);
+    let intersection_keys = diff.intersection;
+
+    let mut unequal_keys = DiffTreeNode::Null;
+
+    for key in intersection_keys {
+        let Mismatch {
+            left_only: l,
+            right_only: r,
+            unequal_values: u,
+        } = match_json(
+            a.get(&key).unwrap(),
+            b.get(&key).unwrap(),
+            sort_arrays,
+            ignore_keys,
+        )?;
+        left_only_keys = insert_child_key_map(left_only_keys, l, &key)?;
+        right_only_keys = insert_child_key_map(right_only_keys, r, &key)?;
+        unequal_keys = insert_child_key_map(unequal_keys, u, &key)?;
+    }
+
+    Ok(Mismatch::new(left_only_keys, right_only_keys, unequal_keys))
+}
+
+fn process_arrays(
+    sort_arrays: bool,
+    a: &Vec<Value>,
+    ignore_keys: &[Regex],
+    b: &Vec<Value>,
+) -> Result<Mismatch> {
+    let a = preprocess_array(sort_arrays, a, ignore_keys);
+    let b = preprocess_array(sort_arrays, b, ignore_keys);
+
+    let mut replaced = Vec::new();
+    let mut deleted = Vec::new();
+    let mut inserted = Vec::new();
+
+    let mut diff = Replace::new(ListDiffHandler::new(
+        &mut replaced,
+        &mut deleted,
+        &mut inserted,
+    ));
+    myers::diff(
+        &mut diff,
+        a.as_slice(),
+        0,
+        a.len(),
+        b.as_slice(),
+        0,
+        b.len(),
+    )
+    .unwrap();
+
+    fn extract_one_sided_values(v: Vec<(usize, usize)>, vals: &[Value]) -> Vec<(usize, &Value)> {
+        v.into_iter()
+            .flat_map(|(o, ol)| (o..o + ol).map(|i| (i, &vals[i])))
+            .collect::<Vec<(usize, &Value)>>()
+    }
+
+    let left_only_values: Vec<_> = extract_one_sided_values(deleted, a.as_slice());
+    let right_only_values: Vec<_> = extract_one_sided_values(inserted, b.as_slice());
+
+    let mut left_only_nodes = values_to_node(left_only_values);
+    let mut right_only_nodes = values_to_node(right_only_values);
+    let mut diff = DiffTreeNode::Null;
+
+    for (o, ol, n, nl) in replaced {
+        let max_length = ol.max(nl);
+        for i in 0..max_length {
+            let inner_a = a.get(o + i).unwrap_or(&Value::Null);
+            let inner_b = b.get(n + i).unwrap_or(&Value::Null);
+            let cdiff = match_json(inner_a, inner_b, sort_arrays, ignore_keys)?;
+            let position = o + i;
+            let Mismatch {
+                left_only: l,
+                right_only: r,
+                unequal_values: u,
+            } = cdiff;
+            left_only_nodes = insert_child_key_diff(left_only_nodes, l, position)?;
+            right_only_nodes = insert_child_key_diff(right_only_nodes, r, position)?;
+            diff = insert_child_key_diff(diff, u, position)?;
         }
     }
+
+    Ok(Mismatch::new(left_only_nodes, right_only_nodes, diff))
 }
 
-fn get_map_of_keys(set: Option<HashSet<String>>) -> KeyNode {
-    if let Some(set) = set {
-        KeyNode::Node(
+fn get_map_of_keys(set: HashSet<String>) -> DiffTreeNode {
+    if !set.is_empty() {
+        DiffTreeNode::Node(
             set.iter()
-                .map(|key| (String::from(key), KeyNode::Nil))
+                .map(|key| (String::from(key), DiffTreeNode::Null))
                 .collect(),
         )
     } else {
-        KeyNode::Nil
+        DiffTreeNode::Null
+    }
+}
+
+fn insert_child_key_diff(
+    parent: DiffTreeNode,
+    child: DiffTreeNode,
+    line: usize,
+) -> Result<DiffTreeNode> {
+    if child == DiffTreeNode::Null {
+        return Ok(parent);
+    }
+    if let DiffTreeNode::Array(mut array) = parent {
+        array.push((line, child));
+        Ok(DiffTreeNode::Array(array))
+    } else if let DiffTreeNode::Null = parent {
+        Ok(DiffTreeNode::Array(vec![(line, child)]))
+    } else {
+        Err(format!("Tried to insert child: {child:?} into parent {parent:?} - structure incoherent, expected a parent array - somehow json structure seems broken").into())
     }
 }
 
-fn insert_child_key_map(parent: KeyNode, child: KeyNode, key: &String) -> KeyNode {
-    if child == KeyNode::Nil {
-        return parent;
+fn insert_child_key_map(
+    parent: DiffTreeNode,
+    child: DiffTreeNode,
+    key: &String,
+) -> Result<DiffTreeNode> {
+    if child == DiffTreeNode::Null {
+        return Ok(parent);
     }
-    if let KeyNode::Node(mut map) = parent {
+    if let DiffTreeNode::Node(mut map) = parent {
         map.insert(String::from(key), child);
-        KeyNode::Node(map) // This is weird! I just wanted to return back `parent` here
-    } else if let KeyNode::Nil = parent {
+        Ok(DiffTreeNode::Node(map))
+    } else if let DiffTreeNode::Null = parent {
         let mut map = HashMap::new();
         map.insert(String::from(key), child);
-        KeyNode::Node(map)
+        Ok(DiffTreeNode::Node(map))
     } else {
-        parent // TODO Trying to insert child node in a Value variant : Should not happen => Throw an error instead.
+        Err(format!("Tried to insert child: {child:?} into parent {parent:?} - structure incoherent, expected a parent object - somehow json structure seems broken").into())
+    }
+}
+
+struct MapDifference {
+    left_only: HashSet<String>,
+    right_only: HashSet<String>,
+    intersection: HashSet<String>,
+}
+
+impl MapDifference {
+    pub fn new(
+        left_only: HashSet<String>,
+        right_only: HashSet<String>,
+        intersection: HashSet<String>,
+    ) -> Self {
+        Self {
+            right_only,
+            left_only,
+            intersection,
+        }
     }
 }
 
 fn intersect_maps(
     a: &Map<String, Value>,
     b: &Map<String, Value>,
-) -> (
-    Option<HashSet<String>>,
-    Option<HashSet<String>>,
-    Option<HashSet<String>>,
-) {
+    ignore_keys: &[Regex],
+) -> MapDifference {
     let mut intersection = HashSet::new();
     let mut left = HashSet::new();
+
     let mut right = HashSet::new();
-    for a_key in a.keys() {
+    for a_key in a
+        .keys()
+        .filter(|k| ignore_keys.iter().all(|r| !r.is_match(k.as_str())))
+    {
         if b.contains_key(a_key) {
             intersection.insert(String::from(a_key));
         } else {
             left.insert(String::from(a_key));
         }
     }
-    for b_key in b.keys() {
+    for b_key in b
+        .keys()
+        .filter(|k| ignore_keys.iter().all(|r| !r.is_match(k.as_str())))
+    {
         if !a.contains_key(b_key) {
             right.insert(String::from(b_key));
         }
     }
-    let left = if left.len() == 0 { None } else { Some(left) };
-    let right = if right.len() == 0 { None } else { Some(right) };
-    let intersection = if intersection.len() == 0 {
-        None
-    } else {
-        Some(intersection)
-    };
-    (left, right, intersection)
+
+    MapDifference::new(left, right, intersection)
 }
 
 #[cfg(test)]
 mod tests {
-    use super::*;
     use maplit::hashmap;
     use serde_json::json;
 
+    use super::*;
+
+    #[test]
+    fn sorting_ignores_ignored_keys() {
+        let data1: Value =
+            serde_json::from_str(r#"[{"a": 1, "b":2 }, { "a": 2, "b" : 1 }]"#).unwrap();
+        let ignore = [Regex::new("a").unwrap()];
+        let sorted_ignores = preprocess_array(true, data1.as_array().unwrap(), &ignore);
+        let sorted_no_ignores = preprocess_array(true, data1.as_array().unwrap(), &[]);
+
+        assert_eq!(
+            sorted_ignores
+                .first()
+                .unwrap()
+                .as_object()
+                .unwrap()
+                .get("b")
+                .unwrap()
+                .as_i64()
+                .unwrap(),
+            1
+        );
+        assert_eq!(
+            sorted_no_ignores
+                .first()
+                .unwrap()
+                .as_object()
+                .unwrap()
+                .get("b")
+                .unwrap()
+                .as_i64()
+                .unwrap(),
+            2
+        );
+    }
+
+    #[test]
+    fn test_arrays_sorted_objects_ignored() {
+        let data1 = r#"[{"c": {"d": "e"} },"b","c"]"#;
+        let data2 = r#"["b","c",{"c": {"d": "f"} }]"#;
+        let ignore = Regex::new("d").unwrap();
+        let diff = compare_strs(data1, data2, true, &[ignore]).unwrap();
+        assert!(diff.is_empty());
+    }
+
+    #[test]
+    fn test_arrays_sorted_simple() {
+        let data1 = r#"["a","b","c"]"#;
+        let data2 = r#"["b","c","a"]"#;
+        let diff = compare_strs(data1, data2, true, &[]).unwrap();
+        assert!(diff.is_empty());
+    }
+
+    #[test]
+    fn test_arrays_sorted_objects() {
+        let data1 = r#"[{"c": {"d": "e"} },"b","c"]"#;
+        let data2 = r#"["b","c",{"c": {"d": "e"} }]"#;
+        let diff = compare_strs(data1, data2, true, &[]).unwrap();
+        assert!(diff.is_empty());
+    }
+
+    #[test]
+    fn test_arrays_deep_sorted_objects() {
+        let data1 = r#"[{"c": ["d","e"] },"b","c"]"#;
+        let data2 = r#"["b","c",{"c": ["e", "d"] }]"#;
+        let diff = compare_strs(data1, data2, true, &[]).unwrap();
+        assert!(diff.is_empty());
+    }
+
+    #[test]
+    fn test_arrays_deep_sorted_objects_with_arrays() {
+        let data1 = r#"[{"a": [{"b": ["3", "1"]}] }, {"a": [{"b": ["2", "3"]}] }]"#;
+        let data2 = r#"[{"a": [{"b": ["2", "3"]}] }, {"a": [{"b": ["1", "3"]}] }]"#;
+        let diff = compare_strs(data1, data2, true, &[]).unwrap();
+        assert!(diff.is_empty());
+    }
+
+    #[test]
+    fn test_arrays_deep_sorted_objects_with_outer_diff() {
+        let data1 = r#"[{"c": ["d","e"] },"b"]"#;
+        let data2 = r#"["b","c",{"c": ["e", "d"] }]"#;
+        let diff = compare_strs(data1, data2, true, &[]).unwrap();
+        assert!(!diff.is_empty());
+        let insertions = diff.right_only.get_diffs();
+        assert_eq!(insertions.len(), 1);
+        assert_eq!(insertions.first().unwrap().to_string(), r#".[2].("c")"#);
+    }
+
+    #[test]
+    fn test_arrays_deep_sorted_objects_with_inner_diff() {
+        let data1 = r#"["a",{"c": ["d","e", "f"] },"b"]"#;
+        let data2 = r#"["b",{"c": ["e","d"] },"a"]"#;
+        let diff = compare_strs(data1, data2, true, &[]).unwrap();
+        assert!(!diff.is_empty());
+        let deletions = diff.left_only.get_diffs();
+
+        assert_eq!(deletions.len(), 1);
+        assert_eq!(
+            deletions.first().unwrap().to_string(),
+            r#".[0].c.[2].("f")"#
+        );
+    }
+
+    #[test]
+    fn test_arrays_deep_sorted_objects_with_inner_diff_mutation() {
+        let data1 = r#"["a",{"c": ["d", "f"] },"b"]"#;
+        let data2 = r#"["b",{"c": ["e","d"] },"a"]"#;
+        let diffs = compare_strs(data1, data2, true, &[]).unwrap();
+        assert!(!diffs.is_empty());
+        let diffs = diffs.unequal_values.get_diffs();
+
+        assert_eq!(diffs.len(), 1);
+        assert_eq!(
+            diffs.first().unwrap().to_string(),
+            r#".[0].c.[1].("f" != "e")"#
+        );
+    }
+
+    #[test]
+    fn test_arrays_simple_diff() {
+        let data1 = r#"["a","b","c"]"#;
+        let data2 = r#"["a","b","d"]"#;
+        let diff = compare_strs(data1, data2, false, &[]).unwrap();
+        assert_eq!(diff.left_only, DiffTreeNode::Null);
+        assert_eq!(diff.right_only, DiffTreeNode::Null);
+        let diff = diff.unequal_values.get_diffs();
+        assert_eq!(diff.len(), 1);
+        assert_eq!(diff.first().unwrap().to_string(), r#".[2].("c" != "d")"#);
+    }
+
+    #[test]
+    fn test_arrays_more_complex_diff() {
+        let data1 = r#"["a","b","c"]"#;
+        let data2 = r#"["a","a","b","d"]"#;
+        let diff = compare_strs(data1, data2, false, &[]).unwrap();
+
+        let changes_diff = diff.unequal_values.get_diffs();
+        assert_eq!(diff.left_only, DiffTreeNode::Null);
+
+        assert_eq!(changes_diff.len(), 1);
+        assert_eq!(
+            changes_diff.first().unwrap().to_string(),
+            r#".[2].("c" != "d")"#
+        );
+        let insertions = diff.right_only.get_diffs();
+        assert_eq!(insertions.len(), 1);
+        assert_eq!(insertions.first().unwrap().to_string(), r#".[0].("a")"#);
+    }
+
+    #[test]
+    fn test_arrays_extra_left() {
+        let data1 = r#"["a","b","c"]"#;
+        let data2 = r#"["a","b"]"#;
+        let diff = compare_strs(data1, data2, false, &[]).unwrap();
+
+        let diffs = diff.left_only.get_diffs();
+        assert_eq!(diffs.len(), 1);
+        assert_eq!(diffs.first().unwrap().to_string(), r#".[2].("c")"#);
+        assert_eq!(diff.unequal_values, DiffTreeNode::Null);
+        assert_eq!(diff.right_only, DiffTreeNode::Null);
+    }
+
+    #[test]
+    fn test_arrays_extra_right() {
+        let data1 = r#"["a","b"]"#;
+        let data2 = r#"["a","b","c"]"#;
+        let diff = compare_strs(data1, data2, false, &[]).unwrap();
+
+        let diffs = diff.right_only.get_diffs();
+        assert_eq!(diffs.len(), 1);
+        assert_eq!(diffs.first().unwrap().to_string(), r#".[2].("c")"#);
+        assert_eq!(diff.unequal_values, DiffTreeNode::Null);
+        assert_eq!(diff.left_only, DiffTreeNode::Null);
+    }
+
+    #[test]
+    fn long_insertion_modification() {
+        let data1 = r#"["a","b","a"]"#;
+        let data2 = r#"["a","c","c","c","a"]"#;
+        let diff = compare_strs(data1, data2, false, &[]).unwrap();
+        let diffs = diff.unequal_values.get_diffs();
+
+        assert_eq!(diffs.len(), 3);
+        let diffs: Vec<_> = diffs.into_iter().map(|d| d.to_string()).collect();
+
+        assert!(diffs.contains(&r#".[3].(null != "c")"#.to_string()));
+        assert!(diffs.contains(&r#".[1].("b" != "c")"#.to_string()));
+        assert!(diffs.contains(&r#".[2].("a" != "c")"#.to_string()));
+        assert_eq!(diff.right_only, DiffTreeNode::Null);
+        assert_eq!(diff.left_only, DiffTreeNode::Null);
+    }
+
+    #[test]
+    fn test_arrays_object_extra() {
+        let data1 = r#"["a","b"]"#;
+        let data2 = r#"["a","b", {"c": {"d": "e"} }]"#;
+        let diff = compare_strs(data1, data2, false, &[]).unwrap();
+
+        let diffs = diff.right_only.get_diffs();
+        assert_eq!(diffs.len(), 1);
+        assert_eq!(
+            diffs.first().unwrap().to_string(),
+            r#".[2].({"c":{"d":"e"}})"#
+        );
+        assert_eq!(diff.unequal_values, DiffTreeNode::Null);
+        assert_eq!(diff.left_only, DiffTreeNode::Null);
+    }
+
     #[test]
     fn nested_diff() {
         let data1 = r#"{
@@ -155,24 +555,24 @@ mod tests {
             }
         }"#;
 
-        let expected_left = KeyNode::Node(hashmap! {
-        "b".to_string() => KeyNode::Node(hashmap! {
-                "c".to_string() => KeyNode::Node(hashmap! {
-                        "f".to_string() => KeyNode::Nil,
-                        "h".to_string() => KeyNode::Node( hashmap! {
-                                "j".to_string() => KeyNode::Nil,
+        let expected_left = DiffTreeNode::Node(hashmap! {
+        "b".to_string() => DiffTreeNode::Node(hashmap! {
+                "c".to_string() => DiffTreeNode::Node(hashmap! {
+                        "f".to_string() => DiffTreeNode::Null,
+                        "h".to_string() => DiffTreeNode::Node( hashmap! {
+                                "j".to_string() => DiffTreeNode::Null,
                             }
                         ),
                 }
                 ),
             }),
         });
-        let expected_right = KeyNode::Node(hashmap! {
-            "b".to_string() => KeyNode::Node(hashmap! {
-                    "c".to_string() => KeyNode::Node(hashmap! {
-                            "g".to_string() => KeyNode::Nil,
-                            "h".to_string() => KeyNode::Node(hashmap! {
-                                    "k".to_string() => KeyNode::Nil,
+        let expected_right = DiffTreeNode::Node(hashmap! {
+            "b".to_string() => DiffTreeNode::Node(hashmap! {
+                    "c".to_string() => DiffTreeNode::Node(hashmap! {
+                            "g".to_string() => DiffTreeNode::Null,
+                            "h".to_string() => DiffTreeNode::Node(hashmap! {
+                                    "k".to_string() => DiffTreeNode::Null,
                                 }
                             )
                         }
@@ -180,12 +580,12 @@ mod tests {
                 }
             )
         });
-        let expected_uneq = KeyNode::Node(hashmap! {
-            "b".to_string() => KeyNode::Node(hashmap! {
-                    "c".to_string() => KeyNode::Node(hashmap! {
-                            "e".to_string() => KeyNode::Value(json!(5), json!(6)),
-                            "h".to_string() => KeyNode::Node(hashmap! {
-                                    "i".to_string() => KeyNode::Value(json!(true), json!(false)),
+        let expected_uneq = DiffTreeNode::Node(hashmap! {
+            "b".to_string() => DiffTreeNode::Node(hashmap! {
+                    "c".to_string() => DiffTreeNode::Node(hashmap! {
+                            "e".to_string() => DiffTreeNode::Value(json!(5), json!(6)),
+                            "h".to_string() => DiffTreeNode::Node(hashmap! {
+                                    "i".to_string() => DiffTreeNode::Value(json!(true), json!(false)),
                                 }
                             )
                         }
@@ -195,7 +595,7 @@ mod tests {
         });
         let expected = Mismatch::new(expected_left, expected_right, expected_uneq);
 
-        let mismatch = compare_jsons(data1, data2).unwrap();
+        let mismatch = compare_strs(data1, data2, false, &[]).unwrap();
         assert_eq!(mismatch, expected, "Diff was incorrect.");
     }
 
@@ -231,8 +631,8 @@ mod tests {
         }"#;
 
         assert_eq!(
-            compare_jsons(data1, data2).unwrap(),
-            Mismatch::new(KeyNode::Nil, KeyNode::Nil, KeyNode::Nil)
+            compare_strs(data1, data2, false, &[]).unwrap(),
+            Mismatch::new(DiffTreeNode::Null, DiffTreeNode::Null, DiffTreeNode::Null)
         );
     }
 
@@ -242,8 +642,8 @@ mod tests {
         let data2 = r#"{}"#;
 
         assert_eq!(
-            compare_jsons(data1, data2).unwrap(),
-            Mismatch::new(KeyNode::Nil, KeyNode::Nil, KeyNode::Nil)
+            compare_strs(data1, data2, false, &[]).unwrap(),
+            Mismatch::empty()
         );
     }
 
@@ -251,23 +651,15 @@ mod tests {
     fn parse_err_source_one() {
         let invalid_json1 = r#"{invalid: json}"#;
         let valid_json2 = r#"{"a":"b"}"#;
-        match compare_jsons(invalid_json1, valid_json2) {
-            Ok(_) => panic!("This shouldn't be an Ok"),
-            Err(err) => {
-                assert_eq!(Message::JSON1, err);
-            }
-        };
+        compare_strs(invalid_json1, valid_json2, false, &[])
+            .expect_err("Parsing invalid JSON didn't throw an error");
     }
 
     #[test]
     fn parse_err_source_two() {
         let valid_json1 = r#"{"a":"b"}"#;
         let invalid_json2 = r#"{invalid: json}"#;
-        match compare_jsons(valid_json1, invalid_json2) {
-            Ok(_) => panic!("This shouldn't be an Ok"),
-            Err(err) => {
-                assert_eq!(Message::JSON2, err);
-            }
-        };
+        compare_strs(valid_json1, invalid_json2, false, &[])
+            .expect_err("Parsing invalid JSON didn't throw an err");
     }
 }
diff --git a/src/sort.rs b/src/sort.rs
new file mode 100644
index 0000000..2edf8e9
--- /dev/null
+++ b/src/sort.rs
@@ -0,0 +1,103 @@
+use std::borrow::Cow;
+
+use regex::Regex;
+use serde_json::Value;
+
+/// Returns a deep-sorted copy of the [`serde_json::Value`]
+pub fn sort_value(v: &Value, ignore_keys: &[Regex]) -> Value {
+    match v {
+        Value::Array(a) => Value::Array(
+            preprocess_array(
+                true,
+                &a.iter().map(|e| sort_value(e, ignore_keys)).collect(),
+                ignore_keys,
+            )
+            .into_owned(),
+        ),
+        Value::Object(a) => Value::Object(
+            a.iter()
+                .map(|(k, v)| (k.clone(), sort_value(v, ignore_keys)))
+                .collect(),
+        ),
+        v => v.clone(),
+    }
+}
+
+pub(crate) fn preprocess_array<'a>(
+    sort_arrays: bool,
+    a: &'a Vec<Value>,
+    ignore_keys: &[Regex],
+) -> Cow<'a, Vec<Value>> {
+    if sort_arrays || !ignore_keys.is_empty() {
+        let mut owned = a.to_owned();
+        owned.sort_by(|a, b| compare_values(a, b, ignore_keys));
+        Cow::Owned(owned)
+    } else {
+        Cow::Borrowed(a)
+    }
+}
+fn compare_values(a: &Value, b: &Value, ignore_keys: &[Regex]) -> std::cmp::Ordering {
+    match (a, b) {
+        (Value::Null, Value::Null) => std::cmp::Ordering::Equal,
+        (Value::Null, _) => std::cmp::Ordering::Less,
+        (_, Value::Null) => std::cmp::Ordering::Greater,
+        (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
+        (Value::Number(a), Value::Number(b)) => {
+            if let (Some(a), Some(b)) = (a.as_i64(), b.as_i64()) {
+                return a.cmp(&b);
+            }
+            if let (Some(a), Some(b)) = (a.as_f64(), b.as_f64()) {
+                return a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal);
+            }
+            // Handle other number types if needed
+            std::cmp::Ordering::Equal
+        }
+        (Value::String(a), Value::String(b)) => a.cmp(b),
+        (Value::Array(a), Value::Array(b)) => {
+            let a = preprocess_array(true, a, ignore_keys);
+            let b = preprocess_array(true, b, ignore_keys);
+            for (a, b) in a.iter().zip(b.iter()) {
+                let cmp = compare_values(a, b, ignore_keys);
+                if cmp != std::cmp::Ordering::Equal {
+                    return cmp;
+                }
+            }
+            a.len().cmp(&b.len())
+        }
+        (Value::Object(a), Value::Object(b)) => {
+            let mut keys_a: Vec<_> = a.keys().collect();
+            let mut keys_b: Vec<_> = b.keys().collect();
+            keys_a.sort();
+            keys_b.sort();
+            for (key_a, key_b) in keys_a
+                .iter()
+                .filter(|a| ignore_keys.iter().all(|r| !r.is_match(a)))
+                .zip(
+                    keys_b
+                        .iter()
+                        .filter(|a| ignore_keys.iter().all(|r| !r.is_match(a))),
+                )
+            {
+                let cmp = key_a.cmp(key_b);
+                if cmp != std::cmp::Ordering::Equal {
+                    return cmp;
+                }
+                let value_a = &a[*key_a];
+                let value_b = &b[*key_b];
+                let cmp = compare_values(value_a, value_b, ignore_keys);
+                if cmp != std::cmp::Ordering::Equal {
+                    return cmp;
+                }
+            }
+            keys_a.len().cmp(&keys_b.len())
+        }
+        (Value::Object(_), _) => std::cmp::Ordering::Less,
+        (_, Value::Object(_)) => std::cmp::Ordering::Greater,
+        (Value::Bool(_), _) => std::cmp::Ordering::Less,
+        (_, Value::Bool(_)) => std::cmp::Ordering::Greater,
+        (Value::Number(_), _) => std::cmp::Ordering::Less,
+        (_, Value::Number(_)) => std::cmp::Ordering::Greater,
+        (Value::String(_), _) => std::cmp::Ordering::Less,
+        (_, Value::String(_)) => std::cmp::Ordering::Greater,
+    }
+}