generated from athena-framework/component-template
/
athena-serializer.cr
186 lines (172 loc) · 6.04 KB
/
athena-serializer.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
require "semantic_version"
require "uuid"
require "json"
require "yaml"
require "athena-config"
require "./annotations"
require "./any"
require "./context"
require "./serializable"
require "./serializer_interface"
require "./serializer"
require "./property_metadata"
require "./deserialization_context"
require "./serialization_context"
require "./construction/*"
require "./exceptions/*"
require "./exclusion_strategies/*"
require "./navigators/*"
require "./visitors/*"
# Convenience alias to make referencing `Athena::Serializer` types easier.
alias ASR = Athena::Serializer
# Convenience alias to make referencing `Athena::Serializer::Annotations` types easier.
alias ASRA = Athena::Serializer::Annotations
# :nodoc:
module JSON; end
# :nodoc:
module YAML; end
# Athena's Serializer component, `ASR` for short, adds enhanced (de)serialization features to your project.
#
# ## Getting Started
#
# The serializer component utilizes a module to specify that a type is serializable, as well as annotations to control how it gets (de)serialized.
#
# ### Installation
#
# Add the dependency to your `shard.yml`:
#
# ```yaml
# dependencies:
# athena-serializer:
# github: athena-framework/serializer
# version: ~> 0.2.0
# ```
#
# Run `shards install`.
#
# ### Usage
#
# See the `ASR::Annotations` namespace a complete list of annotations, as well as each annotation for more detailed information.
#
# ```
# # ExclusionPolicy specifies that all properties should not be (de)serialized
# # unless exposed via the `ASRA::Expose` annotation.
# @[ASRA::ExclusionPolicy(:all)]
# @[ASRA::AccessorOrder(:alphabetical)]
# class Example
# include ASR::Serializable
#
# # Groups can be used to create different "views" of a type.
# @[ASRA::Expose]
# @[ASRA::Groups("details")]
# property name : String
#
# # The `ASRA::Name` controls the name that this property
# # should be deserialized from or be serialized to.
# # It can also be used to set the default serialized naming strategy on the type.
# @[ASRA::Expose]
# @[ASRA::Name(deserialize: "a_prop", serialize: "a_prop")]
# property some_prop : String
#
# # Define a custom accessor used to get the value for serialization.
# @[ASRA::Expose]
# @[ASRA::Groups("default", "details")]
# @[ASRA::Accessor(getter: get_title)]
# property title : String
#
# # ReadOnly properties cannot be set on deserialization
# @[ASRA::Expose]
# @[ASRA::ReadOnly]
# property created_at : Time = Time.utc
#
# # Allows the property to be set via deserialization,
# # but not exposed when serialized.
# @[ASRA::IgnoreOnSerialize]
# property password : String?
#
# # Because of the `:all` exclusion policy, and not having the `ASRA::Expose` annotation,
# # these properties are not exposed.
# getter first_name : String?
# getter last_name : String?
#
# # Runs directly after `self` is deserialized
# @[ASRA::PostDeserialize]
# def split_name : Nil
# @first_name, @last_name = @name.split(' ')
# end
#
# # Allows using the return value of a method as a key/value in the serialized output.
# @[ASRA::VirtualProperty]
# def get_val : String
# "VAL"
# end
#
# private def get_title : String
# @title.downcase
# end
# end
#
# obj = ASR.serializer.deserialize Example, %({"name":"FIRST LAST","a_prop":"STR","title":"TITLE","password":"monkey123","created_at":"2020-10-10T12:34:56Z"}), :json
# obj # => #<Example:0x7f3e3b106740 @created_at=2020-07-05 23:06:58.943298289 UTC, @name="FIRST LAST", @some_prop="STR", @title="TITLE", @password="monkey123", @first_name="FIRST", @last_name="LAST">
# ASR.serializer.serialize obj, :json # => {"a_prop":"STR","created_at":"2020-07-05T23:06:58.94Z","get_val":"VAL","name":"FIRST LAST","title":"title"}
# ASR.serializer.serialize obj, :json, ASR::SerializationContext.new.groups = ["details"] # => {"name":"FIRST LAST","title":"title"}
# ```
module Athena::Serializer
# Returns an `ASR::SerializerInterface` instance for ad-hoc (de)serialization.
#
# The serializer is cached and only instantiated once.
class_getter serializer : ASR::SerializerInterface { ASR::Serializer.new }
# The built-in supported formats.
enum Format
JSON
YAML
# Returns the `ASR::Visitors::SerializationVisitorInterface` related to `self`.
def serialization_visitor
case self
in .json? then ASR::Visitors::JSONSerializationVisitor
in .yaml? then ASR::Visitors::YAMLSerializationVisitor
end
end
# Returns the `ASR::Visitors::DeserializationVisitorInterface` related to `self`.
def deserialization_visitor
case self
in .json? then ASR::Visitors::JSONDeserializationVisitor
in .yaml? then ASR::Visitors::YAMLDeserializationVisitor
end
end
end
# Exclusion Strategies allow controlling which properties should be (de)serialized.
#
# `Athena::Serializer` includes two common strategies: `ASR::ExclusionStrategies::Groups`, and `ASR::ExclusionStrategies::Version`.
#
# Custom strategies can be implemented by via `ExclusionStrategies::ExclusionStrategyInterface`.
#
# !!!todo
# Once feasible, support compile time exclusion strategies.
module Athena::Serializer::ExclusionStrategies; end
# Used to denote a type that is (de)serializable.
#
# This module can be used to make the compiler happy in some situations, it doesn't do anything on its own.
# You most likely want to use `ASR::Serializable` instead.
#
# ```
# require "athena-serializer"
#
# abstract struct BaseModel
# # `ASR::Model` is needed here to ensure typings are correct for the deserialization process.
# # Child types should still include `ASR::Serializable`.
# include ASR::Model
# end
#
# record ModelOne < BaseModel, id : Int32, name : String do
# include ASR::Serializable
# end
#
# record ModelTwo < BaseModel, id : Int32, name : String do
# include ASR::Serializable
# end
#
# record Unionable, type : BaseModel.class
# ```
module Athena::Serializer::Model; end
end