Giter Site home page Giter Site logo

ffjson's Introduction

ffjson: faster JSON for Go

Build Status Fuzzit Status

ffjson generates static MarshalJSON and UnmarshalJSON functions for structures in Go. The generated functions reduce the reliance upon runtime reflection to do serialization and are generally 2 to 3 times faster. In cases where ffjson doesn't understand a Type involved, it falls back to encoding/json, meaning it is a safe drop in replacement. By using ffjson your JSON serialization just gets faster with no additional code changes.

When you change your struct, you will need to run ffjson again (or make it part of your build tools).

Blog Posts

Getting Started

If myfile.go contains the struct types you would like to be faster, and assuming GOPATH is set to a reasonable value for an existing project (meaning that in this particular example if myfile.go is in the myproject directory, the project should be under $GOPATH/src/myproject), you can just run:

go get -u github.com/pquerna/ffjson
ffjson myfile.go
git add myfile_ffjson.go

Performance Status:

  • MarshalJSON is 2x to 3x faster than encoding/json.
  • UnmarshalJSON is 2x to 3x faster than encoding/json.

Features

  • Unmarshal Support: Since v0.9, ffjson supports Unmarshaling of structures.
  • Drop in Replacement: Because ffjson implements the interfaces already defined by encoding/json the performance enhancements are transparent to users of your structures.
  • Supports all types: ffjson has native support for most of Go's types -- for any type it doesn't support with fast paths, it falls back to using encoding/json. This means all structures should work out of the box. If they don't, open a issue!
  • ffjson: skip: If you have a structure you want ffjson to ignore, add ffjson: skip to the doc string for this structure.
  • Extensive Tests: ffjson contains an extensive test suite including fuzz'ing against the JSON parser.

Using ffjson

ffjson generates code based upon existing struct types. For example, ffjson foo.go will by default create a new file foo_ffjson.go that contains serialization functions for all structs found in foo.go.

Usage of ffjson:

        ffjson [options] [input_file]

ffjson generates Go code for optimized JSON serialization.

  -go-cmd="": Path to go command; Useful for `goapp` support.
  -import-name="": Override import name in case it cannot be detected.
  -nodecoder: Do not generate decoder functions
  -noencoder: Do not generate encoder functions
  -w="": Write generate code to this path instead of ${input}_ffjson.go.

Your code must be in a compilable state for ffjson to work. If you code doesn't compile ffjson will most likely exit with an error.

Disabling code generation for structs

You might not want all your structs to have JSON code generated. To completely disable generation for a struct, add ffjson: skip to the struct comment. For example:

// ffjson: skip
type Foo struct {
   Bar string
}

You can also choose not to have either the decoder or encoder generated by including ffjson: nodecoder or ffjson: noencoder in your comment. For instance, this will only generate the encoder (marshal) part for this struct:

// ffjson: nodecoder
type Foo struct {
   Bar string
}

You can also disable encoders/decoders entirely for a file by using the -noencoder/-nodecoder commandline flags.

Using ffjson with go generate

ffjson is a great fit with go generate. It allows you to specify the ffjson command inside your individual go files and run them all at once. This way you don't have to maintain a separate build file with the files you need to generate.

Add this comment anywhere inside your go files:

//go:generate ffjson $GOFILE

To re-generate ffjson for all files with the tag in a folder, simply execute:

go generate

To generate for the current package and all sub-packages, use:

go generate ./...

This is most of what you need to know about go generate, but you can sese more about go generate on the golang blog.

Should I include ffjson files in VCS?

That question is really up to you. If you don't, you will have a more complex build process. If you do, you have to keep the generated files updated if you change the content of your structs.

That said, ffjson operates deterministically, so it will generate the same code every time it run, so unless your code changes, the generated content should not change. Note however that this is only true if you are using the same ffjson version, so if you have several people working on a project, you might need to synchronize your ffjson version.

Performance pitfalls

ffjson has a few cases where it will fall back to using the runtime encoder/decoder. Notable cases are:

  • Interface struct members. Since it isn't possible to know the type of these types before runtime, ffjson has to use the reflect based coder.
  • Structs with custom marshal/unmarshal.
  • Map with a complex value. Simple types like map[string]int is fine though.
  • Inline struct definitions type A struct{B struct{ X int} } are handled by the encoder, but currently has fallback in the decoder.
  • Slices of slices / slices of maps are currently falling back when generating the decoder.

