Struct Mapping with JSON3
The JSON3 package for Julia offers to parse JSON directly into structs.
In this post I demonstrate this with a JWT response.
I receive a response with a body like the following
{
"access_token": "<access token>",
"token_type": "bearer",
"expires_in": 1199,
"refresh_token": "<refresh token>",
".issued": "2021-12-04T07:42:15.6293508Z",
".expires": "2021-12-05T07:42:15.6293508Z"
}
Note that the resolution of the timestamps is finer than milliseconds.
The standard library Dates package handles milliseconds, but not micro- or nanoseconds.
For starters, I use the TimesDates package to circumvent this.
I would like to map this to a struct with similar fields:
struct JSONWebToken
AccessToken::String
TokenType::String
ExpiresIn::Int64
RefreshToken::String
Issued::TimesDates.TimeDateZone
Expires::TimesDates.TimeDateZone
end
The StructTypes package enables the desired parsing.
First we declare that JSONWebToken
should be used for parsing:
StructTypes.StructType(::Type{JSONWebToken}) = StructTypes.Struct()
Then we specify a mapping between fields in the JSON string and the struct:
StructTypes.names(::Type{JSONWebToken}) = (
(:AccessToken, :access_token),
(:TokenType, :token_type),
(:ExpiresIn, :expires_in),
(:RefreshToken, :refresh_token),
(:Issued, Symbol(".issued")),
(:Expires, Symbol(".expires"))
)
Many popular types (including DateTime
) have converters from StructTypes, but TimesDates.TimeDateZone
does not.
This is parsed from a string, so we specify it as such:
StructTypes.StructType(::Type{TimesDates.TimeDateZone}) = StructTypes.StringType()
Now we can perform the parsing (here imagining a response
from the HTTP package):
JSON3.read(response.body, JSONWebToken)
What if we for some reason want the Issued
/Expires
field in JSONWebToken
to be a Dates.DateTime
instead?
Simply changing the type of field does not work because the Dates package refuse to parse a string with (a variable number of digits in) sub-milliseconds precision.
We can get around this by truncating the precision in the string and redefining how a string is parsed to a Dates.DateTime
(borrowing from the StructTypes package):
function StructTypes.construct(::Type{Dates.DateTime}, x::String; kw...)
dt, ms = split(t, '.')
Dates.DateTime(dt * "." * ms[1:3]; kw...)
end