fastjson icon indicating copy to clipboard operation
fastjson copied to clipboard

Ability to read value bytes?

Open JeanMertz opened this issue 6 years ago • 6 comments

This library looks great, and I love the simple API. I considered using this + fasttemplate to transform JSON messages.

fasttemplate supports passing a byte slice as the value to be used for a template tag, but I don't see any way to get the byte slice of a value using this library. I mostly don't care about its type, I just want the underlying value at a given path, and be able to either 1) transform those bytes myself, or 2) pass those bytes as-is along to fasttemplate to put in a new JSON structure.

Right now I'm using jsonparser to do exactly this, by using jsonparser.EachKey, which returns the value as bytes, but it has some extra overhead compared to fastjson.

Would adding a Value.Bytes() function be something to consider here? Or is there another way you'd propose tackling this problem without sacrificing performance?

edit just to make sure: I'm aware of fastjson.GetBytes, but I need to fetch 40+ values in the JSON payload, so this is significantly slower than Parser.ParseByte combined with something like Value.GetBytes

JeanMertz avatar Jun 14 '18 13:06 JeanMertz

@JeanMertz , it looks like you need Value.GetStringBytes :

var p fastjson.Parser
v, err := p.ParseBytes(data)
if err != nil {
    log.Fatal(err)
}

sb1 := v.GetStringBytes("foo", "bar", "baz")
sb2 := v.GetStringBytes("aaa", "0", "c")
// etc.
sb40 := v.GetStringBytes("xyz")

The only difference with jsonparser.EachKey is that Value.GetStringBytes returns only string values by the requested path. It doesn't return string representation for composite values such as objects and arrays.

See also performance tips if you want achieving the maximum performance from fastjson.

valyala avatar Jun 15 '18 13:06 valyala

Thanks @valyala.

The problem is that I don't know what type is present at a specific path, and I don't want to know (since that knowledge is irrelevant for this use-case) all I want to know is

  1. if a value is present at a specific paths (which GetStringBytes handles, by returning nil if no value is present), and
  2. I want to get whatever the value is, as a slice of bytes, so I can do with it whatever I want (in this case, for some values, I want to put them in a different location of the payload, and for other cases, I first want to do some byte-level transformations).

That's why I thought this use-case could be tackled by allowing access to the underlying bytes of a Value with Value.Bytes().

If you know of a different solution, I'm all ears. If there isn't one, and you don't think giving access to the underlying bytes is something you'd like to expose, that's also fine, I can work around it for now by using jsonparser, as I mentioned.


Looking at the code, it seems the underlying bytes aren't directly (internally) accessible on the Value struct:

https://github.com/valyala/fastjson/blob/8ad826f8a1f0034c5a34bb5713b819f4194c3154/parser.go#L475-L487

So achieving this would probably include more overhead than you feel comfortable with. Again, no problem, I just wanted to flag this use-case and see what could be achieved.

JeanMertz avatar Jun 15 '18 14:06 JeanMertz

Unfortunately currently fastjson doesn't provide fast access to the underlying byte slice for JSON values. There is Value.String, but it is for debugging purposes only.

Let's keep this issue open. I'll try implementing fast access to any JSON value bytes (including objects and arrays)

valyala avatar Jun 15 '18 14:06 valyala

@JeanMertz , I added Value.MarshalTo for fast json serialization into byte slice. See the example on how to use it.

valyala avatar Jul 13 '18 16:07 valyala

That's awesome. Thank you for that. I'm going to take it for a spin soon.

JeanMertz avatar Jul 15 '18 14:07 JeanMertz

FYI, now it's possible to parse, modify and marshal modified json:

// Parse the json
v, err := fastjson.Parse(`{"foo":"bar", "baz": 123}`)
if err != nil {
    log.Fatal(err)
}

// Add new items to it
var a Arena
v.Set("newInt", a.NewNumberInt(42))

// Remove old items
v.Del("foo")

// Marshal the modified json to byte slice
buf := v.MarshalTo(nil)

// buf must contain `{"baz":123,"newInt":42}`

valyala avatar Jan 21 '19 03:01 valyala