Reducing Garbage Collection

ffjson already does a lot to help garbage generation. However whenever you go through the json.Marshal you get a new byte slice back. On very high throughput servers this can lead to increased GC pressure.

Tip 1: Use ffjson.Marshal() / ffjson.Unmarshal()

This is probably the easiest optimization for you. Instead of going through encoding/json, you can call ffjson. This will disable the checks that encoding/json does to the json when it receives it from struct functions.

	import "github.com/pquerna/ffjson/ffjson"

	// BEFORE:
	buf, err := json.Marshal(&item)

	// AFTER:
	buf, err := ffjson.Marshal(&item)

This simple change is likely to double the speed of your encoding/decoding.

[![GoDoc][1]][2] [1]: https://godoc.org/github.com/pquerna/ffjson/ffjson?status.svg [2]: https://godoc.org/github.com/pquerna/ffjson/ffjson#Marshal

Tip 2: Pooling the buffer

On servers where you have a lot of concurrent encoding going on, you can hand back the byte buffer you get from json.Marshal once you are done using it. An example could look like this:

import "github.com/pquerna/ffjson/ffjson"

func Encode(item interface{}, out io.Writer) {
	// Encode
	buf, err := ffjson.Marshal(&item)
	
	// Write the buffer
	_,_ = out.Write(buf)
	
	// We are now no longer need the buffer so we pool it. 
	ffjson.Pool(buf)
}

Note that the buffers you put back in the pool can still be reclaimed by the garbage collector, so you wont risk your program building up a big memory use by pooling the buffers.

[![GoDoc][1]][2] [1]: https://godoc.org/github.com/pquerna/ffjson/ffjson?status.svg [2]: https://godoc.org/github.com/pquerna/ffjson/ffjson#Pool

Tip 3: Creating an Encoder

There might be cases where you need to encode many objects at once. This could be a server backing up, writing a lot of entries to files, etc.

To do this, there is an interface similar to encoding/json, that allow you to create a re-usable encoder. Here is an example where we want to encode an array of the Item type, with a comma between entries:

import "github.com/pquerna/ffjson/ffjson"

func EncodeItems(items []Item, out io.Writer) {
        // We create an encoder.
	enc := ffjson.NewEncoder(out)
	
	for i, item := range items {
		// Encode into the buffer
		err := enc.Encode(&item)
		
		// If err is nil, the content is written to out, so we can write to it as well.
		if i != len(items) -1 {
			_,_ = out.Write([]byte{','})
		}
	}
}

Documentation: [![GoDoc][1]][2] [1]: https://godoc.org/github.com/pquerna/ffjson/ffjson?status.svg [2]: https://godoc.org/github.com/pquerna/ffjson/ffjson#Encoder

Tip 4: Avoid interfaces

We don't want to dictate how you structure your data, but having interfaces in your code will make ffjson use the golang encoder for these. When ffjson has to do this, it may even become slower than using json.Marshal directly.

To see where that happens, search the generated _ffjson.go file for the text Falling back, which will indicate where ffjson is unable to generate code for your data structure.

Tip 5: ffjson all the things!

You should not only create ffjson code for your main struct, but also any structs that is included/used in your json code.

So if your struct looks like this:

type Foo struct {
  V Bar
}

You should also make sure that code is generated for Bar if it is placed in another file. Also note that currently it requires you to do this in order, since generating code for Foo will check if code for Bar exists. This is only an issue if Foo and Bar are placed in different files. We are currently working on allowing simultaneous generation of an entire package.

Improvements, bugs, adding features, and taking ffjson new directions!

Please open issues in Github for ideas, bugs, and general thoughts. Pull requests are of course preferred :)

Similar projects

  • go-codec. Very good project, that also allows streaming en/decoding, but requires you to call the library to use.
  • megajson. This has limited support, and development seems to have almost stopped at the time of writing.

Credits

ffjson has recieved significant contributions from:

License

ffjson is licensed under the Apache License, Version 2.0

ffjson's People

Contributors

