mirror of
https://github.com/therootcompany/golib.git
synced 2026-08-09 22:10:24 +00:00
feat(jsontypes): tier word detection by key count for map heuristic
With ≤3 keys, one word-like key is enough to call it a struct. With 4+ keys, a majority must be word-like — prevents a single coincidental match (e.g., "beef" in hex keys) from misclassifying. Also fixes short segment handling: 2-char abbreviations like "ms" in "account_ms" are neutral rather than causing the whole key to fail word detection.
This commit is contained in:
parent
439558a8ac
commit
69d757bc69
@ -172,10 +172,10 @@ func TestHeuristicsMapDetection(t *testing.T) {
|
||||
wantConf bool
|
||||
}{
|
||||
{"numeric keys", []string{"1", "2", "3"}, true, true},
|
||||
{"uuid keys", []string{"a1b2c3d4-e5f6", "b2c3d4e5-f6a7", "c3d4e5f6-a7b8"}, true, true},
|
||||
{"uuid keys", []string{"a1b2c3d4-e5f6", "b2c3d4e5-f6a7", "c3d4e5f6-a7b8"}, true, false},
|
||||
{"field names", []string{"name", "age", "email"}, false, true},
|
||||
{"two keys no words", []string{"ab", "cd"}, true, false},
|
||||
{"hex IDs", []string{"a1b2c3d4", "e5f6a7b8", "c9d0e1f2"}, true, true},
|
||||
{"hex IDs", []string{"a1b2c3d4", "e5f6a7b8", "c9d0e1f2"}, true, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@ -37,27 +37,35 @@ func looksLikeMap(obj map[string]any) (isMap bool, confident bool) {
|
||||
return true, true
|
||||
}
|
||||
|
||||
// Rule 2: Any key is composed of words → struct.
|
||||
if hasWordLikeKey(keys) {
|
||||
// Rule 2: Keys that look like words → struct.
|
||||
// With few keys, one word is enough. With many keys, a majority must
|
||||
// be words — otherwise a few coincidental matches (e.g., "beef" in hex)
|
||||
// could misclassify a map.
|
||||
wordCount := countWordLikeKeys(keys)
|
||||
if n <= 3 {
|
||||
if wordCount >= 1 {
|
||||
return false, true
|
||||
}
|
||||
return true, false
|
||||
}
|
||||
if wordCount > n/2 {
|
||||
return false, true
|
||||
}
|
||||
|
||||
// Rule 3: No recognized words → default to map.
|
||||
// Confident when there are enough keys that we'd expect to find a word
|
||||
// if this were really a struct.
|
||||
return true, n >= 3
|
||||
// Rule 3: No word majority → default to map.
|
||||
return true, true
|
||||
}
|
||||
|
||||
// hasWordLikeKey returns true if any key looks like it's composed of words
|
||||
// (i.e., a struct field name rather than an ID/token). It splits on _ and
|
||||
// camelCase boundaries, then checks if the segments are recognizable words.
|
||||
func hasWordLikeKey(keys []string) bool {
|
||||
// countWordLikeKeys returns how many keys look like they're composed of words
|
||||
// (i.e., struct field names rather than IDs/tokens).
|
||||
func countWordLikeKeys(keys []string) int {
|
||||
count := 0
|
||||
for _, k := range keys {
|
||||
if isWordLikeKey(k) {
|
||||
return true
|
||||
count++
|
||||
}
|
||||
}
|
||||
return false
|
||||
return count
|
||||
}
|
||||
|
||||
// isWordLikeKey checks whether a key is composed of word-like segments,
|
||||
@ -76,11 +84,11 @@ func isWordLikeKey(k string) bool {
|
||||
}
|
||||
hasStrongWord := false
|
||||
for _, seg := range segments {
|
||||
if isAllDigits(seg) {
|
||||
// Pure digit segments are fine (e.g., trailing numbers in "form1065"
|
||||
// which splits to ["form", "1065"] after camelCase split — but
|
||||
// splitWordSegments doesn't split on digit boundaries, so this
|
||||
// mainly catches segments from underscore splits like "line_2").
|
||||
// Pure digits and short segments (< 3 chars) that aren't known
|
||||
// short words are neutral — skip without failing.
|
||||
// This handles abbreviations like "ms", "db", "ui" in keys
|
||||
// like "account_ms" or "build_db".
|
||||
if isAllDigits(seg) || (len(seg) < 3 && !commonShortWords[strings.ToLower(seg)]) {
|
||||
continue
|
||||
}
|
||||
if !isWordSegment(seg) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user