valyala / fastjson

Fast JSON parser and validator for Go. No custom structs, no code generation, no reflection

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

how to use func (*Value) Set

Arnold1 opened this issue · comments

Hi,

i have the following, how can i add a key/value to the json?

// mapping.data is []*fastjson.Value

for _, val := range mapping.data {
   settings := getSettings(val.Get("Type"))

   val.Set("ID", fastjson.MustParse(settings.ID))
}

i currently get: panic: cannot parse JSON: cannot parse empty string; unparsed tail: ""

settings.ID must contain valid JSON if it is passed to fastjson.MustParse. An empty string isn't valid JSON. It must be wrapped into double quotes to become valid JSON.

@valyala oh, my bad. i need to do:

// mapping.data is []*fastjson.Value

for _, val := range mapping.data {
   settings := getSettings(val.Get("Type"))

   if settings != nil {
       val.Set("ID", fastjson.MustParse("\"" + settings.ID + "\""))
   }
}

maybe its also to do error handling by myself (using: fastjson.Parse) and not use fastjson.MustParse...

@valyala what happens if settings.ID is a int64 and i want the value to be an integer and not string? val.Set("ID", settings.ID)

@valyala what happens if settings.ID is a int64 and i want the value to be an integer and not string? val.Set("ID", settings.ID)

@valyala

before:

val.Set("ID", fastjson.MustParse("\""+strconv.FormatUint(settings.ID, 10)+"\""))

how to use NewNumberInt here? how to use Arena for that? may i ask why uses arena compared to fastjson.MustParse?

val.Set("ID", fastjson.NewNumberInt(settings.ID))

@valyala not sure how to use arena, its kinda strange to me...
how to pass it to existing val.Set ?

The following code shows basic usage for Arena:

var a fastjson.Arena
val.Set("ID", a.NewNumberInt(settings.ID))

This usage isn't optimal from performance point of view, since the Arena isn't re-used. This means it allocates memory with each NewNumberInt call. The optimal usage is quite sophisticated - read Arena docs.

can you also do that?

for _, val := range mapping.data {
   settings := getSettings(val.Get("Type"))

   if settings != nil {
      var a fastjson.Arena
      val.Set("ID", a.NewNumberInt(settings.ID))
      val.Set("ID2", a.NewNumberString(settings.ID2))
   }
}

...will that reallocate each time?

also are you allowed to do the following?

// mapping.data is []*fastjson.Value

for _, val := range mapping.data {
   settings := getSettings(val.Get("Type"))

   if settings != nil {
       val.Set("ID", fastjson.MustParse("\"" + settings.ID + "\""))
       val.Set("ID2", fastjson.MustParse("\"" + settings.ID2 + "\""))
   }
}

fastjson.MustParse/fastjson.Parse ... Parse parses s containing JSON. The returned value is valid until the next call to Parse*.

can you also do that?

Yes. It is better to allocate the arena only once before the loop:

var a fastjson.Arena
for _, val := range mapping.data {
   // ...
}

also are you allowed to do the following?

Yes, this is OK, since fastjson.Parse and fastjson.MustParse allocates memory for the returned value. The The returned value is valid until the next call to Parse*. comment corresponds to fastjson.Parser.Parse, which re-uses the returned object on the next call.

The best approach to use fastjson is the following:

var pp fastjson.ParserPool
var ap fastjson.ArenaPool

// ParseAndProcessJSON parses JSON in s and processes it in zero-allocation mode.
// It doesn't hold any values returned from `fastjson.Parser.Parse` call.
func ParseAndProcessJSON(s string) error {
  p := pp.Get()
  defer pp.Put(p)
  v, err := p.Parse(s)
  if err != nil {
    return fmt.Errorf("cannot parse JSON %q: %s", s, err)
  }

  // Get an arena from pool for allocating objects for v inside processJSON below.
  a := ap.Get()
  defer ap.Put(a)

  // processJSON shouldn't hold any references to v, to child objects of v and to a.
  // Otherwise data races will occur in the following cases:
  //    * when Parse is called on the re-used `p`
  //    * when `a` is re-used
  if err := processJSON(v, a); err != nil {
    return fmt.Errorf("cannot process JSON from %q: %s", s, err)
  }
  return nil
}

do you suggest to define var pp fastjson.ParserPool and var ap fastjson.ArenaPool global?

how many elements does it preallocate for fastjson.ArenaPool and fastjson.ArenaPool? dont you need to check if ap.Get() is nil in case all elements are used from pool?

what happens if ParseAndProcessJSON gets called by multiple go routines?

do you suggest to define var pp fastjson.ParserPool and var ap fastjson.ArenaPool global?

yes

how many elements does it preallocate for fastjson.ArenaPool and fastjson.ArenaPool? dont you need to check if ap.Get() is nil in case all elements are used from pool?

Pools don't pre-allocate elements - the elements are allocated when Get is called and the pool is empty.

what happens if ParseAndProcessJSON gets called by multiple go routines?

It should just work if processJSON function adheres rules described in comments above the call.

@valyala

i call defer pp.Put(p) in ParseAndProcessJSON
question: can i still iterate over sendData *fastjson.Value in sendData and call req.SetBody(val.String()) ?

var pp fastjson.ParserPool
var ap fastjson.ArenaPool

func processJSON(a, v, mapping) error {
    for _, val := range mapping.data {
       settings := getSettings(val.Get("Type"))

       if settings != nil {
           val.Set("ID", a.NewString(settings.ID)) // here i use a from fastjson.ArenaPool
           val.Set("ID2", a.NewString(settings.ID2)) // here i use a from fastjson.ArenaPool
       }
    }
}

// ParseAndProcessJSON parses JSON in s and processes it in zero-allocation mode.
// It doesn't hold any values returned from `fastjson.Parser.Parse` call.
func ParseAndProcessJSON(s string, mapping) error {
  // allocate mapping

  p := pp.Get()
  defer pp.Put(p)

  v, err := p.Parse(s)
  if err != nil {
    return fmt.Errorf("cannot parse JSON %q: %s", s, err)
  }

  // Get an arena from pool for allocating objects for v inside processJSON below.
  a := ap.Get()
  defer ap.Put(a)

  // processJSON shouldn't hold any references to v, to child objects of v and to a.
  // Otherwise data races will occur in the following cases:
  //    * when Parse is called on the re-used `p`
  //    * when `a` is re-used
  if err := processJSON(v, a, mapping); err != nil {
    return fmt.Errorf("cannot process JSON from %q: %s", s, err)
  }
  return nil
}


func bidRequestRoute(ctx *fasthttp.RequestCtx) error {
    mapping := ParseAndProcessJSON(ctx) // i do defer pp.Put(p) here, question: can i still iterate over sendData *fastjson.Value in sendData?

    var wg sync.WaitGroup
    for _, val := range mapping {
        wg.Add(1)
        go sendData(data)
    }
  
   wg.Wait()

   // read sendData response...
   // build reponse to ctx request...
   ctx.SetStatusCode(fasthttp.StatusOK)
}

question: can i still iterate over sendData *fastjson.Value in sendData and call req.SetBody(val.String()) ?

This is unsafe, since the parser that owns the returned value has been put back to the pool. This means that concurrent goroutine can obtain it from the pool and call Parse on it while the current goroutine iterates over the mapping. All the iteration and json value processing must be performed inside processJSON function.

can i move the .Get and .Put right before mapping := ParseAndProcessJSON(ctx) - inside func bidRequestRoute(ctx *fasthttp.RequestCtx) error {? that would fix it right?

I'm sorry, but I don't follow what do you mean. Could you provide example code?

@valyala i have the following benchmark, any idea how it fails inside the for loop at iteration 512?!

func BenchmarkTestConsumerMappingUUIDSplitTest2(b *testing.B) {
	expectedConsumerLength := 1
	expectedEntitiesLength := 1
	exchangeID := "1" // default consumer doesnt care about exchangeID

	cMapAV, err := loadCampaignStore("campaignStoreApiTestData3")
	require.NoErr(err)

	getCMap := multiConsumer(
		newDefaultTestConsumer())

	uuid := []byte("6a191e5f-9a6e-0405-d6ba-73f722fe0131")
	cid := []byte("6a191e5f-9a6e-0405-d6ba-73f722fe0131")

	b.ResetTimer()
	fmt.Println("b.N:", b.N)

	for i := 0; i < b.N; i++ {
		parsed, err := fastjson.ParseBytes(loadTestJSON("sendData"))
		require.NoErr(err)

		fmt.Println("i:", i)

		a := global.fastJSONArenaPool.Get()

		bidRequestPieces, mapping := getConsumerMappings(a, parsed, global.defaultConsumerName, exchangeID, 0, getCMap(), uuid, cid, getCampaignSettingMapFunc(cMapAV))
		require.NotNil(bidRequestPieces)
		require.Len(mapping, expectedConsumerLength)
		hashedName := hashIt(global.defaultConsumerName)
		require.Len(mapping[hashedName].selectedEntities, expectedEntitiesLength)

		a.Reset()
		global.fastJSONArenaPool.Put(a)
	}
}

output:

$  go test -run=x -bench=. -benchmem
2019/08/15 09:11:41 main.go:331: Loading with 8 CPUS
goos: darwin
goarch: amd64
BenchmarkTestSendingBatchesRetries-8           	       2	 526029726 ns/op	  316412 B/op	    1225 allocs/op
b.N: 1
i: 0
BenchmarkTestConsumerMappingUUIDSplitTest2-8   	b.N: 100
i: 0
i: 1
i: 2
i: 3
i: 4
i: 5
i: 6
i: 7
i: 8
i: 9
i: 10
i: 11
i: 12
i: 13
i: 14
i: 15
i: 16
i: 17
i: 18
i: 19
i: 20
i: 21
i: 22
i: 23
i: 24
i: 25
i: 26
i: 27
i: 28
i: 29
i: 30
i: 31
i: 32
i: 33
i: 34
i: 35
i: 36
i: 37
i: 38
i: 39
i: 40
i: 41
i: 42
i: 43
i: 44
i: 45
i: 46
i: 47
i: 48
i: 49
i: 50
i: 51
i: 52
i: 53
i: 54
i: 55
i: 56
i: 57
i: 58
i: 59
i: 60
i: 61
i: 62
i: 63
i: 64
i: 65
i: 66
i: 67
i: 68
i: 69
i: 70
i: 71
i: 72
i: 73
i: 74
i: 75
i: 76
i: 77
i: 78
i: 79
i: 80
i: 81
i: 82
i: 83
i: 84
i: 85
i: 86
i: 87
i: 88
i: 89
i: 90
i: 91
i: 92
i: 93
i: 94
i: 95
i: 96
i: 97
i: 98
i: 99
b.N: 5000
i: 0
i: 1
i: 2
i: 3
i: 4
i: 5
i: 6
i: 7
i: 8
i: 9
i: 10
i: 11
i: 12
i: 13
i: 14
i: 15
i: 16
i: 17
i: 18
i: 19
i: 20
i: 21
i: 22
i: 23
i: 24
i: 25
i: 26
i: 27
i: 28
i: 29
i: 30
i: 31
i: 32
i: 33
i: 34
i: 35
i: 36
i: 37
i: 38
i: 39
i: 40
i: 41
i: 42
i: 43
i: 44
i: 45
i: 46
i: 47
i: 48
i: 49
i: 50
i: 51
i: 52
i: 53
i: 54
i: 55
i: 56
i: 57
i: 58
i: 59
i: 60
i: 61
i: 62
i: 63
i: 64
i: 65
i: 66
i: 67
i: 68
i: 69
i: 70
i: 71
i: 72
i: 73
i: 74
i: 75
i: 76
i: 77
i: 78
i: 79
i: 80
i: 81
i: 82
i: 83
i: 84
i: 85
i: 86
i: 87
i: 88
i: 89
i: 90
i: 91
i: 92
i: 93
i: 94
i: 95
i: 96
i: 97
i: 98
i: 99
i: 100
i: 101
i: 102
i: 103
i: 104
i: 105
i: 106
i: 107
i: 108
i: 109
i: 110
i: 111
i: 112
i: 113
i: 114
i: 115
i: 116
i: 117
i: 118
i: 119
i: 120
i: 121
i: 122
i: 123
i: 124
i: 125
i: 126
i: 127
i: 128
i: 129
i: 130
i: 131
i: 132
i: 133
i: 134
i: 135
i: 136
i: 137
i: 138
i: 139
i: 140
i: 141
i: 142
i: 143
i: 144
i: 145
i: 146
i: 147
i: 148
i: 149
i: 150
i: 151
i: 152
i: 153
i: 154
i: 155
i: 156
i: 157
i: 158
i: 159
i: 160
i: 161
i: 162
i: 163
i: 164
i: 165
i: 166
i: 167
i: 168
i: 169
i: 170
i: 171
i: 172
i: 173
i: 174
i: 175
i: 176
i: 177
i: 178
i: 179
i: 180
i: 181
i: 182
i: 183
i: 184
i: 185
i: 186
i: 187
i: 188
i: 189
i: 190
i: 191
i: 192
i: 193
i: 194
i: 195
i: 196
i: 197
i: 198
i: 199
i: 200
i: 201
i: 202
i: 203
i: 204
i: 205
i: 206
i: 207
i: 208
i: 209
i: 210
i: 211
i: 212
i: 213
i: 214
i: 215
i: 216
i: 217
i: 218
i: 219
i: 220
i: 221
i: 222
i: 223
i: 224
i: 225
i: 226
i: 227
i: 228
i: 229
i: 230
i: 231
i: 232
i: 233
i: 234
i: 235
i: 236
i: 237
i: 238
i: 239
i: 240
i: 241
i: 242
i: 243
i: 244
i: 245
i: 246
i: 247
i: 248
i: 249
i: 250
i: 251
i: 252
i: 253
i: 254
i: 255
i: 256
i: 257
i: 258
i: 259
i: 260
i: 261
i: 262
i: 263
i: 264
i: 265
i: 266
i: 267
i: 268
i: 269
i: 270
i: 271
i: 272
i: 273
i: 274
i: 275
i: 276
i: 277
i: 278
i: 279
i: 280
i: 281
i: 282
i: 283
i: 284
i: 285
i: 286
i: 287
i: 288
i: 289
i: 290
i: 291
i: 292
i: 293
i: 294
i: 295
i: 296
i: 297
i: 298
i: 299
i: 300
i: 301
i: 302
i: 303
i: 304
i: 305
i: 306
i: 307
i: 308
i: 309
i: 310
i: 311
i: 312
i: 313
i: 314
i: 315
i: 316
i: 317
i: 318
i: 319
i: 320
i: 321
i: 322
i: 323
i: 324
i: 325
i: 326
i: 327
i: 328
i: 329
i: 330
i: 331
i: 332
i: 333
i: 334
i: 335
i: 336
i: 337
i: 338
i: 339
i: 340
i: 341
i: 342
i: 343
i: 344
i: 345
i: 346
i: 347
i: 348
i: 349
i: 350
i: 351
i: 352
i: 353
i: 354
i: 355
i: 356
i: 357
i: 358
i: 359
i: 360
i: 361
i: 362
i: 363
i: 364
i: 365
i: 366
i: 367
i: 368
i: 369
i: 370
i: 371
i: 372
i: 373
i: 374
i: 375
i: 376
i: 377
i: 378
i: 379
i: 380
i: 381
i: 382
i: 383
i: 384
i: 385
i: 386
i: 387
i: 388
i: 389
i: 390
i: 391
i: 392
i: 393
i: 394
i: 395
i: 396
i: 397
i: 398
i: 399
i: 400
i: 401
i: 402
i: 403
i: 404
i: 405
i: 406
i: 407
i: 408
i: 409
i: 410
i: 411
i: 412
i: 413
i: 414
i: 415
i: 416
i: 417
i: 418
i: 419
i: 420
i: 421
i: 422
i: 423
i: 424
i: 425
i: 426
i: 427
i: 428
i: 429
i: 430
i: 431
i: 432
i: 433
i: 434
i: 435
i: 436
i: 437
i: 438
i: 439
i: 440
i: 441
i: 442
i: 443
i: 444
i: 445
i: 446
i: 447
i: 448
i: 449
i: 450
i: 451
i: 452
i: 453
i: 454
i: 455
i: 456
i: 457
i: 458
i: 459
i: 460
i: 461
i: 462
i: 463
i: 464
i: 465
i: 466
i: 467
i: 468
i: 469
i: 470
i: 471
i: 472
i: 473
i: 474
i: 475
i: 476
i: 477
i: 478
i: 479
i: 480
i: 481
i: 482
i: 483
i: 484
i: 485
i: 486
i: 487
i: 488
i: 489
i: 490
i: 491
i: 492
i: 493
i: 494
i: 495
i: 496
i: 497
i: 498
i: 499
i: 500
i: 501
i: 502
i: 503
i: 504
i: 505
i: 506
i: 507
i: 508
i: 509
i: 510
i: 511
i: 512
2019/08/15 09:11:46 require.go:57: map[]
2019/08/15 09:11:46 require.go:20: 
	File: consumerMapping_test.go:156 // this is line: require.Len(mapping, expectedConsumerLength)
	Got: 0
	Expected: 1
exit status 1

It is hard to determine the root cause of the issue without the contents of loadTestJSON and getConsumerMappings function bodies.
Try moving loadTestJSON call outside the loop.

it was an issue with my test :)

@valyala i try to to the following but i dont get back the expected result. any idea what i do wrong?

func readInt32(b []byte) int32 {
	return int32(binary.LittleEndian.Uint32(b))
	// equivalnt of return int32(binary.LittleEndian.Uint32(b))
	//return int32(uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24)
}

func BenchmarkParsing(b *testing.B) {
	var p fastjson.Parser
	v, err := p.Parse(`{
        "ID": "12345"
	}`)
	require.Nil(err)

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		id := readInt32(v.GetStringBytes("ID"))
		require.Nil(err)
		require.Equal(id, int32(12345))
	}
}

You need to use strconv.ParseInt instead of binary.LittleEndian.Uint32.

@valyala ok my bad.