bookmoons avatar cblomart avatar chapsuk avatar conradludgate avatar croath avatar dadabird avatar dancannon avatar dimfeld avatar dispalt avatar dx034 avatar ei-grad avatar erikdubbelboer avatar haraldnordgren avatar haskeef avatar hollow avatar i avatar klauspost avatar larsmans avatar nkovacs avatar philips avatar posener avatar pquerna avatar rasky avatar reillywatson avatar romankrasavtsev avatar rschmukler avatar ryboe avatar shawnps avatar sztanpet avatar zofrex avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ffjson's Issues

Proposal: Delete FILE_ffson.go before beginning inception

If the structure you are generating ffjson for has changed, it will likely fail when running ffjson FILE.go, because FILE_ffjson.go causes a compiler error.

I would propose to delete the previous FILE_ffjson.go automatically.

Create builtin time.Time marshal/unmarshal

Time.Format() is creeping up in a lot of performance charts, and since it is a very common property, it could maybe speed up things if there was a version that was optimized to only output RFC3339 timestamps.

Proposal: Add command line parameter to only create en/de-coder.

For some structures/files I know I will only need the encoder for instance. It would be nice to be able to disable one or the other.

It could also be a struct comment directive similar to //ffjson:skip - maybe //ffjson:encoderOnly and //ffjson:decoderOnly -

Preferably I would have both options (both as command-line and struct comment), but one would also be nice.

Add "go generate" instructions to documentation

With Go 1.4 out, we can use one of the new features "go generate" - this seems like a great fit for ffjson.

Actually it is extremely easy to add ffjson generation. In your files you would like to have generated, add the following line:

//go:generate ffjson $GOFILE

To re-generate ffjson for all files with the tag, simply execute:

go generate

To generate for the current package and all sub-packages, use:

go generate ./...

Decoder: Attempting to decode null value

FFJson should check if the value is null before calling Unmarshal on pointer values:

Test:

func TestTimeNullTimePtr(t *testing.T) {
    testType(t, &TtimePtr{}, &XtimePtr{})
}

Bootstrap buffer doesn't provide speedup

In both your test and in a more complex one, I get better performance when setting the size of the bootstrap buffer to 0 bytes:

BenchmarkComplexMarshalJSONNative         100000             17252 ns/op  76.80 MB/s
BenchmarkMarshalJSONNative       1000000              2364 ns/op          41.87MB/s

With bootstrap size set to 0:

BenchmarkComplexMarshalJSONNative         100000             16767 ns/op  79.02 MB/s
BenchmarkMarshalJSONNative       1000000              2104 ns/op          47.04 MB/s

The difference is pretty consistent on my machine. How does it look on yours? I would expect the slowdown to come from zeroing the array and in some cases copying the slice into a bigger one.

I have tried setting the size to both lower and higher values, and lower gives speedup, while higher gives slowdown. '0' was the fastest value.

v0.9: "label wraperr defined and not used" in generated code

You are probably aware of this, but the above error is present in code, if there is a structure that doesn't have a bool or integer.

So, either the "ujFunc" template needs to know if there is a field of that type present, or (more cleanly), simply replace:

    if err != nil {
        goto wraperr
    }

with

    if err != nil {
        return fs.WrapErr(err)
    }

And remove the label altogether.

panic: reflect: Elem of invalid type (when encoding struct with interface field)

This occurs in both release and development (0.9) version.

To reproduce, it is enough to try to generate for this struct:

type Foo struct {
 Bar interface{}
}

There is a bug in "getGetInnerValue" in "encoder.go", where there is this code is:

case reflect.Ptr,
        reflect.Interface:
        out += "if " + name + "!= nil {" + "\n"
--->    switch typ.Elem().Kind() {
        case reflect.Struct:
            out += getGetInnerValue(ic, name, typ.Elem(), false)
        default:
            out += getGetInnerValue(ic, "*"+name, typ.Elem(), false)
        }

'typ' is a reflect.Type - the documentation for reflect.Type.Elem() states:

"Elem returns a type's element type. It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice."

Since the case is for either a Ptr or an Interface, this function panics when an interface type is given.

Complete Output (dev version)

Error: Go Run Failed for: c:\temp\win\ffjson-inception354076639.go
STDOUT:

STDERR:
panic: reflect: Elem of invalid type

goroutine 16 [running]:
runtime.panic(0x91fa60, 0xc082190ed0)
        C:/Go/src/pkg/runtime/panic.c:279 +0x11f
reflect.(*rtype).Elem(0x917e40, 0x0, 0x0)
        C:/Go/src/pkg/reflect/type.go:597 +0x1c6
github.com/pquerna/ffjson/inception.getGetInnerValue(0xc0820b8150, 0xc082190ec0,
 0x7, 0x12c1b8, 0x917e40, 0x0, 0x0, 0x0)
        c:/GoPath/src/github.com/pquerna/ffjson/inception/encoder.go:138 +0x110e

github.com/pquerna/ffjson/inception.getValue(0xc0820b8150, 0xc08219f180, 0x0, 0x
0)
        c:/GoPath/src/github.com/pquerna/ffjson/inception/encoder.go:167 +0xae
github.com/pquerna/ffjson/inception.CreateMarshalJSON(0xc0820b8150, 0xc0821a7180
, 0x0, 0x0)
        c:/GoPath/src/github.com/pquerna/ffjson/inception/encoder.go:238 +0xb10
github.com/pquerna/ffjson/inception.(*Inception).generateCode(0xc0820b8150, 0x0,
 0x0)
        c:/GoPath/src/github.com/pquerna/ffjson/inception/inception.go:59 +0x9c
github.com/pquerna/ffjson/inception.(*Inception).Execute(0xc0820b8150)
        c:/GoPath/src/github.com/pquerna/ffjson/inception/inception.go:82 +0x1c3

main.main()
        c:/temp/win/ffjson-inception354076639.go:15 +0x1be

goroutine 19 [finalizer wait]:
runtime.park(0x414f20, 0xee8f38, 0xee6829)
        C:/Go/src/pkg/runtime/proc.c:1369 +0xac
runtime.parkunlock(0xee8f38, 0xee6829)
        C:/Go/src/pkg/runtime/proc.c:1385 +0x42
runfinq()
        C:/Go/src/pkg/runtime/mgc0.c:2644 +0xdd
runtime.goexit()
        C:/Go/src/pkg/runtime/proc.c:1445


goroutine 53 [runnable]:
text/template/parse.lexText(0xc082198b00, 0xc877c8)
        C:/Go/src/pkg/text/template/parse/lex.go:228 +0x388
text/template/parse.(*lexer).run(0xc082198b00)
        C:/Go/src/pkg/text/template/parse/lex.go:198 +0x47
created by text/template/parse.lex
        C:/Go/src/pkg/text/template/parse/lex.go:191 +0x119
exit status 2

Decide how to handle golang asymmetric string tags

While writing tests I encountered this issue:

golang/go#9812

Short story, golang removes quotes if string tag is set, but it is not added on encode, leading to encoded content that cannot be unmarshaled by itself.

My proposal to handling this would be to generate code that does the same, but prints a warning to the user that they are playing with fire (ie their code will only work one way)

[]byte and []uint8 decoding fails

Struct:

type ATbyte struct {
    X []byte
}

Fails with this content:

{"X":"VcELoXc4nIW9KPnUoVg="}

Message: "cannot unmarshal tok:string into Go value for offset=27 line=1 char=27"

Rename "fflib/v1" to "fflib.v2".

By using the trick that golang discards everything after a dot in package path.

This means that if we name the folder "fflib.v1" (or v2), the import path would look like this:

import "github.com/pquerna/ffjson/fflib.v1"

(Without needing to alias it)

And we could call the package "fflib" instead of "v1".

Does that make sense?

Renamed *time.Time gives bad inception.

Reproducer:

type R *time.Time

type A struct {
  B  R
}

Error:

mj.B.MarshalJSON undefined (type R has no field or method MarshalJSON)
uj.B.UnmarshalJSON undefined (type R has no field or method UnmarshalJSON)

Kind() vs Type()

If you have a type like this:

type mystring string

type Foo struct {
  Bar mystring
}

The generated code is currently incorrect. This is because we are using reflect.Type.Kind() to decide its a string, but then we don't convert it into a mystring when setting it or passing it to other functions.

We should add a general purpose function for casting types when they don't match the Kind().

Why sort entries by name?

Hi!

When I was writing some tests I did comparisons to output from encoding/json, and I noticed the field order was different.

I found out the fix for that was to simply remove the sort in reflect.

As a developer, I would actually appreciate if I could get them in the order they appear in the structs, so I don't really see a benefit from it. Is there a reason for the fields to be sorted?

Omitempty on pointer to value should only omit if not nil

Right now pointer to values are omitted if the value is the zero type. For instance:

type struct A {
     Rp *bool      `json:",omitempty"`
}
[...]
    if mj.Rp != nil {
        if *mj.Rp != false { // <<--- Error ---This should not be done
            if *mj.Rp {
                buf.WriteString(`"Rp":true`)
            } else {
                buf.WriteString(`"Rp":false`)
            }
        }
        buf.WriteByte(',')
    }
[...]

I should have time to look at this tomorrow.

Support unexported types

I have a package with several types that must be unexported, but are marshalled/unmarshalled to/from json. Running ffjson returns several errors:

> ffjson server.gen.go
Error: Go Run Failed for: /tmp/ffjson-inception430580992.go
STDOUT:

STDERR:
# command-line-arguments
/tmp/ffjson-inception430580992.go:19: cannot refer to unexported name chatservice.angoInMsg
/tmp/ffjson-inception430580992.go:21: cannot refer to unexported name chatservice.angoClientArgsDataDisplayNotification
/tmp/ffjson-inception430580992.go:23: cannot refer to unexported name chatservice.angoServerArgsDataAdd8
/tmp/ffjson-inception430580992.go:25: cannot refer to unexported name chatservice.angoServerRetsDataDostuff
/tmp/ffjson-inception430580992.go:27: cannot refer to unexported name chatservice.angoOutMsg
/tmp/ffjson-inception430580992.go:29: cannot refer to unexported name chatservice.angoClientRetsDataAskQuestion
/tmp/ffjson-inception430580992.go:31: cannot refer to unexported name chatservice.angoServerArgsDataDostuff
/tmp/ffjson-inception430580992.go:33: cannot refer to unexported name chatservice.angoOutError
/tmp/ffjson-inception430580992.go:37: cannot refer to unexported name chatservice.angoServerArgsDataAdd
/tmp/ffjson-inception430580992.go:39: cannot refer to unexported name chatservice.angoServerRetsDataAdd8
/tmp/ffjson-inception430580992.go:39: too many errors

:

I believe that creating ffjson with unexported types should be possible.

Add Google App Engine Compatibility

Google App Engine has wrapped Go in their own goapp binary, which includes references for appengine packages. When I try and run ffjson on my project, it throws errors when those are referenced.

STDERR:
..\..\mjibson\appstats\appstats.go:29:2: cannot find package "appengine" in any of:
        C:\Go\src\pkg\appengine (from $GOROOT)
        C:\google-cloud-sdk\platform\google_appengine\gopath\src\appengine (from $GOPATH)
..\..\mjibson\appstats\appstats.go:30:2: cannot find package "appengine/memcache" in any of:
        C:\Go\src\pkg\appengine\memcache (from $GOROOT)
        C:\google-cloud-sdk\platform\google_appengine\gopath\src\appengine\memcache (from $GOPATH)
..\..\mjibson\appstats\appstats.go:31:2: cannot find package "appengine/user" in any of:
        C:\Go\src\pkg\appengine\user (from $GOROOT)
        C:\google-cloud-sdk\platform\google_appengine\gopath\src\appengine\user (from $GOPATH)
..\..\mjibson\appstats\appstats.go:32:2: cannot find package "appengine_internal" in any of:
        C:\Go\src\pkg\appengine_internal (from $GOROOT)
        C:\google-cloud-sdk\platform\google_appengine\gopath\src\appengine_internal (from $GOPATH)

Scan Buffer Design

(this is just a collection of ideas, not a final design, feedback welcome)

In order to minimize the number of allocations for parsing of JSON we want to produce a new Buffer struct and some specialized functions.

The core idea is a Buffer structure that optimizes for:

  • Reduce copying of data multiple times.
  • Byte by Byte Scanning
  • Ability to Unread more than one byte.
  • Efficient behavior for streams of unbounded lengths
  • The ability to efficiently slice whole sections of the buffer into another bytes.Buffer so it can be turned into another type, eg a native String.

Currently the bytes.Buffer and bufio.Reader do not do all of these, and while the bufio.Reader is close, it operations on a single []byte buffer, which is hard to avoid copying data around many times during parsing.

I'm currently thinking through the internal implementation details, but I'm thinking about it like a doublly-linked-list, with a pointer kept to the active reading location, and once a buffer is fully consumed, it would be removed from the linked list.

Some proposed Interfaces:

  • Append: Takes a []byte to add to the current Scan Buffer.
  • Hold and Release: Hold prevents 'releasing' of sub-buffers inside at the current position. Once Release is called, any bytes before the current Pos() cannot be unread or used. (they would be removed from the scan buffer at this point)
  • SliceUntil or SliceWriteTo: Transfer a range into a bytes.Buffer. Internally this Potentially crosses multiple internal []byte on the linked list. This could either be based on a byte range, or until a specific byte is found. Once a byte that shouldn't be copied is found, it would allow the parse to go through it byte by byte to do special decoding.
type ScanableBufferIdeas interface {
     Append(input []byte)
     Hold()
     Release()
     Pos() int
     ReadByte() (byte, error)
     SliceUntil(testMap map[byte]bool, bb *bytes.Buffer) error
     SliceWriteTo(offset int, length int, bb *bytes.Buffer) error
}

float64 aliases generate broken code

Example: fftest.go

package fftest

type Price float64

type Item struct {
    Description string
    Cost        Price
}

go build shows no errors. After running ffjson fftest.go, go build reports ./fftest_ffjson.go:31: cannot use mj.Cost (type Price) as type float64 in argument to strconv.AppendFloat

Seems like the problem is in encoder.go:185. float32 aliases work because of the cast to float64 on line 182, and applying the same cast on 185 fixes the issue. Ints and strings also always go through a cast masking the issue when using types which alias them.

json "string" tag not supported

type StringTag struct {
    BoolStr bool   `json:",string"`
    IntStr  int64  `json:",string"`
    StrStr  string `json:",string"`
} 
    var s StringTag
    s.BoolStr = true
    s.IntStr = 42
    s.StrStr = "xzbit"

Generates

{
                 "BoolStr": true,
                 "IntStr": 42,
                 "StrStr": "xzbit"
}

It should be:

{
                 "BoolStr": "true",
                 "IntStr": "42",
                 "StrStr": "\"xzbit\""
}

How can I tell if it's working?

I'm quite new to Go (and ffjson), and I can't seem to figure out if I've actually installed (and I'm using) ffjson. Is there anything I need to import in my main.go?

Thanks!

Difference encoding float64 values

In the builtin json encoder, the precision of strconv.AppendFloat is set to "-1", instead of "10" as in ffjson.

Is there a reason for that, otherwise I think ffjson should just mimic the go encoder?

Handle embedded pointers (crash)

To reproduce:

type XEmbedThis struct {
    S string
}
// Pointer to embedded type
type XEmbedPtr struct {
    *XEmbedThis `json:",omitempty"`
}

Marshalling an XEmbedPtr{} will result in a panic: runtime error: invalid memory address or nil pointer dereference.

This is the generated code:

func (mj *XEmbedPtr) MarshalJSONBuf(buf fflib.EncodingBuffer) error {
    var err error
    var obj []byte
    _ = obj
    _ = err
    buf.WriteString(`{"S":`)
    fflib.WriteJsonString(buf, string(mj.S))
    buf.WriteByte('}')
    return nil
}

It should check if "mj.XEmbedThis" is nil before accessing mj.S. Not a trivial fix, I would expect.

Decoder generator failure []struct

The following file fails to generate valid code:

package test

type Custom struct {
    Subdivisions []struct {
        Name string
    }
}

The failing part of the generated code looks like this:

    if tok == fflib.FFTok_null {
        uj.Subdivisions = nil
    } else {    
-->>        uj.Subdivisions = make([], 0)
    }

Advice on fast vs slow types

The Readme notes that speed improvements will "depending on the structure". What data types will see the most or least improvements?

Error: Could not find source directory

I get the following error while trying to run ffjson for a file

Error: error=Could not find source directory: GOPATH=["/home/sztanpet/go/gocode"] REL="/home/sztanpet/go/destinyggchat" path="":

must REL be a relative path or something?

package/type not correctly noted

In playing with ffjson with https://github.com/coreos/go-etcd at 6fe04d580dfb71c9e34cbce2f4df9eefd1e1241e I get:

./response_ffjson.go:811: undefined: Time

when I try to build something that uses it. It's a quick fix to import "time" and change:

if uj.Expiration == nil {
            uj.Expiration = new(Time)
        }

to

if uj.Expiration == nil {
            uj.Expiration = new(time.Time)
        } 

Point me in the right direction and I may can dig around and figure out what's happening.

Decoder generator crash

The decoder crashes when generating for this struct:

type DecoderCrash struct {
    X []interface{}
}

The error reported is:

Error: Go Run Failed for: c:\temp\win\ffjson-inception114499035.go
STDOUT:

STDERR:
panic: non-numeric type passed in w/o name: uj.X [recovered]
        panic: non-numeric type passed in w/o name: uj.X [recovered]
        panic: non-numeric type passed in w/o name: uj.X

Decoder should not be case sensitive

This is a rather big one. It seems like golang doesn't care about case when looking for matching fields.

Test:

func TestCaseDiffentUnmarshal(t *testing.T) {
    base := Record{}
    ff := FFRecord{}

    err := json.Unmarshal([]byte(`{"ID": 123213, "originid": 22, "Meth": "GET"}`), &base)
    if err != nil {
        t.Fatalf("UnmarshalJSON: %v", err)
    }

    err = json.Unmarshal([]byte(`{"ID": 123213, "originid": 22, "Meth": "GET"}`), &ff)
    if err != nil {
        t.Fatalf("UnmarshalJSON: %v", err)
    }
    require.Equal(t, base, ff, "json.Unmarshal of Record")
}

Golang picks up the content of the fields, even though the case mismatches. ffjson should do the same.

v0.9: _expose.go files not deleted

I had a look at 0.9 - looks very good - awesome work.

On Windows, the "_expose.go" files are not deleted because the files aren't closed before they are deleted. On Unix this will defer the deletion until the program exits, but on Windows the operation will fail.

Inserting a defer im.tempExpose.Close() in inceptionmain.go around line 201 resolves the issue.

A similar problem is with "im.tempMain", which should have a defer im.tempMain.Close() around line 175 in the same file.

encoding/json not included in imports

My generated ffjson file does not include encoding/json in its imports list, but it looks like it is required for an embedded struct. So go build fails because the call to json.Marshal() fails: undefined: json.

Any ideas?

Recover in makeSlice?

Looking at the benchmark times, I noticed roughly 0.7% was spent in a defer in makeSlice. What is the reason for this defer. I am not sure if catching a panic, and sending a new one solves much?

// makeSlice allocates a slice of size n. If the allocation fails, it panics
// with ErrTooLarge.
func makeSlice(n int) []byte {
    // If the make fails, give a known error.
    defer func() {
        if recover() != nil {
            panic(ErrTooLarge)
        }
    }()
    return make([]byte, n)
}

ffjson should be the only package you need to import

github.com/pquerna/ffjson/ffjson should be the only package we tell users to import.

  • Need to add a ffjson.Pool() which just calls fflib.Pool. PR #109
  • Need to update README.md. Done in master
  • Add godoc links for it
  • Add a reusable Buffer to ffjson as per tip number 3.

Error encoding type where underlying value is slice of built-in type

Encoding this gives multiple errors in the generated code:

package test

import "encoding/xml"

type Custom struct {
    Header *xml.CharData `json:",omitempty"`
}

.\test_ffjson.go:37: cannot range over mj.Header (type *xml.CharData)

The generated code:

                for i, v := range mj.Header {
                    if i != 0 {
                        buf.WriteString(`,`)
                    }
                    fflib.FormatBits(&scratch, buf, uint64(v), 10, false)
                }

Here "range mj.Header" should be "range *mj.Header". Should be fixable.

However, the errors in the decoder:

.\test_ffjson.go:184: cannot use make([]uint8, 0) (type []uint8) as type *xml.CharData in assignment
.\test_ffjson.go:238: first argument to append must be slice; have *xml.CharData

These seem a bit harder to fix, since a fix would require importing "encoding/xml", or maybe just fall back on this type of values.

ffjson fails if package name is "main"

If you are generating inside a package called main, you get a name clash in inception:

TEMP\ffjson-inception201615175.go:12: main redeclared in this block
        previous declaration at TEMP\ffjson-inception201615175.go:8

The fix is rather simple, just rename the package on import. Here is the template that uses "importedinceptionpackage" (which I don't think will give any risk of collision):

const inceptionMainTemplate = `
// DO NOT EDIT!
// Code generated by ffjson <https://github.com/pquerna/ffjson>
// DO NOT EDIT!

package main

import (
    "github.com/pquerna/ffjson/inception"
    importedinceptionpackage "{{.ImportName}}"
)

func main() {
    i := ffjsoninception.NewInception("{{.InputPath}}", "{{.PackageName}}", "{{.OutputPath}}")
    i.AddMany(importedinceptionpackage.FFJSONExpose())
    i.Execute()
}
`

Escaping errors in map[string]string

It seems there are some escaping errors when dealing with map[string]string. I will send a PR with the test code, but here it is for reference.

type XMapStringString struct {
    X map[string]string
}

func TestMapStringString(t *testing.T) {
    m := map[string]string{"陫ʋsş\")珷<ºɖgȏ哙ȍ": "2ħ籦ö嗏ʑ>季"}
    testCycle(t, &TMapStringString{X: m}, &XMapStringString{X: m})
}

encoding/json decodes this without any issues. Ffjson reports "ffjson error: (*json.SyntaxError)invalid character ')' after object key offset=66 line=1 char=66".

Support "string" tag in decoder

Have I broken something, or is "string" unsupported in the decoder:

func TestForceStringTaggedDecoder(t *testing.T) {
    testCycle(t, &TstringTagged{}, &XstringTagged{})
    testCycle(t, &TintTagged{}, &XintTagged{})
    testCycle(t, &TboolTagged{}, &XboolTagged{})
}

Adding this test, it seems like the string tag is ignored, and the quotes aren't removed. Is this correct?

Decoder: []time.Duration field generates invalid code

I am writing tests now, adding this generates invalid code:

type AXduration struct {
    X []time.Duration
}

Generates these errors:

\ff_ffjson.go:364: undefined: Duration
\ff_ffjson.go:370: undefined: Duration

The offending code:

uj.X = make([]Duration, 0)
[...]
var v Duration

Note the missing "time." prefix.

Different encoding of value

I get a difference in encoding on a single value:

bug_81

ffjson:   "Q": "\"ċʇP\u003eɹ薷$ȢÝȼ@ȏL\"",
golang    "Q": "\"ċʇP\\u003eɹ薷$ȢÝȼ@ȏL\"",

I will submit a test that shows this, and update this bug.

Remove unneeded if first==true checks.

From what I can see there is not need to have generation time checks for "first", since they should be predictable by the code generator in "inception/encoder.go, func CreateMarshalJSON".

If I remove the "out += var first bool = true + "\n" and replace it with actual code doing the same, the generated code seems correct.

The change is simple. Remove all output of the "first" variable. Add similar functionality in the loop:

    first := true
    for _, f := range si.Fields {
        [...]
        if first {
            first = false
        } else {
            out += "buf.WriteString(`,`)" + "\n"
        }
        [...]
    }

That will make it much faster, since it eliminates unneeded branching on every field of a struct.

The change is trivial, but I can do a PR, if you'd like.

v0.9: Related issues (pointer receiver & redeclaring)

Here is a test case showing two related issues:

package models

type SomeInterface interface {
    MarshalJSON() ([]byte, error)
}

type Base struct{
    A string
}

type Model struct {
    Base
}

// Model should satisfy SomeInterface, which includes MarshalJSON without pointer receiver.
var _ SomeInterface = Model{}

func (b Base) MarshalJSON() ([]byte, error) {
    return nil, nil
}

This gives the following errors:

.\test.go:13: cannot use Model literal (type Model) as type SomeInterface in assignment:
        Model does not implement SomeInterface (MarshalJSON method has pointer receiver)
.\test_ffjson.go:147: method redeclared: Base.MarshalJSON
        method(Base) func() ([]byte, error)
        method(*Base) func() ([]byte, error)

When generating MarshalJSON you are adding it with a pointer receiver:
func (mj *Model) MarshalJSON() ([]byte, error) {

I realize that you are probably doing this to avoid a struct member copy, but that doesn't seem safe.

The second thing is that there is no checks to see if a struct already satisfies the json.Marshaler interface. If it does, I wouldn't expect ffjson to generate any code for it, since it says "Drop in Replacement" on the front page.

My proposal would be to check if structs satisfy json.Marshaler / json.Unmarshaler before generating code for them. Does that make sense?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.