Add support for mixed-level nested keys with dot/blank handling

- Handle @parent:child and @parent🧒grandchild coexistence
- Use '.' key for parent values when nested children exist
- Add comprehensive test cases for double-nested scenarios
- Support both '.' and '' as special keys for parent values
This commit is contained in:
Dan Jones 2026-02-12 10:41:35 -06:00
commit 6095c2497e
3 changed files with 53 additions and 5 deletions

View file

@ -88,6 +88,24 @@ func TestEntryMarshal(t *testing.T) {
[]string{"@me:age 43", "@me:name:first Dan", "@me:name:last Jones"},
nil,
},
{
"double-nested-map-dot",
"Title DM",
when,
[]Meta{{"me", map[string]any{"age": 43, "name": map[string]any{".": "Dan Jones", "nick": "Danny"}}}},
"@begin " + whens + " - Title DM",
[]string{"@me:age 43", "@me:name Dan Jones", "@me:name:nick Danny"},
nil,
},
{
"double-nested-map-blank",
"Title DM",
when,
[]Meta{{"me", map[string]any{"age": 43, "name": map[string]any{"": "Dan Jones", "nick": "Danny"}}}},
"@begin " + whens + " - Title DM",
[]string{"@me:age 43", "@me:name Dan Jones", "@me:name:nick Danny"},
nil,
},
{
"nested-keys-in-json",
"Title NKJ",
@ -169,6 +187,14 @@ func TestEntryUnmarshal(t *testing.T) {
[]Meta{{"me:name", "Dan"}, {"me:coder", true}},
nil,
},
{
"nested-field-dot",
"@begin " + whens + " - A Title\n@me:name Dan Jones\n@me:name:nick Danny\n@me:coder true @end",
"A Title",
when,
[]Meta{{"me:name", "Dan Jones"}, {"me:name:nick", "Danny"}, {"me:coder", true}},
nil,
},
{
"json-field",
"@begin " + whens + " - Some Guy\n" + `@json {"name":"Dan","coder":true} @end`,
@ -256,6 +282,9 @@ func TestEntryJsonMarshal(t *testing.T) {
{"nested-field", "A Title", when, []Meta{{"obj:foo", "bar"}, {"obj:title", "Sub-title"}}, `{"title":"A Title","date":"` + whens + `","obj":{"foo":"bar","title":"Sub-title"}}`, nil},
{"double-nested-field", "A Title", when, []Meta{{"obj:foo", "bar"}, {"obj:me:name", "Dan"}, {"obj:me:age", 27}}, `{"title":"A Title","date":"` + whens + `","obj":{"foo":"bar","me":{"name":"Dan","age":27}}}`, nil},
{"nested-plus-json", "A Title", when, []Meta{{"obj:foo", "bar"}, {"obj:me", json.RawMessage(`{"name":"Dan","age":27}`)}}, `{"title":"A Title","date":"` + whens + `","obj":{"foo":"bar","me":{"name":"Dan","age":27}}}`, nil},
{"nested-part", "A Title", when, []Meta{{"obj:foo", "bar"}, {"obj:me", "Dan"}, {"obj:me:age", 27}}, `{"title":"A Title","date":"` + whens + `","obj":{"foo":"bar","me":{".":"Dan","age":27}}}`, nil},
{"nested-part-order", "A Title", when, []Meta{{"obj:foo", "bar"}, {"obj:me:age", 27}, {"obj:me", "Dan"}}, `{"title":"A Title","date":"` + whens + `","obj":{"foo":"bar","me":{".":"Dan","age":27}}}`, nil},
{"nested-part-order-two", "A Title", when, []Meta{{"obj:foo", "bar"}, {"obj:me:age", 27}, {"obj:me", "Dan"}, {"obj:me:cool", true}}, `{"title":"A Title","date":"` + whens + `","obj":{"foo":"bar","me":{".":"Dan","age":27,"cool":true}}}`, nil},
}
for _, tt := range tests {

View file

@ -50,12 +50,16 @@ func marshalMap(pre string, mp map[string]any, buff *bytes.Buffer) error {
buff.WriteRune('\n')
}
idx++
newKey := pre + ":" + k
if k == "." || k == "" {
newKey = pre
}
if subM, ok := v.(map[string]any); ok {
if err := marshalMap(pre+":"+k, subM, buff); err != nil {
if err := marshalMap(newKey, subM, buff); err != nil {
return err
}
} else {
mSub := Meta{pre + ":" + k, v}
mSub := Meta{newKey, v}
if err := mSub.marshalToBuff(buff); err != nil {
return err
}

View file

@ -42,6 +42,7 @@ func (ms Metas) Map() map[string]any {
}
func parseNestedFields(f map[string]any) {
todelete := make([]string, 0, len(f))
for k, v := range f {
if strings.Contains(k, ":") {
idx := strings.Index(k, ":")
@ -50,14 +51,28 @@ func parseNestedFields(f map[string]any) {
nest, ok := f[top].(map[string]any)
if !ok {
nest = map[string]any{}
curr := f[top]
if curr == nil {
nest = map[string]any{}
} else {
nest = map[string]any{".": curr}
}
}
curr, ok := nest[bottom].(map[string]any)
if ok {
curr["."] = v
} else {
nest[bottom] = v
}
nest[bottom] = v
parseNestedFields(nest)
f[top] = nest
delete(f, k)
todelete = append(todelete, k)
}
}
for _, k := range todelete {
delete(f, k)
}
}
// Implements json.Marshaler