Structural similarity scoring engine for JSON-compatible data
Project description
Sembra
Structural similarity scoring for JSON-compatible data
Italian: "sembrare" — to seem, to resemble
The Problem
Existing deep comparison libraries answer only binary questions:
| Library | Gives you |
|---|---|
fast-deep-equal |
Equal or not? |
lodash.isequal |
Equal or not? |
deep-diff |
What changed? |
deepdiff (Python) |
What changed? |
None answer the most natural question:
How similar are these two structures?
When schemas evolve, keys get renamed, or data comes from different sources, binary equality is useless. You need a similarity score.
The Algorithm: Sembra Similarity
Sembra computes a continuous similarity score S(a, b) ∈ [0, 1] for any two JSON-compatible values using a novel multi-phase algorithm:
Phase 1: Type-Aware Primitive Matching
- Strings: Jaro-Winkler distance (handles typos, minor variations)
- Numbers: Relative difference scoring
- Booleans: Exact match (configurable partial mode)
- Null: Always exact
Phase 2: Optimal Array Alignment
Instead of index-by-index comparison, Sembra computes a similarity matrix between all pairs of elements and finds the optimal matching using a greedy assignment algorithm. This handles:
- Reordered elements
- Inserted/deleted elements
- Partially matching elements
Phase 3: Value-Guided Key Rename Detection
This is the key novelty. When object keys differ between two structures, Sembra infers renames by comparing the values at those keys:
a = {"name": "Alice", "age": 30}
b = {"fullName": "Alice", "yearsOld": 30}
# Sembra detects: name → fullName, age → yearsOld
# Score: >0.85
No other library does this.
Phase 4: Recursive Composition
All phases compose recursively — nested arrays, nested objects, and mixed structures are handled uniformly.
Performance
| Operation | Complexity |
|---|---|
| Identical structures (early exit) | O(n) |
| Similar structures | O(n log n) |
| Dissimilar structures | O(n × m) |
n = number of nodes in the larger structure.
Quick Start
from sembra import sembra, sembra_report, SembraConfig
# Basic similarity
score = sembra({"a": 1, "b": 2}, {"a": 1, "b": 3})
print(score) # ~0.89
# Key rename detection
score = sembra(
{"userName": "Alice", "userAge": 30},
{"name": "Alice", "age": 30}
)
print(score) # >0.8
# Reordered arrays
score = sembra([1, 2, 3], [3, 1, 2])
print(score) # 1.0 (all elements match)
# Detailed report
report = sembra_report({"a": 1}, {"a": 2})
print(report.score)
print(report.matches)
print(f"Computed in {report.duration_ns}ns")
# Configuration
config = SembraConfig(
string_similarity=False,
object_key_rename=False,
number_threshold=0.9,
)
score = sembra({"a": "hello"}, {"a": "hallo"}, config)
Configuration Reference
| Option | Default | Description |
|---|---|---|
string_similarity |
True |
Enable fuzzy string matching |
string_threshold |
0.6 |
Minimum string similarity to count as match |
number_similarity |
True |
Enable fuzzy number matching |
number_threshold |
0.8 |
Minimum number similarity to count as match |
boolean_partial |
False |
Allow partial boolean matching |
array_matching |
True |
Enable optimal array element matching |
array_match_threshold |
0.4 |
Minimum element similarity to match |
object_key_rename |
True |
Enable value-guided key rename detection |
object_rename_threshold |
0.65 |
Minimum value similarity to infer rename |
max_depth |
16 |
Maximum recursion depth |
max_array_size |
200 |
Max array size for element-by-element matching |
use_cache |
True |
Cache intermediate results |
early_exit |
True |
Exit early for same-reference objects |
key_weight_mode |
'uniform' |
Key importance weighting |
Use Cases
- Test assertions with tolerance for structural variation
- Schema migration — map old API responses to new schemas
- Data deduplication — find structurally similar records
- Configuration comparison — detect drifted configs
- API monitoring — alert on structural drift
- Merge conflict resolution — find best correspondence
Comparison with Existing Libraries
| Feature | Sembra | deepdiff | fast-deep-equal | deep-diff |
|---|---|---|---|---|
| Continuous similarity score | ✅ | ❌ | ❌ | ❌ |
| Key rename detection | ✅ | ❌ | ❌ | ❌ |
| Array alignment | ✅ | ❌ | ❌ | ❌ |
| Fuzzy strings | ✅ | ❌ | ❌ | ❌ |
| Fuzzy numbers | ✅ | ❌ | ❌ | ❌ |
| Binary equality | ✅ | ✅ | ✅ | ❌ |
| Diff script | ✅ | ✅ | ❌ | ✅ |
| Cross-type matching | ✅ | ❌ | ❌ | ❌ |
License
MIT — see LICENSE
Author
nhemlos
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sembra-0.1.0.tar.gz.
File metadata
- Download URL: sembra-0.1.0.tar.gz
- Upload date:
- Size: 75.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e591e20bee44b732deecab9d9a73557feb0afc5ff5bcacf5278a8b51b2cfbe02
|
|
| MD5 |
8a9b27080b8b00255305b5cf22754526
|
|
| BLAKE2b-256 |
234089fbe31754c009e04f06b65e374b226f21d516208fb3d5bc1d102a741522
|
File details
Details for the file sembra-0.1.0-py3-none-any.whl.
File metadata
- Download URL: sembra-0.1.0-py3-none-any.whl
- Upload date:
- Size: 8.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4360d798dedeb8e8e454e55b6e38c7f859b6c7b0f728f424b925417cd8d04335
|
|
| MD5 |
3380024555c71d963cf8689228815b97
|
|
| BLAKE2b-256 |
78c6f1d86458712ace2c125583aac0a1b1937efa540925e636caaa8088884c3a
|