jsonfmt: Add TO_JSON convert_blessed support

This commit is contained in:
Yorhel 2025-01-29 13:59:49 +01:00
parent 8ef2a724d1
commit a85ff98914
2 changed files with 51 additions and 11 deletions

View file

@ -106,8 +106,7 @@ static void fujson_fmt_int(pTHX_ fustr *out, SV *val) {
}
if (uv > 0) *(--r) = '0' + (uv % 10);
if (neg) *(--r) = '-';
uv = 31 - (r - buf);
fustr_write(out, r, uv);
fustr_write(out, r, 31 - (r - buf));
}
static void fujson_fmt_av(pTHX_ fustr *out, AV *av) {
@ -139,6 +138,35 @@ static void fujson_fmt_hv(pTHX_ fustr *out, HV *hv) {
fustr_write(out, "}", 1);
}
static void fujson_fmt_obj(pTHX_ fustr *out, SV *rv, SV *obj) {
dSP;
GV *method = gv_fetchmethod_autoload(SvSTASH(obj), "TO_JSON", 0);
if (!method) croak("unable to format '%s' object as JSON", HvNAME(SvSTASH(obj)));
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs(rv);
PUTBACK;
call_sv((SV *)GvCV(method), G_SCALAR);
SPAGAIN;
/* JSON::XS describes this error as "surprisingly common"... I'd be
* surprised indeed if it happens at all, but I suppose it can't hurt to
* copy their check; this sounds like be a pain to debug otherwise. */
if (SvROK(TOPs) && SvRV(TOPs) == obj)
croak("%s::TO_JSON method returned same object as was passed instead of a new one", HvNAME(SvSTASH(obj)));
obj = POPs;
PUTBACK;
fujson_fmt(aTHX_ out, obj);
FREETMPS;
LEAVE;
}
static void fujson_fmt(pTHX_ fustr *out, SV *val) {
SvGETMAGIC(val);
@ -153,6 +181,8 @@ static void fujson_fmt(pTHX_ fustr *out, SV *val) {
} else if (SvNOKp(val)) { /* Must check before IOKp, because integer conversion might have been lossy */
NV nv = SvNV_nomg(val);
if (isinfnan(nv)) croak("unable to format floating point NaN or Inf as JSON");
/* XXX: Cpanel::JSON::XS appears to always append a ".0" for round numbers, other modules do not. */
/* XXX#2: This doesn't support quadmath. Makefile.PL checks for that */
fustr_reserve(out, NV_DIG+1);
Gconvert(nv, NV_DIG, 0, out->cur);
out->cur += strlen(out->cur);
@ -161,7 +191,7 @@ static void fujson_fmt(pTHX_ fustr *out, SV *val) {
} else if (SvROK(val)) {
SV *rv = SvRV(val);
SvGETMAGIC(rv);
if (UNLIKELY(SvOBJECT(rv))) { /* TODO: Check for TO_JSON */ }
if (UNLIKELY(SvOBJECT(rv))) fujson_fmt_obj(aTHX_ out, val, rv);
else if (SvTYPE(rv) == SVt_PVHV) fujson_fmt_hv(aTHX_ out, (HV *)rv);
else if (SvTYPE(rv) == SVt_PVAV) fujson_fmt_av(aTHX_ out, (AV *)rv);
else croak("unable to format reference '%s' as JSON", SvPV_nolen(val));