ializer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/ruby.yml +45 -0
- data/.github/workflows/rubygems-publish.yml +39 -0
- data/.gitignore +12 -0
- data/.mdlrc +1 -0
- data/.rspec +3 -0
- data/.rubocop.yml +21 -0
- data/.ruby-version +1 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +7 -0
- data/README.md +557 -0
- data/Rakefile +8 -0
- data/bin/console +8 -0
- data/bin/setup +8 -0
- data/ializer.gemspec +33 -0
- data/lib/de/ser/ializer.rb +87 -0
- data/lib/ializer/big_decimal_de_ser.rb +17 -0
- data/lib/ializer/boolean_de_ser.rb +18 -0
- data/lib/ializer/config.rb +47 -0
- data/lib/ializer/date_de_ser.rb +19 -0
- data/lib/ializer/default_de_ser.rb +15 -0
- data/lib/ializer/fix_num_de_ser.rb +17 -0
- data/lib/ializer/float_de_ser.rb +31 -0
- data/lib/ializer/millis_de_ser.rb +17 -0
- data/lib/ializer/string_de_ser.rb +15 -0
- data/lib/ializer/symbol_de_ser.rb +15 -0
- data/lib/ializer/time_de_ser.rb +17 -0
- data/lib/ializer/version.rb +5 -0
- data/lib/ializer.rb +42 -0
- data/lib/ser/ializer/field.rb +45 -0
- data/lib/ser/ializer.rb +162 -0
- metadata +200 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b39a084832bc15cc9d722c7269b3200b9e4a6af8c4318ea06390f91acf6a27d5
|
4
|
+
data.tar.gz: '0355388f6e02ac9f8c156706230e172d36b3698d0f22df9f5a90ebf0f121a0b4'
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0b4123ae5e65828108879cf191062ef3f05e980bd1fab81b1b51c262d576ea69664cc2d2a2e75d902049fdf52b33d9c25d26021a90d1c0fda7ae228b65315a25
|
7
|
+
data.tar.gz: ceab7b40078424c4bb23634e059a99ce7fd157bee574e1e7fa6e9202a64d9482a0458acf29d978349ca38f38e71ac58a316acf9e9a3e10a19b48a4ef70998b6a
|
@@ -0,0 +1,45 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on: [push]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
|
9
|
+
strategy:
|
10
|
+
matrix:
|
11
|
+
ruby: [ '2.5.x', '2.6.x' ]
|
12
|
+
|
13
|
+
name: Ruby ${{ matrix.ruby }}
|
14
|
+
steps:
|
15
|
+
- uses: actions/checkout@v1
|
16
|
+
- name: Set up Ruby ${{ matrix.ruby }}
|
17
|
+
uses: actions/setup-ruby@v1
|
18
|
+
with:
|
19
|
+
ruby-version: ${{ matrix.ruby }}
|
20
|
+
|
21
|
+
- name: Install bundler
|
22
|
+
run: |
|
23
|
+
gem install bundler
|
24
|
+
|
25
|
+
- name: Cache bundled gems
|
26
|
+
uses: actions/cache@v1
|
27
|
+
with:
|
28
|
+
path: vendor/bundle
|
29
|
+
key: ${{ runner.os }}-${{ matrix.ruby }}-gem-${{ hashFiles('**/ializer.gemspec') }}
|
30
|
+
restore-keys: |
|
31
|
+
${{ runner.os }}-${{ matrix.ruby }}-${{ hashFiles('**/ializer.gemspec') }}
|
32
|
+
${{ runner.os }}-${{ matrix.ruby }}-
|
33
|
+
|
34
|
+
- name: Bundle
|
35
|
+
run: |
|
36
|
+
bundle config path vendor/bundle
|
37
|
+
bundle install --jobs 4 --retry 3
|
38
|
+
|
39
|
+
- name: RSpec
|
40
|
+
run: |
|
41
|
+
bundle exec rspec
|
42
|
+
|
43
|
+
- name: Rubocop
|
44
|
+
run: |
|
45
|
+
bundle exec rubocop -c .rubocop.yml
|
@@ -0,0 +1,39 @@
|
|
1
|
+
name: RubyGems Publish
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
tags:
|
6
|
+
- 'v[0-9]+.[0-9]+.[0-9]+*'
|
7
|
+
|
8
|
+
jobs:
|
9
|
+
build:
|
10
|
+
runs-on: ubuntu-latest
|
11
|
+
|
12
|
+
steps:
|
13
|
+
- uses: actions/checkout@master
|
14
|
+
|
15
|
+
- name: Set up Ruby 2.6
|
16
|
+
uses: actions/setup-ruby@v1
|
17
|
+
with:
|
18
|
+
version: 2.6.x
|
19
|
+
|
20
|
+
- name: Set Credentials
|
21
|
+
run: |
|
22
|
+
mkdir -p $HOME/.gem
|
23
|
+
touch $HOME/.gem/credentials
|
24
|
+
chmod 0600 $HOME/.gem/credentials
|
25
|
+
printf -- "---\n:github: Bearer ${GITHUB_TOKEN}\n:rubygems: Bearer ${RUBYGEMS_API_KEY}\n" > $HOME/.gem/credentials
|
26
|
+
env:
|
27
|
+
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
28
|
+
RUBYGEMS_API_KEY: ${{secrets.RUBYGEMS_API_KEY}}
|
29
|
+
|
30
|
+
- name: Publish to GitHub Packages
|
31
|
+
run: |
|
32
|
+
export OWNER=$( echo ${{ github.repository }} | cut -d "/" -f 1 )
|
33
|
+
gem build *.gemspec
|
34
|
+
gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem
|
35
|
+
|
36
|
+
- name: Publish to RubyGems
|
37
|
+
run: |
|
38
|
+
gem build *.gemspec
|
39
|
+
gem push --KEY rubygems *.gem
|
data/.gitignore
ADDED
data/.mdlrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rules "~MD013"
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require: rubocop-rspec
|
2
|
+
|
3
|
+
AllCops:
|
4
|
+
TargetRubyVersion:
|
5
|
+
2.6
|
6
|
+
|
7
|
+
Layout/LineLength:
|
8
|
+
Max: 120
|
9
|
+
|
10
|
+
Metrics/BlockLength:
|
11
|
+
Exclude:
|
12
|
+
- 'spec/**/*.rb'
|
13
|
+
|
14
|
+
Style/Documentation:
|
15
|
+
Enabled: false
|
16
|
+
|
17
|
+
RSpec/MultipleExpectations:
|
18
|
+
Enabled: false
|
19
|
+
|
20
|
+
RSpec/ExampleLength:
|
21
|
+
Enabled: false
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.5.1
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright 2019 Jeremy Steinberg
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,557 @@
|
|
1
|
+
[![Build](https://github.com/jsteinberg/ializer/workflows/Ruby/badge.svg)](https://github.com/jsteinberg/ializer/actions)
|
2
|
+
|
3
|
+
# {De | Ser} Ializer
|
4
|
+
|
5
|
+
A fast serializer/deserializer for Ruby Objects.
|
6
|
+
|
7
|
+
## Table of Contents
|
8
|
+
|
9
|
+
* [Design Goals](#design-goals)
|
10
|
+
* [Installation](#installation)
|
11
|
+
* [Usage](#usage)
|
12
|
+
* [Configuration](#configuration)
|
13
|
+
* [Model Definitions](#model-definitions)
|
14
|
+
* [Serializer Definitions](#serializer-definitions)
|
15
|
+
* [DeSerializer Definitions](#deserializer-definitions)
|
16
|
+
* [Object Serialization](#object-serialization)
|
17
|
+
* [Object Deserialization](#object-deserialization)
|
18
|
+
* [Attributes](#attributes)
|
19
|
+
* [Nested Attributes](#nested-attributes)
|
20
|
+
* [Attribute Types](#attribute-types)
|
21
|
+
* [Serialization Context](#serialization-context)
|
22
|
+
* [Conditional Attributes](#conditional-attributes)
|
23
|
+
* [Attribute Sharing](#attribute-sharing)
|
24
|
+
* [Key Transforms](#key-transforms)
|
25
|
+
* [Thread Safety](#thread-safety)
|
26
|
+
* [Performance Comparison](#performance-comparison)
|
27
|
+
* [Contributing](#contributing)
|
28
|
+
|
29
|
+
## Design Goals
|
30
|
+
|
31
|
+
* Simple Singular DSL for defining object-to-data and data-to-object mappings
|
32
|
+
* Support for nested object relationships
|
33
|
+
* Isolate serialization/parsing code from object to not pollute method space
|
34
|
+
* speed
|
35
|
+
|
36
|
+
## Installation
|
37
|
+
|
38
|
+
Add this line to your application's Gemfile:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
gem 'ializer'
|
42
|
+
```
|
43
|
+
|
44
|
+
Execute:
|
45
|
+
|
46
|
+
```bash
|
47
|
+
bundle install
|
48
|
+
```
|
49
|
+
|
50
|
+
Require:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
require 'ializer'
|
54
|
+
```
|
55
|
+
|
56
|
+
## Usage
|
57
|
+
|
58
|
+
### Configuration
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
Ializer.setup do |config|
|
62
|
+
config.key_transform = :dasherize # change serailized key names
|
63
|
+
# or
|
64
|
+
config.key_transformer = ->(key) {
|
65
|
+
key.lowercase.undsercore + '1'
|
66
|
+
}
|
67
|
+
config.warn_on_default = true # outputs a warning to STDOUT(puts) if DefaultDeSer is used
|
68
|
+
config.raise_on_default = false # raises an exception if the DefaultDeSer is used
|
69
|
+
end
|
70
|
+
|
71
|
+
```
|
72
|
+
|
73
|
+
For more information, see [Key Transforms](#key-transforms) and [Attribute Types](#attribute-types) sections.
|
74
|
+
|
75
|
+
### Model Definitions
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
class Order
|
79
|
+
attr_accessor :id, :created_at, :items, :customer
|
80
|
+
|
81
|
+
def initialize(attr = {})
|
82
|
+
@id = attr[:id]
|
83
|
+
@created_at = attr[:created_at]
|
84
|
+
@items = attr[:items] || []
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class OrderItem
|
89
|
+
attr_accessor :name, :price, :in_stock
|
90
|
+
|
91
|
+
def initialize(attr = {})
|
92
|
+
@name = attr[:name]
|
93
|
+
@price = attr[:price]
|
94
|
+
@in_stock = attr[:in_stock]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class Customer
|
99
|
+
attr_accessor :name, :email
|
100
|
+
|
101
|
+
def initialize(attr = {})
|
102
|
+
@name = attr[:name]
|
103
|
+
@email = attr[:email]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
### Serializer Definition
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
class OrderDeSer < Ser::Ializer
|
112
|
+
integer :id
|
113
|
+
timestamp :created_at
|
114
|
+
|
115
|
+
nested :items, deser: OrderItemDeSer
|
116
|
+
nested :customer, deser: CustomerDeSer
|
117
|
+
end
|
118
|
+
|
119
|
+
class OrderItemDeSer < Ser::Ializer
|
120
|
+
string :name
|
121
|
+
decimal :price
|
122
|
+
boolean :in_stock
|
123
|
+
end
|
124
|
+
|
125
|
+
class CustomerDeSer < Ser::Ializer
|
126
|
+
string :name
|
127
|
+
string :email
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
### DeSerializer Definition
|
132
|
+
|
133
|
+
`De::Ser::Ializers` can deserialize from JSON and serialize to JSON. If you only need serialization capabilities, you can inherit from, `Ser::Italizer` instead.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
class OrderDeSer < De::Ser::Ializer
|
137
|
+
integer :id
|
138
|
+
timestamp :created_at
|
139
|
+
|
140
|
+
nested :items, deser: OrderItemDeSer, model_class: OrderItem
|
141
|
+
nested :customer, deser: CustomerDeSer, model_class: Customer
|
142
|
+
end
|
143
|
+
|
144
|
+
class OrderItemDeSer < De::Ser::Ializer
|
145
|
+
string :name
|
146
|
+
decimal :price
|
147
|
+
boolean :in_stock
|
148
|
+
end
|
149
|
+
|
150
|
+
class CustomerDeSer < De::Ser::Ializer
|
151
|
+
string :name
|
152
|
+
string :email
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
### Sample Object
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
order = Order.new(id: 4, created_at: Time.now, items: [])
|
160
|
+
order.items << OrderItem.new(name: 'Baseball', price: BigDecimal('4.99'), in_stock: true)
|
161
|
+
order.items << OrderItem.new(name: 'Football', price: BigDecimal('14.99'), in_stock: false)
|
162
|
+
order.customer = Customer.new(name: 'Bowser', email: 'bowser@example.net')
|
163
|
+
```
|
164
|
+
|
165
|
+
### Object Serialization
|
166
|
+
|
167
|
+
#### Return a hash
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
data = OrderDeSer.serialize(order)
|
171
|
+
```
|
172
|
+
|
173
|
+
#### Return Serialized JSON
|
174
|
+
|
175
|
+
Ializer relies on the [`MultiJson`](https://github.com/intridea/multi_json) gem for json serialization/parsing
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
json_string = OrderDeser.serialize_json(order)
|
179
|
+
```
|
180
|
+
|
181
|
+
#### Serialized Output
|
182
|
+
|
183
|
+
```json
|
184
|
+
{
|
185
|
+
"id": 4,
|
186
|
+
"created-at": "2019-12-01T00:00:00.000-06:00",
|
187
|
+
"items": [
|
188
|
+
{
|
189
|
+
"name": "Baseball",
|
190
|
+
"decimal": "4.99",
|
191
|
+
"in-stock": true
|
192
|
+
},
|
193
|
+
{
|
194
|
+
"name": "Football",
|
195
|
+
"decimal": "14.99",
|
196
|
+
"in-stock": false
|
197
|
+
}
|
198
|
+
],
|
199
|
+
"customer": {
|
200
|
+
"name": "Bowser",
|
201
|
+
"email": "bowser@example.net"
|
202
|
+
}
|
203
|
+
}
|
204
|
+
```
|
205
|
+
|
206
|
+
#### Serialize a collection
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
data = OrderDeSer.serialize([order, order2])
|
210
|
+
```
|
211
|
+
|
212
|
+
#### Serialized Collection Output
|
213
|
+
|
214
|
+
```json
|
215
|
+
[
|
216
|
+
{
|
217
|
+
"id": 3,
|
218
|
+
...
|
219
|
+
},
|
220
|
+
{
|
221
|
+
"id": 4,
|
222
|
+
...
|
223
|
+
}
|
224
|
+
]
|
225
|
+
```
|
226
|
+
|
227
|
+
### Object Deserialization
|
228
|
+
|
229
|
+
**Note:** Objects that are parsed must have a zero-argument initializer (ie: Object.new)
|
230
|
+
|
231
|
+
#### Parsing a hash
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
model = OrderDeSer.parse(data, Order)
|
235
|
+
|
236
|
+
=> #<Order:0x00007f9e44aabd80 @id=4, @created_at=Sun, 01 Dec 2019 00:00:00 -0600, @items=[#<OrderItem:0x00007f9e44aab6f0 @name="Baseball", @in_stock=true, @price=0.499e1>, #<OrderItem:0x00007f9e44aab628 @name="Football", @in_stock=false, @price=0.1499e2>], @customer=#<Customer:0x00007f9e44aab498 @name="Bowser", @email="bowser@example.net">>
|
237
|
+
```
|
238
|
+
|
239
|
+
## Attributes
|
240
|
+
|
241
|
+
Attributes are defined in `ializer` using the `property` method.
|
242
|
+
|
243
|
+
By default, attributes are read directly from the model property of the same name. In this example, `name` is expected to be a property of the object being serialized:
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
class Customer < De::Ser::Ializer
|
247
|
+
property :id, type: :integer
|
248
|
+
property :name, type: String
|
249
|
+
end
|
250
|
+
```
|
251
|
+
|
252
|
+
Custom typed methods also exist to provide a cleaner DSL.
|
253
|
+
|
254
|
+
```ruby
|
255
|
+
class Customer < De::Ser::Ializer
|
256
|
+
integer :id
|
257
|
+
string :name
|
258
|
+
end
|
259
|
+
```
|
260
|
+
|
261
|
+
### Nested Attributes
|
262
|
+
|
263
|
+
`ializer` was built for serialization and parsing of nested objects. You can create a nested object via the `property` method or a specialized `nested` method.
|
264
|
+
|
265
|
+
The `nested` method allows you to define a deser inline.
|
266
|
+
|
267
|
+
```ruby
|
268
|
+
class OrderDeSer < De::Ser::Ializer
|
269
|
+
integer :id
|
270
|
+
timestamp :created_at
|
271
|
+
|
272
|
+
nested :items, deser: OrderItemDeSer, model_class: OrderItem
|
273
|
+
# OR
|
274
|
+
property :items, deser: OrderItemDeSer, model_classx: OrderItem
|
275
|
+
|
276
|
+
nested :customer, model_class: Customer do
|
277
|
+
string :name
|
278
|
+
string :email
|
279
|
+
end
|
280
|
+
end
|
281
|
+
```
|
282
|
+
|
283
|
+
The `property` method **DOES NOT** allow you to define a deser inline, but instead allows you to override the value of the field.
|
284
|
+
|
285
|
+
```ruby
|
286
|
+
class OrderDeSer < De::Ser::Ializer
|
287
|
+
integer :id
|
288
|
+
property :items, deser: OrderItemDeSer, model_class: OrderItem do |object, _context|
|
289
|
+
object.items.select(&:should_display?)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
```
|
293
|
+
|
294
|
+
### Attribute Types
|
295
|
+
|
296
|
+
The following types are included with `ializer`
|
297
|
+
|
298
|
+
| Type | method alias | mappings |
|
299
|
+
|------------|--------------|----------------------------|
|
300
|
+
| BigDecimal | `decimal()` | :BigDecimal, :decimal |
|
301
|
+
| Boolean | `boolean()` | :Boolean, :boolean |
|
302
|
+
| Date | `date()` | Date, :date |
|
303
|
+
| Integer | `integer()` | Integer, :integer |
|
304
|
+
| Float | `float()` | Float, :float |
|
305
|
+
| Time | `millis()` | :Millis, :millis |
|
306
|
+
| String | `string()` | String, :millis |
|
307
|
+
| Symbol | `symbol()` | Symbol, :symbol, :sym |
|
308
|
+
| Time | `timestamp()`| Time, DateTime, :timestamp |
|
309
|
+
| Default | `default()` | :default |
|
310
|
+
|
311
|
+
**Note: Default just uses the current value of the field and will only properly deserialize if it is a standard json value type(number, string, boolean).**
|
312
|
+
|
313
|
+
#### Default Attribute Configuration Options
|
314
|
+
|
315
|
+
There are a few settings for dealing with the `DefaultDeSer`.
|
316
|
+
|
317
|
+
```ruby
|
318
|
+
Ializer.setup do |config|
|
319
|
+
config.warn_on_default = true # outputs a warning to STDOUT(puts) if DefaultDeSer is used
|
320
|
+
config.raise_on_default = false # raises an exception if the DefaultDeSer is used
|
321
|
+
end
|
322
|
+
```
|
323
|
+
|
324
|
+
Since `De::Ser::Ializer`s are configured on load, raising an exception should halt the application from starting(instead of silently failing later). By default a warning is logged to `STDOUT`.
|
325
|
+
|
326
|
+
#### Registering Custom Attribute types
|
327
|
+
|
328
|
+
You can register your own types or override the provided types. A custom attribute type DeSer must implement the following methods. When registering, you must register with the base `Ser::Ializer` class.
|
329
|
+
|
330
|
+
```ruby
|
331
|
+
Ser::Ializer.register(method_name, deser, *mappings)
|
332
|
+
```
|
333
|
+
|
334
|
+
```ruby
|
335
|
+
class CustomDeSer
|
336
|
+
def self.serialize(value, _context = nil)
|
337
|
+
"#{value}_custom"
|
338
|
+
end
|
339
|
+
|
340
|
+
def self.parse(value)
|
341
|
+
value.split("_")[0]
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
Ser::Ializer.register(:custom, CustomDeSer, :custom)
|
346
|
+
```
|
347
|
+
|
348
|
+
Then you can use them as follows:
|
349
|
+
|
350
|
+
```ruby
|
351
|
+
class Customer < De::Ser::Ializer
|
352
|
+
integer :id
|
353
|
+
string :name
|
354
|
+
custom :custom_prop
|
355
|
+
# or
|
356
|
+
property :custom_prop, type: :custom
|
357
|
+
end
|
358
|
+
```
|
359
|
+
|
360
|
+
To override the provided type desers, you do the following:
|
361
|
+
|
362
|
+
```ruby
|
363
|
+
class MyTimeDeSer
|
364
|
+
def self.serialize(value, _context = nil)
|
365
|
+
value.to_my_favorite_time_serialization_format
|
366
|
+
end
|
367
|
+
|
368
|
+
def self.parse(value)
|
369
|
+
Time.parse_my_favorite_time_serialization_format(value)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
Ser::Ializer.register('timestamp', MyTimeDeSer, Time, DateTime, :timestamp)
|
374
|
+
```
|
375
|
+
|
376
|
+
Custom attributes that must be serialized but do not exist on the model can be declared using Ruby block syntax:
|
377
|
+
|
378
|
+
```ruby
|
379
|
+
class Customer < De::Ser::Ializer
|
380
|
+
string :full_name do |object, _context|
|
381
|
+
"#{object.first_name} (#{object.last_name})"
|
382
|
+
end
|
383
|
+
end
|
384
|
+
```
|
385
|
+
|
386
|
+
The block syntax can also be used to override the property on the object:
|
387
|
+
|
388
|
+
```ruby
|
389
|
+
class Customer < De::Ser::Ializer
|
390
|
+
string :name do |object, _context|
|
391
|
+
"#{object.name} Part 2"
|
392
|
+
end
|
393
|
+
end
|
394
|
+
```
|
395
|
+
|
396
|
+
You can also override the property on an object with a specially named method:
|
397
|
+
|
398
|
+
```ruby
|
399
|
+
class Customer < De::Ser::Ializer
|
400
|
+
string :name
|
401
|
+
|
402
|
+
def self.name(object, _context) # overrides :name attribute
|
403
|
+
"#{object.name} Part 2"
|
404
|
+
end
|
405
|
+
end
|
406
|
+
```
|
407
|
+
|
408
|
+
Attributes can also use a different name by passing the original method or accessor with a proc shortcut:
|
409
|
+
|
410
|
+
```ruby
|
411
|
+
class Customer < De::Ser::Ializer
|
412
|
+
string :name, key: 'customer-name' # Note: an explicitly set key will not be transformed by the configured key_transformer
|
413
|
+
end
|
414
|
+
```
|
415
|
+
|
416
|
+
### Serialization Context
|
417
|
+
|
418
|
+
In some cases a `Ser::Ializer` might require more information than what is available on the record. A context object can be passed to serialization and used however necessary.
|
419
|
+
|
420
|
+
```ruby
|
421
|
+
class CustomerSerializer < Ser::Ializer
|
422
|
+
integer :id
|
423
|
+
string :name
|
424
|
+
|
425
|
+
string :phone_number do |object, context|
|
426
|
+
if context.admin?
|
427
|
+
object.phone_number
|
428
|
+
else
|
429
|
+
object.phone_number.last(4)
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
# ...
|
435
|
+
|
436
|
+
CustomerDeSer.serialize(order, current_user)
|
437
|
+
```
|
438
|
+
|
439
|
+
### Conditional Attributes
|
440
|
+
|
441
|
+
Conditional attributes can be defined by passing a Proc to the `if` key on the `property` method. Return `truthy` if the attribute should be serialized, and `falsey` if not. The record and any params passed to the serializer are available inside the Proc as the first and second parameters, respectively.
|
442
|
+
|
443
|
+
```ruby
|
444
|
+
class CustomerSerializer < Ser::Ializer
|
445
|
+
integer :id
|
446
|
+
string :name
|
447
|
+
|
448
|
+
string :phone_number if: ->(object, context) { context.admin? }
|
449
|
+
end
|
450
|
+
|
451
|
+
# ...
|
452
|
+
CustomerDeSer.serialize(order, current_user)
|
453
|
+
```
|
454
|
+
|
455
|
+
**Note: instead of a Proc, any object that responds to call with arity 2 can be passed to `:if`.**
|
456
|
+
|
457
|
+
```ruby
|
458
|
+
class AdminChecker
|
459
|
+
def self.admin?(_object, context)
|
460
|
+
context.admin?
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
class CustomerSerializer < Ser::Ializer
|
465
|
+
integer :id
|
466
|
+
string :name
|
467
|
+
|
468
|
+
string :phone_number if: AdminChecker.method(:admin?)
|
469
|
+
end
|
470
|
+
|
471
|
+
# ...
|
472
|
+
CustomerDeSer.serialize(order, current_user)
|
473
|
+
```
|
474
|
+
|
475
|
+
### Attribute Sharing
|
476
|
+
|
477
|
+
There are a couple of ways to share attributes from different desers.
|
478
|
+
|
479
|
+
#### Inheritance
|
480
|
+
|
481
|
+
```ruby
|
482
|
+
class SimpleUserDeSer < De::Ser::Ializer
|
483
|
+
integer :id
|
484
|
+
string :username
|
485
|
+
end
|
486
|
+
|
487
|
+
class UserDeSer < SimpleUserDeSer
|
488
|
+
string :email
|
489
|
+
end
|
490
|
+
```
|
491
|
+
|
492
|
+
#### Composition
|
493
|
+
|
494
|
+
```ruby
|
495
|
+
class BaseApiDeSer < De::Ser::Ializer
|
496
|
+
integer :id
|
497
|
+
timestamp :created_at
|
498
|
+
end
|
499
|
+
|
500
|
+
class UserDeSer < De::Ser::Ializer
|
501
|
+
with BaseApiDeSer
|
502
|
+
string :email
|
503
|
+
end
|
504
|
+
```
|
505
|
+
|
506
|
+
**Note:** Because of scoping, including a deser using `with` will not include any method overrides
|
507
|
+
|
508
|
+
```ruby
|
509
|
+
class BaseApiDeSer < De::Ser::Ializer
|
510
|
+
integer :id
|
511
|
+
timestamp :created_at
|
512
|
+
|
513
|
+
def self.created_at(object, context)
|
514
|
+
# NOT INCLUDED IN UserDeSer. UserDeSer will call `.created_at` on any serialization target
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
class UserDeSer < De::Ser::Ializer
|
519
|
+
with BaseApiDeSer
|
520
|
+
string :email
|
521
|
+
end
|
522
|
+
```
|
523
|
+
|
524
|
+
For more examples check the [`spec/support/deser`](https://github.com/jsteinberg/ializer/tree/master/spec/support/deser) folder.
|
525
|
+
|
526
|
+
### Key Transforms
|
527
|
+
|
528
|
+
By default `ializer` uses object field names as the key name. You can override this setting by either specifying a string method for transforms or providing a proc for manual transformation.
|
529
|
+
|
530
|
+
**Note:** `key_transformer` will override any value set as the `key_transform`
|
531
|
+
|
532
|
+
```ruby
|
533
|
+
Ializer.setup do |config|
|
534
|
+
config.key_transform = :dasherize
|
535
|
+
end
|
536
|
+
|
537
|
+
# or
|
538
|
+
|
539
|
+
Ializer.setup do |config|
|
540
|
+
config.key_transformer = ->(key) {
|
541
|
+
key.lowercase.undsercore + '1'
|
542
|
+
}
|
543
|
+
end
|
544
|
+
|
545
|
+
```
|
546
|
+
|
547
|
+
### Thread Safety
|
548
|
+
|
549
|
+
Defining of desers is not thread safe. As long as defitions are preloaded then thread safety is not a concern. **Note: because of this you should not create desers at runtime**
|
550
|
+
|
551
|
+
## Performance Comparison
|
552
|
+
|
553
|
+
TODO
|
554
|
+
|
555
|
+
## Contributing
|
556
|
+
|
557
|
+
TODO
|