11package jsonview
22
33import (
4+ "encoding/json"
45 "errors"
56 "fmt"
67 "math"
8+ "os"
79 "strings"
810
911 "github.com/charmbracelet/bubbles/help"
@@ -12,6 +14,7 @@ import (
1214 "github.com/charmbracelet/bubbles/viewport"
1315 tea "github.com/charmbracelet/bubbletea"
1416 "github.com/charmbracelet/lipgloss"
17+ "github.com/charmbracelet/x/term"
1518 "github.com/muesli/reflow/truncate"
1619 "github.com/muesli/reflow/wordwrap"
1720 "github.com/tidwall/gjson"
@@ -100,29 +103,99 @@ var (
100103type JSONView interface {
101104 GetPath () string
102105 GetData () gjson.Result
103- Update (tea.Msg ) tea.Cmd
106+ Update (tea.Msg , bool ) tea.Cmd
104107 View () string
105108 Resize (width , height int )
106109}
107110
108111type TableView struct {
109- path string
110- data gjson.Result
111- table table.Model
112- rowData []gjson.Result
112+ width int
113+ height int
114+ path string
115+ data gjson.Result
116+ table table.Model
117+ rowData []gjson.Result
118+ iterator AnyIterator
119+ isLoading bool
120+ columns []table.Column
113121}
114122
115123func (tv * TableView ) GetPath () string { return tv .path }
116124func (tv * TableView ) GetData () gjson.Result { return tv .data }
117125func (tv * TableView ) View () string { return tv .table .View () }
118126
119- func (tv * TableView ) Update (msg tea.Msg ) tea.Cmd {
127+ func (tv * TableView ) Update (msg tea.Msg , raw bool ) tea.Cmd {
120128 var cmd tea.Cmd
121129 tv .table , cmd = tv .table .Update (msg )
130+
131+ // Check if we need to load more data
132+ if tv .iterator != nil && ! tv .isLoading && tv .data .IsArray () {
133+ cursor := tv .table .Cursor ()
134+ totalRows := len (tv .table .Rows ())
135+
136+ // Load more when we're at the last row
137+ if cursor == totalRows - 1 {
138+ tv .isLoading = true
139+ return tv .loadMoreData (raw )
140+ }
141+ }
142+
122143 return cmd
123144}
124145
146+ func (tv * TableView ) loadMoreData (raw bool ) tea.Cmd {
147+ return func () tea.Msg {
148+ if tv .iterator == nil {
149+ return nil
150+ }
151+
152+ if ! tv .iterator .Next () {
153+ tv .isLoading = false
154+ return tv .iterator .Err ()
155+ }
156+
157+ obj := tv .iterator .Current ()
158+ var result gjson.Result
159+ if jsonBytes , err := json .Marshal (obj ); err != nil {
160+ return err
161+ } else {
162+ result = gjson .ParseBytes (jsonBytes )
163+ }
164+
165+ if ! result .Exists () {
166+ tv .isLoading = false
167+ return nil
168+ }
169+
170+ // Add the new item to our data
171+ tv .rowData = append (tv .rowData , result )
172+
173+ // Add new row to the table
174+ newRow := table.Row {formatValue (result , raw )}
175+
176+ // For array of objects, we need to format according to columns
177+ if len (tv .columns ) > 1 && result .IsObject () {
178+ newRow = make (table.Row , len (tv .columns ))
179+ for i , col := range tv .columns {
180+ newRow [i ] = formatValue (result .Get (col .Title ), raw )
181+ }
182+ }
183+
184+ rows := tv .table .Rows ()
185+ rows = append (rows , newRow )
186+ tv .table .SetRows (rows )
187+
188+ // Resize columns to accommodate the new data
189+ tv .Resize (tv .width , tv .height )
190+
191+ tv .isLoading = false
192+ return nil
193+ }
194+ }
195+
125196func (tv * TableView ) Resize (width , height int ) {
197+ tv .width = width
198+ tv .height = height
126199 tv .updateColumnWidths (width )
127200 tv .table .SetHeight (min (height - heightOffset , tableMinHeight + len (tv .table .Rows ())))
128201}
@@ -190,7 +263,8 @@ type TextView struct {
190263func (tv * TextView ) GetPath () string { return tv .path }
191264func (tv * TextView ) GetData () gjson.Result { return tv .data }
192265func (tv * TextView ) View () string { return tv .viewport .View () }
193- func (tv * TextView ) Update (msg tea.Msg ) tea.Cmd {
266+
267+ func (tv * TextView ) Update (msg tea.Msg , raw bool ) tea.Cmd {
194268 var cmd tea.Cmd
195269 tv .viewport , cmd = tv .viewport .Update (msg )
196270 return cmd
@@ -218,12 +292,57 @@ type JSONViewer struct {
218292 help help.Model
219293}
220294
295+ // ExploreJSON explores a single JSON value known ahead of time
221296func ExploreJSON (title string , json gjson.Result ) error {
222297 view , err := newView ("" , json , false )
223298 if err != nil {
224299 return err
225300 }
226301
302+ viewer := & JSONViewer {stack : []JSONView {view }, root : title , rawMode : false , help : help .New ()}
303+
304+ _ , err = tea .NewProgram (viewer ).Run ()
305+ if viewer .message != "" {
306+ _ , msgErr := fmt .Println ("\n " + viewer .message )
307+ err = errors .Join (err , msgErr )
308+ }
309+ return err
310+ }
311+
312+ // ExploreJSONStream explores JSON data loaded incrementally via an iterator
313+ func ExploreJSONStream [T any ](title string , it Iterator [T ]) error {
314+ anyIt := genericToAnyIterator (it )
315+
316+ preloadCount := 20
317+ if termHeight , _ , err := term .GetSize (os .Stdout .Fd ()); err == nil {
318+ preloadCount = termHeight
319+ }
320+
321+ items := make ([]any , 0 , preloadCount )
322+ for i := 0 ; i < preloadCount && anyIt .Next (); i ++ {
323+ items = append (items , anyIt .Current ())
324+ }
325+
326+ if err := anyIt .Err (); err != nil {
327+ return err
328+ }
329+
330+ // Convert items to JSON array
331+ jsonBytes , err := json .Marshal (items )
332+ if err != nil {
333+ return err
334+ }
335+ arrayJSON := gjson .ParseBytes (jsonBytes )
336+ view , err := newTableView ("" , arrayJSON , false )
337+ if err != nil {
338+ return err
339+ }
340+
341+ // Set iterator if there might be more data
342+ if len (items ) == preloadCount {
343+ view .iterator = anyIt
344+ }
345+
227346 viewer := & JSONViewer {stack : []JSONView {view }, root : title , rawMode : false , help : help .New ()}
228347 _ , err = tea .NewProgram (viewer ).Run ()
229348 if viewer .message != "" {
@@ -265,7 +384,7 @@ func (v *JSONViewer) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
265384 }
266385 }
267386
268- return v , v .current ().Update (msg )
387+ return v , v .current ().Update (msg , v . rawMode )
269388}
270389
271390func (v * JSONViewer ) getSelectedContent () string {
@@ -343,11 +462,16 @@ func (v *JSONViewer) toggleRaw() (tea.Model, tea.Cmd) {
343462 v .rawMode = ! v .rawMode
344463
345464 for i , view := range v .stack {
346- rawView , err := newView (view .GetPath (), view .GetData (), v .rawMode )
465+ viewWithRaw , err := newView (view .GetPath (), view .GetData (), v .rawMode )
347466 if err != nil {
348467 return v , tea .Printf ("Error: %s" , err )
349468 }
350- v .stack [i ] = rawView
469+ if newTV , ok := viewWithRaw .(* TableView ); ok {
470+ if tv , ok := view .(* TableView ); ok && tv .iterator != nil {
471+ newTV .iterator = tv .iterator
472+ }
473+ }
474+ v .stack [i ] = viewWithRaw
351475 }
352476
353477 v .resize (v .width , v .height )
@@ -431,7 +555,13 @@ func newArrayTableView(path string, data gjson.Result, array []gjson.Result, raw
431555 }
432556
433557 t := createTable (columns , rows , arrayColor )
434- return & TableView {path : path , data : data , table : t , rowData : rowData }
558+ return & TableView {
559+ path : path ,
560+ data : data ,
561+ table : t ,
562+ rowData : rowData ,
563+ columns : columns ,
564+ }
435565}
436566
437567func newArrayOfObjectsTableView (path string , data gjson.Result , array []gjson.Result , raw bool ) * TableView {
@@ -462,7 +592,13 @@ func newArrayOfObjectsTableView(path string, data gjson.Result, array []gjson.Re
462592 }
463593
464594 t := createTable (columns , rows , arrayColor )
465- return & TableView {path : path , data : data , table : t , rowData : rowData }
595+ return & TableView {
596+ path : path ,
597+ data : data ,
598+ table : t ,
599+ rowData : rowData ,
600+ columns : columns ,
601+ }
466602}
467603
468604func newObjectTableView (path string , data gjson.Result , raw bool ) * TableView {
@@ -489,7 +625,13 @@ func newObjectTableView(path string, data gjson.Result, raw bool) *TableView {
489625 }
490626
491627 t := createTable (columns , rows , objectColor )
492- return & TableView {path : path , data : data , table : t , rowData : rowData }
628+ return & TableView {
629+ path : path ,
630+ data : data ,
631+ table : t ,
632+ rowData : rowData ,
633+ columns : columns ,
634+ }
493635}
494636
495637func createTable (columns []table.Column , rows []table.Row , bgColor lipgloss.Color ) table.Model {
@@ -588,3 +730,46 @@ func sum(ints []int) int {
588730 }
589731 return total
590732}
733+
734+ // An iterator over `any` values
735+ type AnyIterator interface {
736+ Next () bool
737+ Err () error
738+ Current () any
739+ }
740+
741+ // A generic iterator interface that is used by the `genericIterator` struct
742+ // below to convert iterators over specific types to an AnyIterator
743+ type Iterator [T any ] interface {
744+ Next () bool
745+ Err () error
746+ Current () T
747+ }
748+
749+ // genericIterator adapts a generic Iterator[T] to an AnyIterator.
750+ type genericIterator [T any ] struct {
751+ iterator Iterator [T ]
752+ current any
753+ }
754+
755+ func (g * genericIterator [T ]) Next () bool {
756+ if ! g .iterator .Next () {
757+ return false
758+ }
759+ g .current = g .iterator .Current ()
760+ return true
761+ }
762+
763+ func (g * genericIterator [T ]) Err () error {
764+ return g .iterator .Err ()
765+ }
766+
767+ func (g * genericIterator [T ]) Current () any {
768+ return g .current
769+ }
770+
771+ func genericToAnyIterator [T any ](it Iterator [T ]) AnyIterator {
772+ return & genericIterator [T ]{
773+ iterator : it ,
774+ }
775+ }
0 commit comments