github.com/philpearl/plenc@v0.0.15/plenccodec/json.go (about)

     1  package plenccodec
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"unsafe"
     7  
     8  	"github.com/philpearl/plenc/plenccore"
     9  )
    10  
    11  // One map we handle is one that can deal with JSON map[string]any. In
    12  // this case the value is either nil, string, integer, float64, bool, array (of
    13  // these types) or object (another map[string]interface). We would need to
    14  // encode the value type as the standard wire types of proto don't encode enough
    15  // info. Encode as a list of key value pairs with a value type field. There's
    16  // some unfortunate overlap between the types we need and wire types, but it's
    17  // probably better to make consistent use of the proto encoding?? Why is this
    18  // better than just serialising with JSON and dumping the bytes? It's probably a
    19  // bit denser has faster to parse.
    20  
    21  // JSONMapCodec is for serialising JSON maps encoded in Go as
    22  // map[string]any. To use this codec you must register it for use with
    23  // map[string]any or a named map[string]any type
    24  type JSONMapCodec struct{}
    25  
    26  // JSONArrayCodec is for serialising JSON arrays encoded as []any
    27  type JSONArrayCodec struct{}
    28  
    29  type jsonType uint
    30  
    31  const (
    32  	jsonTypeNil jsonType = iota
    33  	jsonTypeString
    34  	jsonTypeInt
    35  	jsonTypeFloat
    36  	jsonTypeBool
    37  	jsonTypeArray
    38  	jsonTypeObject
    39  	jsonTypeNumber
    40  )
    41  
    42  func (JSONMapCodec) Omit(ptr unsafe.Pointer) bool {
    43  	return ptr == nil
    44  }
    45  
    46  func (c JSONMapCodec) size(ptr unsafe.Pointer) (size int) {
    47  	// this is just a map pointer here!
    48  	var m map[string]any
    49  	*(*unsafe.Pointer)((unsafe.Pointer)(&m)) = ptr
    50  
    51  	// We'll use the WTSlice wire type, so first is the number of items
    52  	size = plenccore.SizeVarUint(uint64(len(m)))
    53  	for k, v := range m {
    54  		// With WTSlice each item is preceeded by its length
    55  		itemSize := c.sizeKV(k, v)
    56  		size += plenccore.SizeVarUint(uint64(itemSize)) + itemSize
    57  	}
    58  	return size
    59  }
    60  
    61  func (c JSONMapCodec) sizeKV(k string, v any) (size int) {
    62  	size += plenccore.SizeTag(StringCodec{}.WireType(), 1)
    63  	size += plenccore.SizeVarUint(uint64(len(k)))
    64  	size += len(k)
    65  	return size + sizeJSONValue(v)
    66  }
    67  
    68  func (c JSONMapCodec) append(data []byte, ptr unsafe.Pointer) []byte {
    69  	// this is just a map pointer here!
    70  	var m map[string]any
    71  	*(*unsafe.Pointer)((unsafe.Pointer)(&m)) = ptr
    72  
    73  	// First the number of items
    74  	data = plenccore.AppendVarUint(data, uint64(len(m)))
    75  
    76  	// Next each item preceeded by its length
    77  	for k, v := range m {
    78  		s := c.sizeKV(k, v)
    79  		data = plenccore.AppendVarUint(data, uint64(s))
    80  		data = c.appendKV(data, k, v)
    81  	}
    82  
    83  	return data
    84  }
    85  
    86  var keyTag = plenccore.AppendTag(nil, StringCodec{}.WireType(), 1)
    87  
    88  func (c JSONMapCodec) appendKV(data []byte, k string, v any) []byte {
    89  	data = StringCodec{}.Append(data, unsafe.Pointer(&k), keyTag)
    90  	return appendJSONValue(data, v)
    91  }
    92  
    93  func (c JSONMapCodec) Read(data []byte, ptr unsafe.Pointer, wt plenccore.WireType) (n int, err error) {
    94  	count, n := plenccore.ReadVarUint(data)
    95  	if n == 0 {
    96  		return 0, nil
    97  	}
    98  	offset := n
    99  
   100  	m := *(*map[string]any)(ptr)
   101  	if m == nil {
   102  		m = make(map[string]any, count)
   103  		*(*map[string]any)(ptr) = m
   104  	}
   105  
   106  	for ; count > 0; count-- {
   107  		l, n := plenccore.ReadVarUint(data[offset:])
   108  		if n < 0 {
   109  			return 0, fmt.Errorf("bad length in map")
   110  		}
   111  		offset += n
   112  		var key string
   113  		var val any
   114  
   115  		n, err := readJSONKV(data[offset:offset+int(l)], &key, &val)
   116  		if err != nil {
   117  			return 0, err
   118  		}
   119  		offset += n
   120  		m[key] = val
   121  	}
   122  
   123  	return offset, nil
   124  }
   125  
   126  func (c JSONMapCodec) New() unsafe.Pointer {
   127  	m := make(map[string]any)
   128  	return unsafe.Pointer(&m)
   129  }
   130  
   131  func (c JSONMapCodec) WireType() plenccore.WireType { return plenccore.WTSlice }
   132  
   133  func (c JSONMapCodec) Descriptor() Descriptor {
   134  	// This needs to be a special descriptor!
   135  	return Descriptor{Type: FieldTypeJSONObject}
   136  }
   137  
   138  func (c JSONMapCodec) Size(ptr unsafe.Pointer, tag []byte) int {
   139  	return c.size(ptr) + len(tag)
   140  }
   141  
   142  func (c JSONMapCodec) Append(data []byte, ptr unsafe.Pointer, tag []byte) []byte {
   143  	data = append(data, tag...)
   144  	return c.append(data, ptr)
   145  }
   146  
   147  func (c JSONArrayCodec) Omit(ptr unsafe.Pointer) bool {
   148  	return (ptr == nil) || (len(*(*[]any)(ptr)) == 0)
   149  }
   150  
   151  func (c JSONArrayCodec) size(ptr unsafe.Pointer) (size int) {
   152  	a := *(*[]any)(ptr)
   153  	size = plenccore.SizeVarUint(uint64(len(a)))
   154  	// Each entry is encoded preceeded by its length
   155  	for _, val := range a {
   156  		itemSize := sizeJSONValue(val)
   157  		size += plenccore.SizeVarUint(uint64(itemSize)) + itemSize
   158  	}
   159  	return size
   160  }
   161  
   162  func (c JSONArrayCodec) append(data []byte, ptr unsafe.Pointer) []byte {
   163  	a := *(*[]any)(ptr)
   164  	data = plenccore.AppendVarUint(data, uint64(len(a)))
   165  	// Each entry is encoded preceeded by its length
   166  	for _, val := range a {
   167  		itemSize := sizeJSONValue(val)
   168  		data = plenccore.AppendVarUint(data, uint64(itemSize))
   169  		data = appendJSONValue(data, val)
   170  	}
   171  	return data
   172  }
   173  
   174  func (c JSONArrayCodec) Read(data []byte, ptr unsafe.Pointer, wt plenccore.WireType) (n int, err error) {
   175  	count, n := plenccore.ReadVarUint(data)
   176  	offset := n
   177  
   178  	a := *(*[]any)(ptr)
   179  	if a == nil {
   180  		a = make([]any, count)
   181  		*(*[]any)(ptr) = a
   182  	}
   183  
   184  	for i := range a {
   185  		l, n := plenccore.ReadVarUint(data[offset:])
   186  		if n < 0 {
   187  			return 0, fmt.Errorf("bad length in map")
   188  		}
   189  		offset += n
   190  
   191  		n, err := readJSONKV(data[offset:offset+int(l)], nil, &a[i])
   192  		if err != nil {
   193  			return 0, err
   194  		}
   195  		offset += n
   196  	}
   197  
   198  	return offset, nil
   199  }
   200  
   201  func (c JSONArrayCodec) New() unsafe.Pointer {
   202  	return unsafe.Pointer(&[]any{})
   203  }
   204  
   205  func (c JSONArrayCodec) WireType() plenccore.WireType { return plenccore.WTSlice }
   206  
   207  func (c JSONArrayCodec) Descriptor() Descriptor {
   208  	// This needs to be a special descriptor!
   209  	return Descriptor{Type: FieldTypeJSONArray}
   210  }
   211  
   212  func (c JSONArrayCodec) Size(ptr unsafe.Pointer, tag []byte) int {
   213  	return c.size(ptr) + len(tag)
   214  }
   215  
   216  func (c JSONArrayCodec) Append(data []byte, ptr unsafe.Pointer, tag []byte) []byte {
   217  	data = append(data, tag...)
   218  	return c.append(data, ptr)
   219  }
   220  
   221  func sizeJSONValue(v any) (size int) {
   222  	size += plenccore.SizeTag(plenccore.WTVarInt, 2)
   223  	switch v := v.(type) {
   224  	case nil:
   225  		size += plenccore.SizeVarUint(uint64(jsonTypeNil))
   226  	case string:
   227  		size += plenccore.SizeVarUint(uint64(jsonTypeString))
   228  
   229  		size += StringCodec{}.Size(unsafe.Pointer(&v), valueWTLTag)
   230  	case int:
   231  		size += plenccore.SizeVarUint(uint64(jsonTypeInt))
   232  		size += IntCodec[int]{}.Size(unsafe.Pointer(&v), valueWTVITag)
   233  	case float64:
   234  		size += plenccore.SizeVarUint(uint64(jsonTypeFloat))
   235  		size += Float64Codec{}.Size(unsafe.Pointer(&v), valueWT64Tag)
   236  	case bool:
   237  		size += plenccore.SizeVarUint(uint64(jsonTypeBool))
   238  		size += BoolCodec{}.Size(unsafe.Pointer(&v), valueWTVITag)
   239  	case []any:
   240  		size += plenccore.SizeVarUint(uint64(jsonTypeArray))
   241  		size += JSONArrayCodec{}.Size(unsafe.Pointer(&v), valueWTSliceTag)
   242  	case map[string]any:
   243  		size += plenccore.SizeVarUint(uint64(jsonTypeObject))
   244  		size += JSONMapCodec{}.Size(unsafe.Pointer(unpackEFace(v).data), valueWTSliceTag)
   245  	case json.Number:
   246  		// Save this as a string
   247  		size += plenccore.SizeVarUint(uint64(jsonTypeNumber))
   248  		size += StringCodec{}.Size(unsafe.Pointer(&v), valueWTLTag)
   249  	default:
   250  		panic(fmt.Sprintf("unexpected json type %T", v))
   251  	}
   252  
   253  	return size
   254  }
   255  
   256  var (
   257  	valueWTLTag     = plenccore.AppendTag(nil, plenccore.WTLength, 3)
   258  	valueWTVITag    = plenccore.AppendTag(nil, plenccore.WTVarInt, 3)
   259  	valueWT64Tag    = plenccore.AppendTag(nil, plenccore.WT64, 3)
   260  	valueWTSliceTag = plenccore.AppendTag(nil, plenccore.WTSlice, 3)
   261  )
   262  
   263  func appendJSONValue(data []byte, v any) []byte {
   264  	data = plenccore.AppendTag(data, plenccore.WTVarInt, 2)
   265  	switch v := v.(type) {
   266  	case nil:
   267  		data = plenccore.AppendVarUint(data, uint64(jsonTypeNil))
   268  	case string:
   269  		data = plenccore.AppendVarUint(data, uint64(jsonTypeString))
   270  		data = StringCodec{}.Append(data, unsafe.Pointer(&v), valueWTLTag)
   271  	case int:
   272  		data = plenccore.AppendVarUint(data, uint64(jsonTypeInt))
   273  		data = IntCodec[int]{}.Append(data, unsafe.Pointer(&v), valueWTVITag)
   274  	case float64:
   275  		data = plenccore.AppendVarUint(data, uint64(jsonTypeFloat))
   276  		data = Float64Codec{}.Append(data, unsafe.Pointer(&v), valueWT64Tag)
   277  	case bool:
   278  		data = plenccore.AppendVarUint(data, uint64(jsonTypeBool))
   279  		data = BoolCodec{}.Append(data, unsafe.Pointer(&v), valueWTVITag)
   280  	case []any:
   281  		data = plenccore.AppendVarUint(data, uint64(jsonTypeArray))
   282  		data = JSONArrayCodec{}.Append(data, unsafe.Pointer(&v), valueWTSliceTag)
   283  	case map[string]any:
   284  		data = plenccore.AppendVarUint(data, uint64(jsonTypeObject))
   285  		data = JSONMapCodec{}.Append(data, unsafe.Pointer(unpackEFace(v).data), valueWTSliceTag)
   286  	case json.Number:
   287  		// Save this as a string
   288  		data = plenccore.AppendVarUint(data, uint64(jsonTypeNumber))
   289  		data = StringCodec{}.Append(data, unsafe.Pointer(&v), valueWTLTag)
   290  	default:
   291  		panic(fmt.Sprintf("unexpected json type %T", v))
   292  	}
   293  
   294  	return data
   295  }
   296  
   297  func readJSONKV(data []byte, key *string, val *any) (n int, err error) {
   298  	var (
   299  		jType  jsonType
   300  		offset int
   301  	)
   302  
   303  	for offset < len(data) {
   304  		wt, index, n := plenccore.ReadTag(data[offset:])
   305  		offset += n
   306  		switch index {
   307  		case 1:
   308  			// When using this for reading arrays we simply don't see this index
   309  			l, n := plenccore.ReadVarUint(data[offset:])
   310  			if n < 0 {
   311  				return 0, fmt.Errorf("bad length on string field")
   312  			}
   313  			offset += n
   314  
   315  			n, err := StringCodec{}.Read(data[offset:offset+int(l)], unsafe.Pointer(key), wt)
   316  			if err != nil {
   317  				return 0, err
   318  			}
   319  			offset += n
   320  		case 2:
   321  			v, n := plenccore.ReadVarUint(data[offset:])
   322  			if n < 0 {
   323  				return 0, fmt.Errorf("invalid map type field")
   324  			}
   325  			jType = jsonType(v)
   326  			offset += n
   327  		case 3:
   328  			switch jType {
   329  			case jsonTypeString:
   330  				l, n := plenccore.ReadVarUint(data[offset:])
   331  				if n < 0 {
   332  					return 0, fmt.Errorf("bad length on string field")
   333  				}
   334  				offset += n
   335  				var v string
   336  				n, err := StringCodec{}.Read(data[offset:offset+int(l)], unsafe.Pointer(&v), wt)
   337  				if err != nil {
   338  					return 0, err
   339  				}
   340  				*val = v
   341  				offset += n
   342  
   343  			case jsonTypeInt:
   344  				var v int
   345  				n, err := IntCodec[int]{}.Read(data[offset:], unsafe.Pointer(&v), wt)
   346  				if err != nil {
   347  					return 0, err
   348  				}
   349  				offset += n
   350  				*val = v
   351  
   352  			case jsonTypeFloat:
   353  				var v float64
   354  				n, err := Float64Codec{}.Read(data[offset:], unsafe.Pointer(&v), wt)
   355  				if err != nil {
   356  					return 0, err
   357  				}
   358  				offset += n
   359  				*val = v
   360  
   361  			case jsonTypeBool:
   362  				var v bool
   363  				n, err := BoolCodec{}.Read(data[offset:], unsafe.Pointer(&v), wt)
   364  				if err != nil {
   365  					return 0, err
   366  				}
   367  				offset += n
   368  				*val = v
   369  
   370  			case jsonTypeArray:
   371  				var v []any
   372  				n, err := JSONArrayCodec{}.Read(data[offset:], unsafe.Pointer(&v), wt)
   373  				if err != nil {
   374  					return 0, err
   375  				}
   376  				offset += n
   377  				*val = v
   378  
   379  			case jsonTypeObject:
   380  				var v map[string]any
   381  				n, err := JSONMapCodec{}.Read(data[offset:], unsafe.Pointer(&v), wt)
   382  				if err != nil {
   383  					return 0, err
   384  				}
   385  				offset += n
   386  				*val = v
   387  
   388  			case jsonTypeNumber:
   389  				l, n := plenccore.ReadVarUint(data[offset:])
   390  				if n < 0 {
   391  					return 0, fmt.Errorf("bad length on JSON number field")
   392  				}
   393  				offset += n
   394  				var v json.Number
   395  				n, err := StringCodec{}.Read(data[offset:offset+int(l)], unsafe.Pointer(&v), wt)
   396  				if err != nil {
   397  					return 0, err
   398  				}
   399  				*val = v
   400  				offset += n
   401  
   402  			default:
   403  				return 0, fmt.Errorf("unexpected json type %d", jType)
   404  			}
   405  		}
   406  	}
   407  
   408  	return offset, nil
   409  }