Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: support for embedded / raw json #18

Closed
maj-o opened this issue May 5, 2018 · 7 comments
Closed

Proposal: support for embedded / raw json #18

maj-o opened this issue May 5, 2018 · 7 comments
Assignees
Labels
enhancement New feature or request

Comments

@maj-o
Copy link

maj-o commented May 5, 2018

Some kind of embedded json saved handled as raw []byte is usefull.
If the type of value does not matter or is unknown at this point of computation. Taking a modified json rpc request as example for test, shows the benefits. A router is interested in id and method. The method itself is interested in params. So params should not be routers problem. I made the changes, so it works for me. It would be great, if the code could be reviewed and added in some way.

Proposed code for decoder.go:

// Decode reads the next JSON-encoded value from its input and stores it in the value pointed to by v.
//
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
func (dec *Decoder) Decode(v interface{}) error {
	if dec.isPooled == 1 {
		panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
	}
	switch vt := v.(type) {
	case *string:
		return dec.decodeString(vt)
	case *int:
		return dec.decodeInt(vt)
	case *int32:
		return dec.decodeInt32(vt)
	case *uint32:
		return dec.decodeUint32(vt)
	case *int64:
		return dec.decodeInt64(vt)
	case *uint64:
		return dec.decodeUint64(vt)
	case *float64:
		return dec.decodeFloat64(vt)
	case *bool:
		return dec.decodeBool(vt)
	case UnmarshalerObject:
		_, err := dec.decodeObject(vt)
		return err
	case UnmarshalerArray:
		_, err := dec.decodeArray(vt)
		return err
	case *EmbeddedJson:    // <-----------------------------------------  NEW
		return dec.decodeEmbeddedJson(vt)     // <------------  NEW
	default:
		return InvalidUnmarshalError(fmt.Sprintf(invalidUnmarshalErrorMsg, reflect.TypeOf(vt).String()))
	}
}


// >> NEW >>

type EmbeddedJson []byte

func (dec *Decoder) decodeEmbeddedJson(ej *EmbeddedJson) error {
	var err error
	if ej == nil {
		ej = &EmbeddedJson{}
	}
	beginOfEmbeddedJson := dec.cursor
	for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
		switch dec.data[dec.cursor] {
		case ' ', '\n', '\t', '\r', ',':
			continue
		// is null
		case 'n', 't':
			dec.cursor = dec.cursor + 4
		// is false
		case 'f':
			dec.cursor = dec.cursor + 5
		// is an object
		case '{':
			dec.cursor = dec.cursor + 1
			dec.cursor, err = dec.skipObject()
		// is string
		case '"':
			dec.cursor = dec.cursor + 1
			err = dec.skipString()                    // why no new dec.cursor in result?
		// is array
		case '[':
			dec.cursor = dec.cursor + 1
			dec.cursor, err = dec.skipArray()
		case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-':
			dec.cursor, err = dec.skipNumber()
		}
		break
	}
	if err == nil {
		if dec.cursor-1 > beginOfEmbeddedJson {
			*ej = append(*ej, dec.data[beginOfEmbeddedJson:dec.cursor]...)
		}
	}
	return err
}

func (dec *Decoder) AddEmbeddedJson(v *EmbeddedJson) error {
	err := dec.decodeEmbeddedJson(v)
	if err != nil {
		return err
	}
	dec.called |= 1
	return nil
}

// << NEW <<

QUICK TEST:

type Request struct {
	id     string
	method string
	params EmbeddedJson
	more   int
}

func (r *Request) UnmarshalObject(dec *Decoder, key string) error {
	switch key {
	case "id":
		return dec.AddString(&r.id)
	case "method":
		return dec.AddString(&r.method)
	case "params":
		return dec.AddEmbeddedJson(&r.params)
	case "more":
		dec.AddInt(&r.more)
	}
	return nil
}

func (r *Request) NKeys() int {
	return 4
}

func TestEmbeddedJson(t *testing.T) {
	json := []byte(`{"id":"someid","method":"getmydata","params":{"example":"of raw data"}, "more":123}`)
	request := &Request{}
	Unmarshal(json, request)
	t.Log(request)
	t.Log(string(request.params))
}

I know test is not complete - but shows, what the benefits are.
For json rpc the params are important inside of the method using them.
This change would make things simpler (simpler ist better ;-)


ENCODER:

func Marshal(v interface{}) ([]byte, error) {
	switch vt := v.(type) {
	case MarshalerObject:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeObject(vt)
	case MarshalerArray:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeArray(vt)
	case string:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeString(vt)
	case bool:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeBool(vt)
	case int:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeInt(vt)
	case int64:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeInt64(vt)
	case int32:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeInt(int(vt))
	case int16:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeInt(int(vt))
	case int8:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeInt(int(vt))
	case uint64:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeInt(int(vt))
	case uint32:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeInt(int(vt))
	case uint16:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeInt(int(vt))
	case uint8:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeInt(int(vt))
	case float64:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeFloat(vt)
	case float32:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeFloat32(vt)
	case *EmbeddedJson:                   // << ------------ NEW
		enc := BorrowEncoder(nil)         // << ------------ NEW
		defer enc.Release()               // << ------------ NEW
		return enc.encodeEmbeddedJson(vt) // << ------------ NEW
	default:
		return nil, InvalidMarshalError(fmt.Sprintf(invalidMarshalErrorMsg, reflect.TypeOf(vt).String()))
	}
}

