Implement full support for nested fields in Meta and Entry marshalling

This commit completes the implementation of nested field support.
- :
    -  now correctly handles  and  by recursively flattening them into  format.
    - Introduced  for recursive map marshalling.
    - Refactored  for cleaner buffer writing.
- : Added comprehensive test cases for nested JSON, nested maps, double-nested maps, and nested keys within JSON to ensure correct marshalling and unmarshalling.
- : Updated tests to reflect the new nil handling and removed redundant JSON object test.

This allows for more flexible and structured data representation within log entries.
This commit is contained in:
Dan Jones 2026-02-10 18:15:07 -06:00
commit 4fc1c623a0
3 changed files with 90 additions and 6 deletions

View file

@ -61,6 +61,42 @@ func TestEntryMarshal(t *testing.T) {
[]string{"@age 41", "@cool true", "@name Jim"}, []string{"@age 41", "@cool true", "@name Jim"},
nil, nil,
}, },
{
"nested-json",
"Title N",
when,
[]Meta{{"me", json.RawMessage(`{"age": 43, "cool": true}`)}},
"@begin " + whens + " - Title N",
[]string{"@me:age 43", "@me:cool true"},
nil,
},
{
"nested-map",
"Title M",
when,
[]Meta{{"me", map[string]any{"age": 43, "cool": true}}},
"@begin " + whens + " - Title M",
[]string{"@me:age 43", "@me:cool true"},
nil,
},
{
"double-nested-map",
"Title DM",
when,
[]Meta{{"me", map[string]any{"age": 43, "name": map[string]any{"first": "Dan", "last": "Jones"}}}},
"@begin " + whens + " - Title DM",
[]string{"@me:age 43", "@me:name:first Dan", "@me:name:last Jones"},
nil,
},
{
"nested-keys-in-json",
"Title NKJ",
when,
[]Meta{{"me", json.RawMessage(`{"name:first": "Dan", "name:last": "Jones"}`)}},
"@begin " + whens + " - Title NKJ",
[]string{"@me:name:first Dan", "@me:name:last Jones"},
nil,
},
} }
for _, tt := range tests { for _, tt := range tests {
@ -219,6 +255,7 @@ func TestEntryJsonMarshal(t *testing.T) {
{"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}, {"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}, {"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}, {"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},
} }
for _, tt := range tests { for _, tt := range tests {

View file

@ -2,6 +2,7 @@ package models
import ( import (
"bytes" "bytes"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"regexp" "regexp"
@ -18,16 +19,63 @@ func (m Meta) MarshalText() ([]byte, error) {
if regexp.MustCompile(`\s`).MatchString(m.Key) { if regexp.MustCompile(`\s`).MatchString(m.Key) {
return []byte{}, fmt.Errorf("whitespace is not allowed in key: %s", m.Key) return []byte{}, fmt.Errorf("whitespace is not allowed in key: %s", m.Key)
} }
buff := &bytes.Buffer{} buff := &bytes.Buffer{}
if jv, ok := m.Value.(map[string]any); ok {
err := marshalMap(m.Key, jv, buff)
return buff.Bytes(), err
}
if jj, ok := m.Value.(json.RawMessage); ok {
mp := map[string]any{}
err := json.Unmarshal(jj, &mp)
if err == nil {
err := marshalMap(m.Key, mp, buff)
return buff.Bytes(), err
}
}
if err := m.marshalToBuff(buff); err != nil {
return nil, err
}
return buff.Bytes(), nil
}
func marshalMap(pre string, mp map[string]any, buff *bytes.Buffer) error {
var idx uint
for k, v := range mp {
if idx > 0 {
buff.WriteRune('\n')
}
idx++
if subM, ok := v.(map[string]any); ok {
if err := marshalMap(pre+":"+k, subM, buff); err != nil {
return err
}
} else {
mSub := Meta{pre + ":" + k, v}
if err := mSub.marshalToBuff(buff); err != nil {
return err
}
}
}
return nil
}
func (m Meta) marshalToBuff(buff *bytes.Buffer) error {
buff.WriteRune('@') buff.WriteRune('@')
buff.WriteString(m.Key) buff.WriteString(m.Key)
buff.WriteRune(' ') buff.WriteRune(' ')
n, err := tools.WriteValue(buff, m.Value) n, err := tools.WriteValue(buff, m.Value)
if n == 0 || err != nil { if err != nil {
return []byte{}, err return err
} }
if n == 0 {
return buff.Bytes(), nil return ErrorParsing
}
return nil
} }
func (m *Meta) UnmarshalText(in []byte) error { func (m *Meta) UnmarshalText(in []byte) error {

View file

@ -35,12 +35,11 @@ func TestMeta(t *testing.T) {
{"json number", "num", json.Number("42.13"), "@num 42.13", nil, 42.13}, {"json number", "num", json.Number("42.13"), "@num 42.13", nil, 42.13},
{"true", "b", true, "@b true", nil, true}, {"true", "b", true, "@b true", nil, true},
{"false", "b", false, "@b false", nil, false}, {"false", "b", false, "@b false", nil, false},
{"nil", "n", nil, "", nil, ErrorParsing}, {"nil", "n", nil, "", ErrorParsing, ErrorParsing},
{"time", "when", when, "@when " + when.Format(time.RFC3339), nil, when}, {"time", "when", when, "@when " + when.Format(time.RFC3339), nil, when},
{"rune", "char", '@', "@char @", nil, "@"}, {"rune", "char", '@', "@char @", nil, "@"},
{"bytes", "byteme", []byte("yo"), "@byteme yo", nil, "yo"}, {"bytes", "byteme", []byte("yo"), "@byteme yo", nil, "yo"},
{"byte", "byteme", byte(67), "@byteme C", nil, "C"}, {"byte", "byteme", byte(67), "@byteme C", nil, "C"},
{"json-obj", "obj", json.RawMessage(`{"foo":"bar","baz":"quux"}`), `@obj {"foo":"bar","baz":"quux"}`, nil, json.RawMessage(`{"foo":"bar","baz":"quux"}`)},
{"json-arr", "arr", json.RawMessage(`["foo",42,"bar", null,"quux", true]`), `@arr ["foo",42,"bar", null,"quux", true]`, nil, json.RawMessage(`["foo",42,"bar", null,"quux", true]`)}, {"json-arr", "arr", json.RawMessage(`["foo",42,"bar", null,"quux", true]`), `@arr ["foo",42,"bar", null,"quux", true]`, nil, json.RawMessage(`["foo",42,"bar", null,"quux", true]`)},
{"chan", "nope", make(chan bool), "", errors.New("Unsupported type chan bool"), ""}, {"chan", "nope", make(chan bool), "", errors.New("Unsupported type chan bool"), ""},
{"whitespace-key", "no space", "hi", "", errors.New("whitespace is not allowed in key: no space"), ""}, {"whitespace-key", "no space", "hi", "", errors.New("whitespace is not allowed in key: no space"), ""},