✨ Special handling of json field
This commit is contained in:
		
					parent
					
						
							
								0da5efcafe
							
						
					
				
			
			
				commit
				
					
						16a0a9d20b
					
				
			
		
					 4 changed files with 112 additions and 30 deletions
				
			
		
							
								
								
									
										110
									
								
								models/entry.go
									
										
									
									
									
								
							
							
						
						
									
										110
									
								
								models/entry.go
									
										
									
									
									
								
							|  | @ -14,9 +14,10 @@ import ( | ||||||
| const DateFormat = "January 02, 2006 at 03:04:05PM -0700" | const DateFormat = "January 02, 2006 at 03:04:05PM -0700" | ||||||
| 
 | 
 | ||||||
| type Entry struct { | type Entry struct { | ||||||
| 	Title  string | 	Title       string | ||||||
| 	Date   time.Time | 	Date        time.Time | ||||||
| 	Fields []Meta | 	Fields      []Meta | ||||||
|  | 	skipMissing bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type metaRes struct { | type metaRes struct { | ||||||
|  | @ -29,13 +30,23 @@ func (e Entry) getFieldMarshalChan() chan metaRes { | ||||||
| 	ch := make(chan metaRes, size) | 	ch := make(chan metaRes, size) | ||||||
| 	var wg sync.WaitGroup | 	var wg sync.WaitGroup | ||||||
| 
 | 
 | ||||||
| 	// @todo figure out a way to handle json field |  | ||||||
| 	for i := 0; i < size; i++ { | 	for i := 0; i < size; i++ { | ||||||
| 		wg.Add(1) | 		wg.Add(1) | ||||||
| 		go func(m Meta) { | 		go func(m Meta) { | ||||||
| 			defer wg.Done() | 			defer wg.Done() | ||||||
| 			o, er := m.MarshalText() | 			if m.Key == "json" { | ||||||
| 			ch <- metaRes{o, er} | 				if j, ok := m.Value.(json.RawMessage); ok { | ||||||
|  | 					sub := Entry{skipMissing: true} | ||||||
|  | 					json.Unmarshal(j, &sub) | ||||||
|  | 					for _, subM := range sub.Fields { | ||||||
|  | 						o, er := subM.MarshalText() | ||||||
|  | 						ch <- metaRes{o, er} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				o, er := m.MarshalText() | ||||||
|  | 				ch <- metaRes{o, er} | ||||||
|  | 			} | ||||||
| 
 | 
 | ||||||
| 		}(e.Fields[i]) | 		}(e.Fields[i]) | ||||||
| 	} | 	} | ||||||
|  | @ -137,7 +148,6 @@ func (e *Entry) getFieldUnarshalChan(in []byte) chan Meta { | ||||||
| 	scan.Split(scanEntry) | 	scan.Split(scanEntry) | ||||||
| 	scan.Scan() // throw out first line | 	scan.Scan() // throw out first line | ||||||
| 
 | 
 | ||||||
| 	// @todo figure out a way to handle json field |  | ||||||
| 	for scan.Scan() { | 	for scan.Scan() { | ||||||
| 		wg.Add(1) | 		wg.Add(1) | ||||||
| 		go func(field []byte) { | 		go func(field []byte) { | ||||||
|  | @ -145,7 +155,17 @@ func (e *Entry) getFieldUnarshalChan(in []byte) chan Meta { | ||||||
| 			m := new(Meta) | 			m := new(Meta) | ||||||
| 			err := m.UnmarshalText(field) | 			err := m.UnmarshalText(field) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				ch <- *m | 				if m.Key == "json" { | ||||||
|  | 					if j, ok := m.Value.(json.RawMessage); ok { | ||||||
|  | 						sub := Entry{skipMissing: true} | ||||||
|  | 						json.Unmarshal(j, &sub) | ||||||
|  | 						for _, subM := range sub.Fields { | ||||||
|  | 							ch <- subM | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					ch <- *m | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		}(scan.Bytes()) | 		}(scan.Bytes()) | ||||||
| 	} | 	} | ||||||
|  | @ -170,15 +190,63 @@ func (e Entry) MarshalJSON() ([]byte, error) { | ||||||
| 	out["date"] = e.Date.Format(time.RFC3339) | 	out["date"] = e.Date.Format(time.RFC3339) | ||||||
| 	for _, f := range e.Fields { | 	for _, f := range e.Fields { | ||||||
| 		if _, ok := out[f.Key]; !ok { | 		if _, ok := out[f.Key]; !ok { | ||||||
| 			out[f.Key] = f.Value | 			if f.Key == "json" { | ||||||
| 			if vt, ok := f.Value.(time.Time); ok { | 				ob := map[string]any{} | ||||||
| 				out[f.Key] = vt.Format(time.RFC3339) | 				if j, ok := f.Value.(json.RawMessage); ok { | ||||||
|  | 					json.Unmarshal(j, &ob) | ||||||
|  | 				} | ||||||
|  | 				// If we couldn't get valid data from there, this will just be empty | ||||||
|  | 				for k, v := range ob { | ||||||
|  | 					if k != "title" && k != "date" { | ||||||
|  | 						out[k] = v | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				out[f.Key] = f.Value | ||||||
|  | 				if vt, ok := f.Value.(time.Time); ok { | ||||||
|  | 					out[f.Key] = vt.Format(time.RFC3339) | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return json.Marshal(out) | 	return json.Marshal(out) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (e *Entry) unmarshalJsonChanHelper(m map[string]any, ch chan Meta, wg *sync.WaitGroup) { | ||||||
|  | 	for k, v := range m { | ||||||
|  | 		wg.Add(1) | ||||||
|  | 		go func(key string, value any) { | ||||||
|  | 			defer wg.Done() | ||||||
|  | 			if key != "json" { | ||||||
|  | 				ch <- Meta{key, value} | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			subM := map[string]any{} | ||||||
|  | 			if s, ok := value.(string); ok { | ||||||
|  | 				dec := json.NewDecoder(strings.NewReader(s)) | ||||||
|  | 				dec.UseNumber() | ||||||
|  | 				dec.Decode(&subM) | ||||||
|  | 			} else { | ||||||
|  | 				subM = value.(map[string]any) | ||||||
|  | 			} | ||||||
|  | 			e.unmarshalJsonChanHelper(subM, ch, wg) | ||||||
|  | 		}(k, v) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *Entry) getUnmarshalJsonChan(m map[string]any) chan Meta { | ||||||
|  | 	ch := make(chan Meta, len(m)) | ||||||
|  | 	var wg sync.WaitGroup | ||||||
|  | 
 | ||||||
|  | 	e.unmarshalJsonChanHelper(m, ch, &wg) | ||||||
|  | 	go func() { | ||||||
|  | 		wg.Wait() | ||||||
|  | 		close(ch) | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
|  | 	return ch | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (e *Entry) UnmarshalJSON(in []byte) error { | func (e *Entry) UnmarshalJSON(in []byte) error { | ||||||
| 	out := map[string]any{} | 	out := map[string]any{} | ||||||
| 	dec := json.NewDecoder(bytes.NewReader(in)) | 	dec := json.NewDecoder(bytes.NewReader(in)) | ||||||
|  | @ -188,30 +256,30 @@ func (e *Entry) UnmarshalJSON(in []byte) error { | ||||||
| 		return newParsingError(err) | 		return newParsingError(err) | ||||||
| 	} | 	} | ||||||
| 	title, ok := out["title"].(string) | 	title, ok := out["title"].(string) | ||||||
| 	if !ok || title == "" { | 	if (!ok || title == "") && !e.skipMissing { | ||||||
| 		return ErrorMissingTitle | 		return ErrorMissingTitle | ||||||
| 	} | 	} | ||||||
| 	e.Title = title | 	e.Title = title | ||||||
| 	delete(out, "title") |  | ||||||
| 	dates, ok := out["date"].(string) | 	dates, ok := out["date"].(string) | ||||||
| 	if !ok || dates == "" { | 	if (!ok || dates == "") && !e.skipMissing { | ||||||
| 		return ErrorMissingDate | 		return ErrorMissingDate | ||||||
| 	} | 	} | ||||||
| 	date, err := time.Parse(time.RFC3339, dates) | 	date, err := time.Parse(time.RFC3339, dates) | ||||||
| 	if err != nil { | 	if err != nil && !e.skipMissing { | ||||||
| 		return newParsingError(err) | 		return newParsingError(err) | ||||||
| 	} | 	} | ||||||
| 	e.Date = date | 	e.Date = date | ||||||
| 	delete(out, "date") | 	ch := e.getUnmarshalJsonChan(out) | ||||||
| 	for k, v := range out { | 	for m := range ch { | ||||||
| 		m := Meta{Key: k} | 		if m.Key == "title" || m.Key == "date" { | ||||||
| 		if vs, ok := v.(string); ok { | 			continue | ||||||
|  | 		} else if vs, ok := m.Value.(string); ok { | ||||||
| 			if vd, err := time.Parse(time.RFC3339, vs); err == nil { | 			if vd, err := time.Parse(time.RFC3339, vs); err == nil { | ||||||
| 				m.Value = vd | 				m.Value = vd | ||||||
| 			} else { | 			} else { | ||||||
| 				m.Value = vs | 				m.Value = vs | ||||||
| 			} | 			} | ||||||
| 		} else if n, ok := v.(json.Number); ok { | 		} else if n, ok := m.Value.(json.Number); ok { | ||||||
| 			it, _ := n.Int64() | 			it, _ := n.Int64() | ||||||
| 			fl, _ := n.Float64() | 			fl, _ := n.Float64() | ||||||
| 			if float64(it) == fl { | 			if float64(it) == fl { | ||||||
|  | @ -219,8 +287,6 @@ func (e *Entry) UnmarshalJSON(in []byte) error { | ||||||
| 			} else { | 			} else { | ||||||
| 				m.Value = fl | 				m.Value = fl | ||||||
| 			} | 			} | ||||||
| 		} else { |  | ||||||
| 			m.Value = v |  | ||||||
| 		} | 		} | ||||||
| 		e.Fields = append(e.Fields, m) | 		e.Fields = append(e.Fields, m) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -52,7 +52,6 @@ func TestEntryMarshal(t *testing.T) { | ||||||
| 			[]string{"@age 41", "@cool true", "@name Jim"}, | 			[]string{"@age 41", "@cool true", "@name Jim"}, | ||||||
| 			nil, | 			nil, | ||||||
| 		}, | 		}, | ||||||
| 		/* uncomment when implemented |  | ||||||
| 		{ | 		{ | ||||||
| 			"json-field", | 			"json-field", | ||||||
| 			"Title J", | 			"Title J", | ||||||
|  | @ -62,7 +61,6 @@ func TestEntryMarshal(t *testing.T) { | ||||||
| 			[]string{"@age 41", "@cool true", "@name Jim"}, | 			[]string{"@age 41", "@cool true", "@name Jim"}, | ||||||
| 			nil, | 			nil, | ||||||
| 		}, | 		}, | ||||||
| 		*/ |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, tt := range tests { | 	for _, tt := range tests { | ||||||
|  | @ -72,7 +70,7 @@ func TestEntryMarshal(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func getEntryMarshalTestRunner(title string, date time.Time, fields []Meta, first string, lines []string, err error) func(*testing.T) { | func getEntryMarshalTestRunner(title string, date time.Time, fields []Meta, first string, lines []string, err error) func(*testing.T) { | ||||||
| 	return func(t *testing.T) { | 	return func(t *testing.T) { | ||||||
| 		en := Entry{title, date, fields} | 		en := Entry{title, date, fields, false} | ||||||
| 		o, er := en.MarshalText() | 		o, er := en.MarshalText() | ||||||
| 		assert.Equal(t, err, er) | 		assert.Equal(t, err, er) | ||||||
| 		if first == "" { | 		if first == "" { | ||||||
|  | @ -127,16 +125,14 @@ func TestEntryUnmarshal(t *testing.T) { | ||||||
| 			[]Meta{{"me", json.RawMessage(`{"name":"Dan","coder":true}`)}}, | 			[]Meta{{"me", json.RawMessage(`{"name":"Dan","coder":true}`)}}, | ||||||
| 			nil, | 			nil, | ||||||
| 		}, | 		}, | ||||||
| 		/* uncomment when implemented |  | ||||||
| 		{ | 		{ | ||||||
| 			"json-field", | 			"json-field", | ||||||
| 			"@begin " + whens + " - Some Guy\n" + `@json {"name":"Dan","coder":true} @end`, | 			"@begin " + whens + " - Some Guy\n" + `@json {"name":"Dan","coder":true} @end`, | ||||||
| 			"A Title", | 			"Some Guy", | ||||||
| 			when, | 			when, | ||||||
| 			[]Meta{{"name", "Dan"}, {"coder", true}}, | 			[]Meta{{"name", "Dan"}, {"coder", true}}, | ||||||
| 			nil, | 			nil, | ||||||
| 		}, | 		}, | ||||||
| 		*/ |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, tt := range tests { | 	for _, tt := range tests { | ||||||
|  | @ -171,7 +167,7 @@ func getEntryUnmarshalTestRunner(in string, title string, date time.Time, fields | ||||||
| 					break | 					break | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			assert.Truef(t, got, "Couldn't find field %+v", f) | 			assert.Truef(t, got, "Couldn't find field %+v. We have %+v", f, e.Fields) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | @ -212,6 +208,7 @@ func TestEntryJsonMarshal(t *testing.T) { | ||||||
| 		{"empty-date", "A Title", time.Time{}, simple, "", ErrorMissingDate}, | 		{"empty-date", "A Title", time.Time{}, simple, "", ErrorMissingDate}, | ||||||
| 		{"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}, | 		{"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}, | 		{"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}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, tt := range tests { | 	for _, tt := range tests { | ||||||
|  | @ -221,7 +218,7 @@ func TestEntryJsonMarshal(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func getEntryJsonMarshalTestRunner(title string, date time.Time, fields []Meta, out string, err error) func(t *testing.T) { | func getEntryJsonMarshalTestRunner(title string, date time.Time, fields []Meta, out string, err error) func(t *testing.T) { | ||||||
| 	return func(t *testing.T) { | 	return func(t *testing.T) { | ||||||
| 		e := Entry{title, date, fields} | 		e := Entry{title, date, fields, false} | ||||||
| 		o, er := json.Marshal(e) | 		o, er := json.Marshal(e) | ||||||
| 		if err == nil { | 		if err == nil { | ||||||
| 			assert.JSONEq(t, out, string(o)) | 			assert.JSONEq(t, out, string(o)) | ||||||
|  | @ -275,6 +272,22 @@ func TestEntryJsonUnmarshal(t *testing.T) { | ||||||
| 			[]Meta{{"posted", when.Add(-time.Hour)}}, | 			[]Meta{{"posted", when.Add(-time.Hour)}}, | ||||||
| 			nil, | 			nil, | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"json-field", | ||||||
|  | 			`{"title":"A Title","date":"` + whens + `","json":{"age": 41, "cool": true, "name": "Jim"}}`, | ||||||
|  | 			"A Title", | ||||||
|  | 			when, | ||||||
|  | 			[]Meta{{"age", int64(41)}, {"cool", true}, {"name", "Jim"}}, | ||||||
|  | 			nil, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"json-field-embed", | ||||||
|  | 			`{"title":"A Title","date":"` + whens + `","json":"{\"age\": 41, \"cool\": true, \"name\": \"Jim\"}"}`, | ||||||
|  | 			"A Title", | ||||||
|  | 			when, | ||||||
|  | 			[]Meta{{"age", int64(41)}, {"cool", true}, {"name", "Jim"}}, | ||||||
|  | 			nil, | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, tt := range tests { | 	for _, tt := range tests { | ||||||
|  |  | ||||||
|  | @ -33,6 +33,8 @@ func (m Meta) MarshalText() ([]byte, error) { | ||||||
| 		buff.WriteString(v) | 		buff.WriteString(v) | ||||||
| 	case int: | 	case int: | ||||||
| 		buff.WriteString(strconv.Itoa(v)) | 		buff.WriteString(strconv.Itoa(v)) | ||||||
|  | 	case int64: | ||||||
|  | 		buff.WriteString(strconv.FormatInt(v, 10)) | ||||||
| 	case float64: | 	case float64: | ||||||
| 		buff.WriteString(strconv.FormatFloat(v, 'f', -1, 64)) | 		buff.WriteString(strconv.FormatFloat(v, 'f', -1, 64)) | ||||||
| 	case json.Number: | 	case json.Number: | ||||||
|  |  | ||||||
|  | @ -27,6 +27,7 @@ func TestMeta(t *testing.T) { | ||||||
| 		newVal any | 		newVal any | ||||||
| 	}{ | 	}{ | ||||||
| 		{"int", "num", 42, "@num 42", nil, 42}, | 		{"int", "num", 42, "@num 42", nil, 42}, | ||||||
|  | 		{"int64", "num", int64(42), "@num 42", nil, int(42)}, | ||||||
| 		{"float", "num", 42.13, "@num 42.13", nil, 42.13}, | 		{"float", "num", 42.13, "@num 42.13", nil, 42.13}, | ||||||
| 		{"string", "word", "hello", "@word hello", nil, "hello"}, | 		{"string", "word", "hello", "@word hello", nil, "hello"}, | ||||||
| 		{"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}, | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue