github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/builtins/types/jsonlines/unmarshal.go (about)

     1  package jsonlines
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/json"
     7  	"fmt"
     8  
     9  	"github.com/lmorg/murex/lang"
    10  	"github.com/lmorg/murex/lang/types"
    11  )
    12  
    13  func iface2Str(a []interface{}) []string {
    14  	s := make([]string, len(a))
    15  	for i := range a {
    16  		s[i] = fmt.Sprint(a[i])
    17  	}
    18  	return s
    19  }
    20  
    21  func unmarshal(p *lang.Process) (interface{}, error) {
    22  	var (
    23  		jStruct  []interface{}
    24  		v        interface{}
    25  		jTable   [][]string
    26  		row      []interface{}
    27  		b        []byte
    28  		err      error
    29  		nextEOF  bool
    30  		isStruct bool
    31  	)
    32  
    33  	scanner := bufio.NewScanner(p.Stdin)
    34  	for scanner.Scan() {
    35  		b = scanner.Bytes()
    36  		if len(bytes.TrimSpace(b)) == 0 {
    37  			continue
    38  		}
    39  
    40  		isStruct = isStruct || noSquare(b)
    41  
    42  		switch {
    43  		case !isStruct:
    44  			// is a table
    45  
    46  			err = json.Unmarshal(b, &row)
    47  			if err == nil {
    48  				jTable = append(jTable, iface2Str(row))
    49  				continue
    50  			}
    51  
    52  			isStruct = true
    53  			for i := range jTable {
    54  				jStruct = append(jStruct, jTable[i])
    55  			}
    56  
    57  			fallthrough
    58  
    59  		case len(jTable) != 0:
    60  			// not a row
    61  
    62  			for i := range jTable {
    63  				jStruct = append(jStruct, jTable[i])
    64  			}
    65  			jTable = nil
    66  
    67  			fallthrough
    68  
    69  		default:
    70  			// is a struct
    71  			err = json.Unmarshal(b, &v)
    72  			switch {
    73  			case err == nil:
    74  				jStruct = append(jStruct, v)
    75  				continue
    76  
    77  			case len(jStruct) == 0 && len(b) > 1 && bytes.Contains(b, []byte{'}', '{'}):
    78  				nextEOF = true
    79  				continue
    80  
    81  			case noQuote(b) && noSquare(b) && noCurly(b):
    82  				b = append([]byte{'"'}, b...)
    83  				b = append(b, '"')
    84  				err = json.Unmarshal(b, &v)
    85  				if err == nil {
    86  					jStruct = append(jStruct, v)
    87  					continue
    88  				}
    89  				fallthrough
    90  
    91  			default:
    92  				return jStruct, fmt.Errorf("unable to unmarshal index %d in jsonlines: %s", len(jStruct), err)
    93  			}
    94  		}
    95  
    96  	}
    97  
    98  	if err != nil && nextEOF {
    99  		return unmarshalNoCrLF(b)
   100  	}
   101  
   102  	err = scanner.Err()
   103  	if err != nil {
   104  		return nil, fmt.Errorf("error while unmarshalling a %s: %s", types.JsonLines, err.Error())
   105  	}
   106  
   107  	if isStruct {
   108  		return jStruct, err
   109  	}
   110  	return jTable, err
   111  }
   112  
   113  func unmarshalNoCrLF(b []byte) (interface{}, error) {
   114  	var (
   115  		start   int
   116  		v       interface{}
   117  		err     error
   118  		jStruct []interface{}
   119  		quoted  bool
   120  		escaped bool
   121  	)
   122  
   123  	// don't range because we want to skip first byte to avoid ugly bounds
   124  	// checks within the loop (eg `b[i]-1`).
   125  	for i := 1; i < len(b)-1; i++ {
   126  		switch b[i] {
   127  		case '\\':
   128  			escaped = !escaped
   129  		case '"':
   130  			if escaped {
   131  				escaped = false
   132  			} else {
   133  				quoted = !quoted
   134  			}
   135  		case '{':
   136  			if escaped {
   137  				escaped = false
   138  				continue
   139  			}
   140  			if !quoted && b[i-1] == '}' {
   141  				err = json.Unmarshal(b[start:i], &v)
   142  				if err != nil {
   143  					return nil, err
   144  				}
   145  				jStruct = append(jStruct, v)
   146  				start = i
   147  			}
   148  		default:
   149  			if escaped {
   150  				escaped = false
   151  			}
   152  		}
   153  	}
   154  
   155  	// catch remainder
   156  	err = json.Unmarshal(b[start:], &v)
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  	jStruct = append(jStruct, v)
   161  
   162  	return jStruct, nil
   163  }