88import Foundation
99
1010extension String {
11+ /// Normalizes a key string into lowercase snake_case format.
12+ ///
13+ /// This helps unify access to JSON keys that may use various formats such as:
14+ /// - camelCase
15+ /// - PascalCase
16+ /// - snake_case
17+ /// - kebab-case
18+ /// - Spaced strings ("Feature Toggle")
19+ ///
20+ /// For example:
21+ /// - "betaFeatureX" -> "beta_feature_x"
22+ /// - "BETA-FEATURE-X" -> "beta_feature_x"
23+ /// - "Feature Toggle" -> "feature_toggle"
24+ ///
25+ /// Returns: A normalized version of the string for consistent lookup.
1126 func normalizedKey( ) -> String {
1227 let pattern = #"(?<=[a-z0-9])(?=[A-Z])|[_\-\s]+"#
1328 let regex = try ! NSRegularExpression ( pattern: pattern, options: [ ] )
@@ -21,6 +36,15 @@ extension String {
2136 . joined ( separator: " _ " )
2237 }
2338
39+ /// Calculates the Levenshtein distance between two strings.
40+ ///
41+ /// The Levenshtein distance is a measure of how many single-character edits
42+ /// (insertions, deletions, or substitutions) are required to change one word into another.
43+ ///
44+ /// This is useful for implementing fuzzy string matching.
45+ ///
46+ /// - Parameter target: The string to compare against.
47+ /// - Returns: The number of edits needed to match the target.
2448 func levenshteinDistance( to target: String ) -> Int {
2549 let source = Array ( self )
2650 let target = Array ( target)
@@ -47,6 +71,17 @@ extension String {
4771}
4872
4973extension Dictionary where Key == String , Value == DynamicJSON {
74+ /// Attempts to find the best-matching key in the dictionary for a given lookup key.
75+ ///
76+ /// Matching is performed in the following order:
77+ /// 1. Exact match (after normalization)
78+ /// 2. Partial containment (normalized query is a substring of a key)
79+ /// 3. Fuzzy match using Levenshtein distance (within a threshold)
80+ ///
81+ /// - Parameters:
82+ /// - key: The original key string provided for lookup.
83+ /// - logMatch: A closure used to report fallback key matches for debugging.
84+ /// - Returns: The best matching `DynamicJSON` value, or `nil` if no reasonable match found.
5085 func fuzzyMatch( for key: String , logMatch: ( _ original: String , _ matched: String ) -> Void ) -> DynamicJSON ? {
5186 let normalized = key. normalizedKey ( )
5287
0 commit comments