@@ -106,3 +106,167 @@ JSON.lower(p::Point2D) = [p.x, p.y]
106
106
107
107
Define a custom serialization rule for a particular data type. Must return a
108
108
value that can be directly serialized; see help for more details.
109
+
110
+ ### Customizing JSON
111
+
112
+ Users may find the default behaviour of JSON inappropriate for their use case. In
113
+ such cases, JSON provides two mechanisms for users to customize serialization. The
114
+ first method, ` JSON.Writer.StructuralContext ` , is used to customize the cosmetic
115
+ properties of the serialized JSON. (For example, the default pretty printing vs.
116
+ compact printing is supported by provided two different ` StructuralContext ` s.)
117
+ Examples of applications for which ` StructuralContext ` is appropriate include:
118
+ particular formatting demands for JSON (maybe not in compliance with the JSON
119
+ standard) or JSON-like formats with different syntax.
120
+
121
+ The second method, ` JSON.Serializations.Serialization ` , is used to control the
122
+ translation of Julia objects into JSON serialization instructions. In most cases,
123
+ writing a method for ` JSON.lower ` (as mentioned above) is sufficient to define
124
+ JSON serializations for user-defined objects. However, this is not appropriate for
125
+ overriding or deleting predefined serializations (since that would globally affect
126
+ users of the ` JSON ` module and is an instance of dangerous
127
+ [ type piracy] ( https://docs.julialang.org/en/v1/manual/style-guide/index.html#Avoid-type-piracy-1 ) ).
128
+ For these use-cases, users should define a custom instance of ` Serialization ` .
129
+ An example of an application for this use case includes: a commonly requested
130
+ extension to JSON which serializes float NaN and infinite values as ` NaN ` or ` Inf ` ,
131
+ in contravention of the JSON standard.
132
+
133
+ Both methods are controlled by the ` JSON.show_json ` function, which has the following
134
+ signature:
135
+
136
+ ```
137
+ JSON.show_json(io::StructuralContext, serialization::Serialization, object)
138
+ ```
139
+
140
+ which is expected to write to ` io ` in a way appropriate based on the rules of
141
+ ` Serialization ` , but here ` io ` is usually (but not required to be) handled in a
142
+ higher-level manner than a raw ` IO ` object would ordinarily be.
143
+
144
+ #### StructuralContext
145
+
146
+ To define a new ` StructuralContext ` , the following boilerplate is recommended:
147
+
148
+ ``` julia
149
+ import JSON. Writer. StructuralContext
150
+ [mutable] struct MyContext <: StructuralContext
151
+ io:: IO
152
+ [ ... additional state / settings for context goes here ... ]
153
+ end
154
+ ```
155
+
156
+ If your structural context is going to be very similar to the existing JSON
157
+ contexts, it is also possible to instead subtype the abstract subtype
158
+ ` JSONContext ` of ` StructuralContext ` . If this is the case, an ` io::IO ` field (as
159
+ above) is preferred, although the default implementation will only use this
160
+ for ` write ` , so replacing that method is enough to avoid this requirement.
161
+ The default implementations will also use an internal
162
+
163
+ The following methods should be defined for your context, regardless of whether it
164
+ subtypes ` JSONContext ` or ` StructuralContext ` directly. If some of these methods
165
+ are omitted, then ` CommonSerialization ` cannot be generally used with this context.
166
+
167
+ ```
168
+ # called when the next object in a vector or next pair of a dict is to be written
169
+ # (requiring a newline and indent for some contexts)
170
+ # can do nothing if the context need not support indenting
171
+ JSON.Writer.indent(io::MyContext)
172
+
173
+ # called for vectors/dicts to separate items, usually writes ","
174
+ # unless this is the first element in a JSON array
175
+ # (default implementation for JSONContext exists, but requires a mutable bool
176
+ # `first` field, and this is an implementation detail not to be relied on;
177
+ # to define own or delegate explicitly)
178
+ JSON.Writer.delimit(io::MyContext)
179
+
180
+ # called for dicts to separate key and value, usually writes ": "
181
+ JSON.Writer.separate(io::MyContext)
182
+
183
+ # called to indicate start and end of a vector
184
+ JSON.Writer.begin_array(io::MyContext)
185
+ JSON.Writer.end_array(io::MyContext)
186
+
187
+ # called to indicate start and end of a dict
188
+ JSON.Writer.begin_object(io::MyContext)
189
+ JSON.Writer.end_object(io::MyContext)
190
+ ```
191
+
192
+ For the following methods, ` JSONContext ` provides a default implementation,
193
+ but it can be specialized. For ` StructuralContext ` s which are not
194
+ ` JSONContext ` s, the ` JSONContext ` defaults are not appropriate and so are
195
+ not available.
196
+
197
+ ``` julia
198
+ # directly write a specific byte (if supported)
199
+ # default implementation writes to underlying `.io` field
200
+ # note that this enables JSONContext to act as any `io::IO`,
201
+ # i.e. one can use `print`, `show`, etc.
202
+ Base. write (io:: MyContext , byte:: UInt8 )
203
+
204
+ # write "null"
205
+ # default implementation writes to underlying `.io` field
206
+ JSON. Writer. show_null (io:: MyContext )
207
+
208
+ # write an object or string in a manner safe for JSON string
209
+ # default implementation calls `print` but escapes each byte as appropriate
210
+ # and adds double quotes around the content of `elt`
211
+ JSON. Writer. show_string (io:: MyContext , elt)
212
+
213
+ # write a new element of JSON array
214
+ # default implementation calls delimit, then indent, then show_json
215
+ JSON. Writer. show_element (io:: MyContext , elt)
216
+
217
+ # write a key for a JSON object
218
+ # default implementation calls delimit, then indent, then show_string,
219
+ # then seperate
220
+ JSON. Writer. show_key (io:: MyContext , elt)
221
+
222
+ # write a key-value pair for a JSON object
223
+ # default implementation calls show key, then show_json
224
+ JSON. Writer. show_pair (io:: MyContext , s:: Serialization , key, value)
225
+ ```
226
+
227
+ What follows is an example of a ` JSONContext ` subtype which is very similar
228
+ to the default context, but which uses ` None ` instead of ` null ` for JSON nulls,
229
+ which is then generally compatible with Python object literal notation (PYON). It
230
+ wraps a default ` JSONContext ` to delegate all the required methods to. Since
231
+ the wrapped context already has a ` .io ` , this object does not need to include
232
+ an ` .io ` field, and so the ` write ` method must also be delegated, since the default
233
+ is not appropriate. The only other specialization needed is ` show_null ` .
234
+
235
+ ``` julia
236
+ import JSON. Writer
237
+ import JSON. Writer. JSONContext
238
+ mutable struct PYONContext <: JSONContext
239
+ underlying:: JSONContext
240
+ end
241
+
242
+ for delegate in [:indent ,
243
+ :delimit ,
244
+ :separate ,
245
+ :begin_array ,
246
+ :end_array ,
247
+ :begin_object ,
248
+ :end_object ]
249
+ @eval JSON. Writer.$ delegate (io:: PYONContext ) = JSON. Writer.$ delegate (io. underlying)
250
+ end
251
+ Base. write (io:: PYONContext , byte:: UInt8 ) = write (io. underlying, byte)
252
+
253
+ JSON. Writer. show_null (io:: PYONContext ) = print (io, " None" )
254
+ pyonprint (io:: IO , obj) = let io = PYONContext (JSON. Writer. PrettyContext (io, 4 ))
255
+ JSON. print (io, obj)
256
+ return
257
+ end
258
+ ```
259
+
260
+ The usage of this ` pyonprint ` function is as any other ` print ` function, e.g.
261
+
262
+ ``` julia
263
+ julia> pyonprint (stdout , [1 , 2 , nothing ])
264
+ [
265
+ 1 ,
266
+ 2 ,
267
+ None
268
+ ]
269
+
270
+ julia> sprint (pyonprint, missing )
271
+ " None"
272
+ ```
0 commit comments