// >> NEW >>

func (enc *Encoder) encodeEmbeddedJson(v *EmbeddedJson) ([]byte, error) {
	enc.writeBytes(*v)
	return enc.buf, nil
}

func (enc *Encoder) AddEmbeddedJson(v *EmbeddedJson) {
	enc.grow(len(*v) + 4)
	r, ok := enc.getPreviousRune()
	if ok && r != '[' {
		enc.writeByte(',')
	}
	enc.writeBytes(*v)
}

func (enc *Encoder) AddEmbeddedJsonOmitEmpty(v *EmbeddedJson) {
	if v == nil || len(*v) == 0 {
		return
	}
	r, ok := enc.getPreviousRune()
	if ok && r != '[' {
		enc.writeByte(',')
	}
	enc.writeBytes(*v)
}

func (enc *Encoder) AddEmbeddedJsonKey(key string, v *EmbeddedJson) {
	enc.grow(len(key) + len(*v) + 5)
	r, ok := enc.getPreviousRune()
	if ok && r != '{' {
		enc.writeByte(',')
	}
	enc.writeByte('"')
	enc.writeStringEscape(key)
	enc.writeBytes(objKey)
	enc.writeBytes(*v)
}

func (enc *Encoder) AddEmbeddedJsonKeyOmitEmpty(key string, v *EmbeddedJson) {
	if v == nil || len(*v) == 0 {
		return
	}
	enc.grow(len(key) + len(*v) + 5)
	r, ok := enc.getPreviousRune()
	if ok && r != '{' {
		enc.writeByte(',')
	}
	enc.writeByte('"')
	enc.writeStringEscape(key)
	enc.writeBytes(objKey)
	enc.writeBytes(*v)
}


// << NEW <<

Encoder, Decoder Test:

type Request struct {
	id     string
	method string
	params EmbeddedJson
	more   int
}

func (r *Request) UnmarshalObject(dec *Decoder, key string) error {
	switch key {
	case "id":
		return dec.AddString(&r.id)
	case "method":
		return dec.AddString(&r.method)
	case "params":
		return dec.AddEmbeddedJson(&r.params)

	case "more":
		dec.AddInt(&r.more)
	}
	return nil
}

func (r *Request) NKeys() int {
	return 4
}

func (r *Request) MarshalObject(enc *Encoder) {
	enc.AddStringKeyOmitEmpty("id", r.id)
	enc.AddStringKey("method", r.method)
	enc.AddEmbeddedJsonKey("params", &r.params)
	enc.AddIntKeyOmitEmpty("more", r.more)
}
func (r *Request) IsNil() bool {
	return r == nil
}

var request = &Request{}

func TestDecodeEmbeddedJson(t *testing.T) {
	json := []byte(`{"id":"someid","method":"getmydata","params":{"example":"of raw data"},"more":123}`)
	Unmarshal(json, request)
	t.Log(request)
	t.Log(string(request.params))
}

func TestEncodeEmbeddedJson(t *testing.T) {
	json := `{"id":"someid","method":"getmydata","params":{"example":"of raw data"},"more":123}`
	marshalled, err := MarshalObject(request)
	if err != nil {
		t.Error(err)
	}
	t.Log(string(marshalled))
	if string(marshalled) != json {
		t.Error("bad", string(marshalled), " != ", json)
	}
}
@francoispqt
Copy link
Owner

francoispqt commented May 5, 2018

Hi, thank you for your proposal, as discussed before, it is definitely an interesting feature.

I'm going to review your implementation and will let you know my feedback as soon as possible.

@maj-o
Copy link
Author

maj-o commented May 5, 2018 via email

@francoispqt
Copy link
Owner

francoispqt commented May 6, 2018

Hi Andreas,

Sorry was a bit late yesterday when I started reviewing your proposal (I live in Hong Kong). I am creating a branch, where I will add the feature.

branch name:
feature/support-for-embedded-json

If you want to contribute you can submit pull request to that branch.

When ready will merge the branch along with another branch where I added some optimisations, this will be a new release 0.10.5.

@francoispqt
Copy link
Owner

Pull request is open here #19
You can see evolution of implementation. Should be completed soon.

@francoispqt
Copy link
Owner

I renamed EmbeddedJson to EmbeddedJSON as it conventional to go (golint was actually complaining about EmbeddedJson.

Also on the decoding part, I am returning an error if the EmbeddedJSON is a nil pointer.

@francoispqt francoispqt added the enhancement New feature or request label May 6, 2018
@francoispqt francoispqt self-assigned this May 6, 2018
@francoispqt
Copy link
Owner

francoispqt commented May 6, 2018

Version 0.10.5 has been released. Will add documentation when I have time. Closing the issue.

@maj-o
Copy link
Author

maj-o commented May 6, 2018

C H A P E A U !

Had no time to take a look earlier.
No errors or wishes so far (passes all tests) - code is readable and performant - does what it should. :-)
GREAT ! Thank You very much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants