You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
refactor: major performance refactor and API improvements
This commit delivers significant performance improvements through reflection caching, direct type switching, and global regex optimization. Additionally enhances error reporting and API consistency.
Key changes:
- Add struct field metadata caching system
- Replace abstraction layers with raw switch statments
- Move timezone regex to global scope
- Implement field-specific error wrapping
- Make Parse function public
- Always initialize pointer fields
Performance gains range from 2.5x to 10.3x speedup with substantial memory allocation reductions across all benchmark scenarios.
`qparser` is a simple package that help parse query parameters into struct in Go. It is inspired by [gorilla/schema](https://github.com/gorilla/schema) with main focus on query parameters. Built on top of Go stdlib, it uses custom struct tag `qp` to define the query parameter key .
6
+
`qparser` is a simple package that helps parse query parameters into structs in Go. It is inspired by [gorilla/schema](https://github.com/gorilla/schema) with a main focus on query parameters. Built on top of Go stdlib, it uses a custom struct tag `qp` to define the query parameter key.
7
+
8
+
## Table of Contents
9
+
-[Installation](#installation)
10
+
-[Examples](#examples)
11
+
-[Supported field types](#supported-field-types)
12
+
-[Error Handling](#error-handling)
13
+
-[Notes](#notes)
14
+
-[Benchmarks](#benchmarks)
7
15
8
16
## Installation
9
17
```bash
10
18
go get -u github.com/prawirdani/qparser@latest
11
19
```
12
20
13
-
## Example
14
-
Here's an example of how to use `qparser` to parse query parameters into struct.
21
+
## Examples
22
+
23
+
### Parse from `net/http` request.
24
+
Here's an example of how to use `qparser` to parse query parameters into struct from `net/http` request.
@@ -31,6 +41,44 @@ func MyHandler(w http.ResponseWriter, r *http.Request) {
31
41
}
32
42
```
33
43
44
+
### Parse from URL
45
+
You can also parse query parameters from URL string by calling the `ParseURL` function. Here's an example:
46
+
```go
47
+
48
+
funcmain() {
49
+
varpagination Pagination
50
+
51
+
url:="http://example.com/path?page=1&limit=5"
52
+
53
+
err:= qparser.ParseURL(url, &pagination)
54
+
if err != nil {
55
+
// Handle Error
56
+
}
57
+
58
+
// Do something with pagination
59
+
}
60
+
```
61
+
62
+
### Parse from url.Values
63
+
You can also parse query parameters directly from `url.Values` by calling the `Parse` function. Here's an example:
64
+
```go
65
+
66
+
funcmain() {
67
+
varpagination Pagination
68
+
69
+
values:= url.Values{
70
+
"page": []string{"1"},
71
+
"limit": []string{"5"},
72
+
}
73
+
74
+
err:= qparser.Parse(values, &pagination)
75
+
if err != nil {
76
+
// Handle Error
77
+
}
78
+
79
+
// Do something with pagination
80
+
}
81
+
```
34
82
### Multiple Values Query & Nested Struct
35
83
To support multiple values for a single query parameter, use a slice type. For nested structs, utilize the qp tag within the fields of the nested struct to pass the query parameters. It's important to note that the parent struct containing the nested/child struct **should not have its own qp tag**. Here's an example:
36
84
```go
@@ -65,44 +113,6 @@ There are three ways for the parser to handle multiple values query parameters:
65
113
66
114
Simply ensure that the qp tags are defined appropriately in your struct fields to map these parameters correctly.
67
115
68
-
### Parse from URL
69
-
You can also parse query parameters from URL string by calling the `ParseURL` function. Here's an example:
70
-
```go
71
-
72
-
funcmain() {
73
-
varpagination Pagination
74
-
75
-
url:="http://example.com/path?page=1&limit=5"
76
-
77
-
err:= qparser.ParseURL(url, &pagination)
78
-
if err != nil {
79
-
// Handle Error
80
-
}
81
-
82
-
// Do something with pagination
83
-
}
84
-
```
85
-
86
-
## Notes
87
-
- Empty query values are not validated by default. For custom validation (including empty value checks), you can create your own validator by creating a pointer/value receiver method on the struct or with help of a third party validator package like [go-playground/validator](https://github.com/go-playground/validator).
88
-
- Missing query parameters:
89
-
- Primitive type fields keep their zero values (e.g., `0` for int, `""` for string, `false` for bool)
90
-
- Pointer fields are remain **nil** and slice are set to nil slice ([]).
91
-
- A pointer nested struct will remain nil, **only if all the fields are missing**. If any field is present, the struct will be initialized and the missing fields will be set to their zero values.
92
-
- For multiple values query parameters, same value will be appended to the slice. If you want to make sure each value in the slice is unique, you can create a pointer receiver method on the struct to remove the duplicates or sanitize the values.
93
-
- The `qp` tag value is **case-sensitive** and must match the query parameter key exactly.
94
-
95
-
## Supported field types
96
-
- String
97
-
- Boolean
98
-
- Integers (int, int8, int16, int32 and int64)
99
-
- Unsigned Integers (uint, uint8, uint16, uint32 and uint64)
100
-
- Floats (float64 and float32)
101
-
- Slice of above types
102
-
- Nested Struct
103
-
- time.Time
104
-
- A pointer to one of above
105
-
106
116
### Time Handling
107
117
Supports time.Time, *time.Time, and type aliases. Handles a variety of standard time formats, both with and without timezone offsets, and supports nanosecond-level precision. Date formats follow the YYYY-MM-DD layout.
108
118
<divalign="center">
@@ -122,7 +132,8 @@ Supports time.Time, *time.Time, and type aliases. Handles a variety of standard
122
132
| RFC3339 (Z or offset) |`2006-01-02T15:04:05Z07:00`|
123
133
| RFC3339Nano (Z or offset, nanosecond prec) |`2006-01-02T15:04:05.999999999Z07:00`|
124
134
| Space separator + TZ |`2006-01-02 15:04:05-07:00`|
125
-
| Space separator + fractional + TZ |`2006-01-02 15:04:05.123456789 -07:00`|
135
+
| Space separator + TZ (+ offset) |`2006-01-02 15:04:05+07:00`|
136
+
| Space separator + fractional + TZ |`2006-01-02 15:04:05.123456789-07:00`|
126
137
127
138
</div>
128
139
@@ -132,31 +143,125 @@ Supports time.Time, *time.Time, and type aliases. Handles a variety of standard
- Unsigned Integers (uint, uint8, uint16, uint32 and uint64)
168
+
- Floats (float64 and float32)
169
+
- Slice of above types
170
+
- Nested Struct
171
+
- time.Time
172
+
- A pointer to one of above
173
+
174
+
175
+
## Error Handling
176
+
qparser provides detailed error information with field-specific context. Errors are wrapped with field names to help identify exactly which parameter failed to parse.
fmt.Printf("Field error in %q: %v\n", fieldErr.FieldName, fieldErr.Err)
201
+
202
+
// Handle specific error types
203
+
switch {
204
+
case errors.Is(fieldErr.Err, qparser.ErrInvalidValue):
205
+
fmt.Printf("Invalid value format for field %s\n", fieldErr.FieldName)
206
+
case errors.Is(fieldErr.Err, qparser.ErrOutOfRange):
207
+
fmt.Printf("Value out of range for field %s\n", fieldErr.FieldName)
208
+
case errors.Is(fieldErr.Err, qparser.ErrUnsupportedKind):
209
+
fmt.Printf("Unsupported type for field %s\n", fieldErr.FieldName)
210
+
}
211
+
} else {
212
+
fmt.Printf("General error: %v\n", err)
213
+
}
214
+
return
215
+
}
216
+
217
+
fmt.Printf("Parsed filter: %+v\n", filter)
218
+
}
219
+
```
220
+
221
+
### Error Types
222
+
223
+
qparser defines several error types for different parsing scenarios:
224
+
225
+
-**`ErrInvalidValue`**: Value cannot be parsed as the target type (e.g., "abc" as integer)
226
+
-**`ErrOutOfRange`**: Value is too large for the target numeric type (e.g., "999" as int8)
227
+
-**`ErrUnsupportedKind`**: Target type is not supported by the parser
228
+
-**`ErrUnexportedStruct`**: Struct contains unexported fields with `qp` tags
229
+
230
+
### FieldError Structure
231
+
232
+
The `FieldError` type provides both the field name and the underlying error:
233
+
234
+
```go
235
+
typeFieldErrorstruct {
236
+
FieldNamestring// Name of the field that failed
237
+
Errerror// Underlying error
238
+
}
239
+
```
240
+
241
+
This allows you to:
242
+
- Identify exactly which field failed parsing
243
+
- Access the specific error type for targeted error handling
244
+
- Provide user-friendly error messages in your API responses
245
+
246
+
247
+
## Notes
248
+
- Empty query values are not validated by default. For custom validation (including empty value checks), implement your own validation method on the struct or use a third-party validator such as [go-playground/validator](https://github.com/go-playground/validator).
249
+
- Missing query parameters:
250
+
- Primitive fields keep their zero values (0, "", false, etc.).
251
+
- Pointer fields are always initialized, even when the parameter is missing. They will contain the zero value of the underlying type.
252
+
- Slice fields become an empty slice ([]T{}), not nil.
253
+
- Pointer nested structs are also always initialized. Missing fields inside them receive their zero values.
254
+
- For repeated query parameters, the value is appended to the slice every time. If you want deduplication or sanitization, implement a post-processing method on your struct.
255
+
- The qp tag is case-sensitive and must match the query parameter key exactly.
0 commit comments