Skip to content

Commit

Permalink
Document the unmarshaling of unknown parameter fields.
Browse files Browse the repository at this point in the history
Addresses #5. When unmarshaling parameters, unknown struct fields are not
allowed by the default encoder. Document how this can be overridden by the
caller, and add example code to demonstrate the techniques.
  • Loading branch information
creachadair committed Feb 22, 2020
1 parent 790f31f commit 2b3ce39
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 4 deletions.
13 changes: 9 additions & 4 deletions base.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type Handler interface {
// The inbound request is the same value passed to the Handle method -- the
// latter is primarily useful in handlers generated by handler.New, which do
// not receive this value directly.
Handle(context.Context, *Request) (interface{}, error)
Handle(ctx context.Context, req *Request) (interface{}, error)
}

// A Request is a request message from a client to a server.
Expand All @@ -67,9 +67,14 @@ func (r *Request) Method() string { return r.method }
// HasParams reports whether the request has non-empty parameters.
func (r *Request) HasParams() bool { return len(r.params) != 0 }

// UnmarshalParams decodes the parameters into v. If r has empty parameters, it
// returns nil without modifying v. If r is invalid it returns an InvalidParams
// error.
// UnmarshalParams decodes the request parameters of r into v. If r has empty
// parameters, it returns nil without modifying v. If r is invalid it returns
// an InvalidParams error.
//
// By default, unknown keys are disallowed when unmarshaling into a v of struct
// type. The caller may override this, either by implementing json.Unmarshaler
// on the concrete type of v, or by unmarshaling into a json.RawMessage and
// explicitly decoding the result. The examples demonstrate how to do this.
func (r *Request) UnmarshalParams(v interface{}) error {
if len(r.params) == 0 {
return nil
Expand Down
49 changes: 49 additions & 0 deletions examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package jrpc2_test

import (
"context"
"encoding/json"
"fmt"
"log"
"strings"

"github.com/creachadair/jrpc2"
"github.com/creachadair/jrpc2/channel"
"github.com/creachadair/jrpc2/code"
"github.com/creachadair/jrpc2/handler"
)

Expand Down Expand Up @@ -94,3 +96,50 @@ func ExampleClient_Batch() {
// len(rsps) = 1
// Response #1: Hello, world!
}

func ExampleRequest_UnmarshalParams() {
const msg = `{"jsonrpc":"2.0", "id":101, "method":"M", "params":{"a":1, "b":2, "c":3}}`

reqs, err := jrpc2.ParseRequests([]byte(msg))
if err != nil {
log.Fatalf("ParseRequests: %v", err)
}

var t, u struct {
A int `json:"a"`
B int `json:"b"`
}

// By default, unmarshaling prohibits unknown fields (here, "c").
err = reqs[0].UnmarshalParams(&t)
if code.FromError(err) != code.InvalidParams {
log.Fatalf("Expected invalid parameters, got: %v", err)
}

// Solution 1: Decode explicitly.
var tmp json.RawMessage
if err := reqs[0].UnmarshalParams(&tmp); err != nil {
log.Fatalf("UnmarshalParams: %v", err)
}
if err := json.Unmarshal(tmp, &t); err != nil {
log.Fatalf("Unmarshal: %v", err)
}
fmt.Printf("t.A=%d, t.B=%d\n", t.A, t.B)

// Solution 2: Provide a type (here, "lax") that implements json.Unmarshaler.
if err := reqs[0].UnmarshalParams((*lax)(&u)); err != nil {
log.Fatalf("UnmarshalParams: %v", err)
}
fmt.Printf("u.A=%d, u.B=%d\n", u.A, u.B)

// Output:
// t.A=1, t.B=2
// u.A=1, u.B=2
}

type lax struct{ A, B int }

func (v *lax) UnmarshalJSON(bits []byte) error {
type T lax
return json.Unmarshal(bits, (*T)(v))
}

0 comments on commit 2b3ce39

Please sign in to comment.