From c4e864afa5c0b118a245c66daa104d1270be04fb Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Tue, 10 Feb 2026 16:13:39 -0600 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=20nested=20fiel?= =?UTF-8?q?ds=20in=20log=20entries=20and=20JSON=20marshalling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces the ability to handle nested fields within log entries. The file has been updated with a function that transforms flat keys with a : delimiter (e.g., obj:foo) into nested JSON objects (e.g., ). The file includes new test cases to verify that: - Nested fields are correctly unmarshalled from string representations. - Nested fields are correctly marshalled into JSON objects. This enhancement allows for more structured and organized metadata within log entries. --- models/entry_test.go | 10 ++++++++++ models/metas.go | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/models/entry_test.go b/models/entry_test.go index 89223eb..852277b 100644 --- a/models/entry_test.go +++ b/models/entry_test.go @@ -125,6 +125,14 @@ func TestEntryUnmarshal(t *testing.T) { []Meta{{"me", json.RawMessage(`{"name":"Dan","coder":true}`)}}, nil, }, + { + "nested-field", + "@begin " + whens + " - A Title\n@me:name Dan\n@me:coder true @end", + "A Title", + when, + []Meta{{"me:name", "Dan"}, {"me:coder", true}}, + nil, + }, { "json-field", "@begin " + whens + " - Some Guy\n" + `@json {"name":"Dan","coder":true} @end`, @@ -209,6 +217,8 @@ func TestEntryJsonMarshal(t *testing.T) { {"obj-field", "A Title", when, []Meta{{"obj", json.RawMessage(`{"foo":"bar","title":"Sub-title"}`)}}, `{"title":"A Title","date":"` + whens + `","obj":{"foo":"bar","title":"Sub-title"}}`, nil}, {"date-field", "A Title", when, []Meta{{"when", when.Add(time.Hour)}}, `{"title":"A Title","date":"` + whens + `","when":"` + when.Add(time.Hour).Format(time.RFC3339) + `"}`, nil}, {"json-field", "A Title", when, []Meta{{"json", json.RawMessage(`{"age": 41, "cool": true, "name": "Jim"}`)}}, `{"title":"A Title","date":"` + whens + `","age":41,"cool": true, "name": "Jim"}`, nil}, + {"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}, } for _, tt := range tests { diff --git a/models/metas.go b/models/metas.go index cf4c23e..5b68455 100644 --- a/models/metas.go +++ b/models/metas.go @@ -2,6 +2,7 @@ package models import ( "encoding/json" + "strings" "time" ) @@ -33,9 +34,32 @@ func (ms Metas) Map() map[string]any { } } } + + // Next we have to go through them again to find nested fields + parseNestedFields(out) + return out } +func parseNestedFields(f map[string]any) { + for k, v := range f { + if strings.Contains(k, ":") { + idx := strings.Index(k, ":") + top := k[:idx] + bottom := k[(idx + 1):] + + nest, ok := f[top].(map[string]any) + if !ok { + nest = map[string]any{} + } + nest[bottom] = v + parseNestedFields(nest) + f[top] = nest + delete(f, k) + } + } +} + // Implements json.Marshaler func (ms Metas) MarshalJSON() ([]byte, error) { return json.Marshal(ms.Map())