Skip to content

Commit bb5bcb8

Browse files
glightning: added support for omitempty tag for fields in json (#30)
jrpc2: added support for omitempty tag for fields in json
1 parent 40d026e commit bb5bcb8

2 files changed

Lines changed: 111 additions & 14 deletions

File tree

jrpc2/jsonrpc2.go

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package jrpc2
22

33
import (
4+
"encoding"
45
"encoding/hex"
56
"encoding/json"
67
"errors"
@@ -312,6 +313,10 @@ func GetNamedParams(target Method) map[string]interface{} {
312313
}
313314

314315
func isZero(x interface{}) bool {
316+
if x == nil {
317+
return true
318+
}
319+
315320
return reflect.DeepEqual(x, reflect.Zero(reflect.TypeOf(x)).Interface())
316321
}
317322

@@ -354,10 +359,7 @@ func ParseNamedParams(target Method, params map[string]interface{}) error {
354359
targetValue := reflect.Indirect(reflect.ValueOf(target))
355360
err := innerParseNamed(targetValue, params)
356361
if err != nil {
357-
fmt.Println("ERR")
358-
fmt.Println(err)
359-
fmt.Println(targetValue)
360-
fmt.Println(params)
362+
return err
361363
}
362364
return nil
363365
}
@@ -372,11 +374,16 @@ func innerParseNamed(targetValue reflect.Value, params map[string]interface{}) e
372374
continue
373375
}
374376
fT := tType.Field(i)
375-
// check for the json tag match, as well a simple
376-
// lower case name match
377377
tag, _ := fT.Tag.Lookup("json")
378-
if tag == key || key == strings.ToLower(fT.Name) {
378+
379+
name, omit := parseTag(tag)
380+
381+
if name == key || key == strings.ToLower(fT.Name) {
379382
found = true
383+
if omit && isZero(value) {
384+
break
385+
}
386+
380387
err := innerParse(targetValue, fVal, value)
381388
if err != nil {
382389
return err
@@ -411,14 +418,12 @@ func innerParse(targetValue reflect.Value, fVal reflect.Value, value interface{}
411418
}
412419

413420
// json.RawMessage escape hatch
414-
var eg json.RawMessage
415-
if fVal.Type() == reflect.TypeOf(eg) {
421+
if strings.Contains(fVal.Type().String(), "RawMessage") {
416422
out, err := json.Marshal(value)
417423
if err != nil {
418424
return err
419425
}
420-
jm := json.RawMessage(out)
421-
fVal.Set(reflect.ValueOf(jm))
426+
fVal.Set(reflect.ValueOf(out).Convert(fVal.Type()))
422427
return nil
423428
}
424429

@@ -473,8 +478,12 @@ func innerParse(targetValue reflect.Value, fVal reflect.Value, value interface{}
473478
return nil
474479
}
475480

476-
av := value.([]interface{})
481+
av, ok := value.([]interface{})
482+
if !ok {
483+
return NewError(nil, InvalidParams, fmt.Sprintf("Expected JSON array for slice field %s, but got %T", fVal.Type().Name(), value))
484+
}
477485
fVal.Set(reflect.MakeSlice(fVal.Type(), len(av), len(av)))
486+
478487
for i := range av {
479488
err := innerParse(targetValue, fVal.Index(i), av[i])
480489
if err != nil {
@@ -517,22 +526,71 @@ func innerParse(targetValue reflect.Value, fVal reflect.Value, value interface{}
517526
}
518527
case reflect.Ptr:
519528
if v.Kind() == reflect.Invalid {
520-
// i'm afraid that's a nil, my dear
521529
return nil
522530
}
531+
532+
umType := reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()
533+
tmType := reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
534+
ptrType := fVal.Type()
535+
536+
// if the pointer itself implements Unmarshaler,
537+
// or if the thing it points to does,
538+
// then we can use that to unmarshal this value
539+
if ptrType.Implements(umType) || reflect.PointerTo(ptrType.Elem()).Implements(umType) {
540+
n := reflect.New(ptrType.Elem())
541+
data, err := json.Marshal(value)
542+
if err != nil {
543+
return err
544+
}
545+
if err := json.Unmarshal(data, n.Interface()); err != nil {
546+
return err
547+
}
548+
fVal.Set(n)
549+
return nil
550+
}
551+
552+
// Handle types implementing encoding.TextUnmarshaler by parsing from a string and setting the field.
553+
if ptrType.Implements(tmType) || reflect.PointerTo(ptrType.Elem()).Implements(tmType) {
554+
s, ok := value.(string)
555+
if !ok {
556+
return NewError(nil, InvalidParams, fmt.Sprintf("Expected string input for %s.%s, but got %T", targetValue.Type().Name(), fVal.Type().Name(), value))
557+
}
558+
n := reflect.New(ptrType.Elem())
559+
if err := n.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(s)); err != nil {
560+
return err
561+
}
562+
fVal.Set(n)
563+
return nil
564+
}
565+
566+
// For pointer-to-non-struct fields, allocate a new element,
567+
// parse into it, then assign the pointer.
568+
if fVal.Type().Elem().Kind() != reflect.Struct {
569+
n := reflect.New(fVal.Type().Elem())
570+
err := innerParse(targetValue, n.Elem(), value)
571+
if err != nil {
572+
return err
573+
}
574+
fVal.Set(n)
575+
return nil
576+
}
577+
523578
if v.Kind() != reflect.Map {
524579
return NewError(nil, InvalidParams, fmt.Sprintf("Types don't match. Expected a map[string]interface{} from the JSON, instead got %s", v.Kind().String()))
525580
}
581+
526582
if fVal.IsNil() {
527583
// You need a new pointer object thing here
528584
// so allocate one with this voodoo-magique
529585
fVal.Set(reflect.New(fVal.Type().Elem()))
530586
}
587+
531588
return innerParseNamed(fVal.Elem(), value.(map[string]interface{}))
532589
case reflect.Struct:
533590
if v.Kind() != reflect.Map {
534591
return NewError(nil, InvalidParams, fmt.Sprintf("Types don't match. Expected a map[string]interface{} from the JSON, instead got %s", v.Kind().String()))
535592
}
593+
536594
return innerParseNamed(fVal, value.(map[string]interface{}))
537595
case reflect.String:
538596
fVal.SetString(fmt.Sprintf("%v", v))

jrpc2/jsonrpc2_test.go

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"github.com/stretchr/testify/assert"
1111
)
1212

13-
//// This section (below) is for method json marshalling,
13+
// This section (below) is for method json marshalling,
1414
// with special emphasis on how the parameters get marshalled
1515
// and unmarshalled to/from 'Method' objects
1616
type HelloMethod struct {
@@ -653,3 +653,42 @@ func TestServerRegistry(t *testing.T) {
653653
err_ = server.Unregister(method)
654654
assert.Equal(t, "Method not registered", err_.Error())
655655
}
656+
657+
type OmitEmptyMethod struct {
658+
Required string `json:"required"`
659+
Optional *string `json:"optional,omitempty"`
660+
Count *uint32 `json:"count,omitempty"`
661+
}
662+
663+
func (m OmitEmptyMethod) New() interface{} {
664+
return &OmitEmptyMethod{}
665+
}
666+
667+
func (m OmitEmptyMethod) Call() (jrpc2.Result, error) {
668+
return nil, nil
669+
}
670+
671+
func (m OmitEmptyMethod) Name() string {
672+
return "omit_empty"
673+
}
674+
675+
func TestParsingOmitEmptyFields(t *testing.T) {
676+
requestJson := `{"id":1,"method":"omit_empty","params":{"required":"value","optional":"hello","count":7},"jsonrpc":"2.0"}`
677+
s := jrpc2.NewServer()
678+
s.Register(&OmitEmptyMethod{})
679+
680+
var result jrpc2.Request
681+
err := s.Unmarshal([]byte(requestJson), &result)
682+
assert.Nil(t, err)
683+
684+
method, ok := result.Method.(*OmitEmptyMethod)
685+
assert.True(t, ok)
686+
assert.Equal(t, "omit_empty", method.Name())
687+
assert.Equal(t, "value", method.Required)
688+
689+
assert.NotNil(t, method.Optional)
690+
assert.Equal(t, "hello", *method.Optional)
691+
692+
assert.NotNil(t, method.Count)
693+
assert.Equal(t, uint32(7), *method.Count)
694+
}

0 commit comments

Comments
 (0)