46inline const std::vector<std::shared_ptr<millijson::Base> >& extract_array(
47 const std::unordered_map<std::string, std::shared_ptr<millijson::Base> >& properties,
48 const std::string& name,
49 const std::string& path)
51 auto vIt = properties.find(name);
52 if (vIt == properties.end()) {
53 throw std::runtime_error(
"expected '" + name +
"' property for object at '" + path +
"'");
56 const auto& values_ptr = vIt->second;
57 if (values_ptr->type() != millijson::ARRAY) {
58 throw std::runtime_error(
"expected an array in '" + path +
"/" + name +
"'");
61 return static_cast<const millijson::Array*
>(values_ptr.get())->value();
64inline const millijson::Array* has_names(
const std::unordered_map<std::string, std::shared_ptr<millijson::Base> >& properties,
const std::string& path) {
65 auto nIt = properties.find(
"names");
66 if (nIt == properties.end()) {
70 const auto name_ptr = nIt->second;
71 if (name_ptr->type() != millijson::ARRAY) {
72 throw std::runtime_error(
"expected an array in '" + path +
"/names'");
74 return static_cast<const millijson::Array*
>(name_ptr.get());
77template<
class Destination_>
78void fill_names(
const millijson::Array* names_ptr, Destination_* dest,
const std::string& path) {
79 const auto& names = names_ptr->value();
80 if (names.size() != dest->size()) {
81 throw std::runtime_error(
"length of 'names' and 'values' should be the same in '" + path +
"'");
84 const auto nnames = names.size();
85 for (I<
decltype(nnames)> i = 0; i < nnames; ++i) {
86 if (names[i]->type() != millijson::STRING) {
87 throw std::runtime_error(
"expected a string at '" + path +
"/names/" + std::to_string(i) +
"'");
89 dest->set_name(i,
static_cast<const millijson::String*
>(names[i].get())->value());
93template<
class Function_>
94auto process_array_or_scalar_values(
95 const std::unordered_map<std::string, std::shared_ptr<millijson::Base> >& properties,
96 const std::string& path,
99 auto vIt = properties.find(
"values");
100 if (vIt == properties.end()) {
101 throw std::runtime_error(
"expected 'values' property for object at '" + path +
"'");
104 auto names_ptr = has_names(properties, path);
105 bool has_names = names_ptr != NULL;
107 typename std::invoke_result<Function_,std::vector<std::shared_ptr<millijson::Base> >,bool,
bool>::type out_ptr;
109 const auto& values_ptr = vIt->second;
110 if (values_ptr->type() == millijson::ARRAY) {
111 out_ptr = fun(
static_cast<const millijson::Array*
>(values_ptr.get())->value(), has_names,
false);
113 std::vector<std::shared_ptr<millijson::Base> > temp { values_ptr };
114 out_ptr = fun(temp, has_names,
true);
118 fill_names(names_ptr, out_ptr, path);
123template<
class Destination_,
class Function_>
124void extract_integers(
const std::vector<std::shared_ptr<millijson::Base> >& values, Destination_* dest, Function_ check,
const std::string& path,
const Version& version) {
125 const auto n = values.size();
126 for (I<
decltype(n)> i = 0; i < n; ++i) {
127 if (values[i]->type() == millijson::NOTHING) {
128 dest->set_missing(i);
132 if (values[i]->type() != millijson::NUMBER) {
133 throw std::runtime_error(
"expected a number at '" + path +
"/values/" + std::to_string(i) +
"'");
136 auto val =
static_cast<const millijson::Number*
>(values[i].get())->value();
137 if (val != std::floor(val)) {
138 throw std::runtime_error(
"expected an integer at '" + path +
"/values/" + std::to_string(i) +
"'");
142 constexpr double upper = std::numeric_limits<std::int32_t>::max();
143 constexpr double lower = std::numeric_limits<std::int32_t>::min();
144 if (val < lower || val > upper) {
145 throw std::runtime_error(
"value at '" + path +
"/values/" + std::to_string(i) +
"' cannot be represented by a 32-bit signed integer");
148 const std::int32_t ival = val;
149 if (version.equals(1, 0) && ival == -2147483648) {
150 dest->set_missing(i);
159template<
class Destination_,
class Function_>
160void extract_strings(
const std::vector<std::shared_ptr<millijson::Base> >& values, Destination_* dest, Function_ check,
const std::string& path) {
161 const auto n = values.size();
162 for (I<
decltype(n)> i = 0; i < n; ++i) {
163 if (values[i]->type() == millijson::NOTHING) {
164 dest->set_missing(i);
168 if (values[i]->type() != millijson::STRING) {
169 throw std::runtime_error(
"expected a string at '" + path +
"/values/" + std::to_string(i) +
"'");
172 const auto& str =
static_cast<const millijson::String*
>(values[i].get())->value();
178template<
class Provisioner_,
class Externals_>
179std::shared_ptr<Base> parse_object(
const millijson::Base* contents, Externals_& ext,
const std::string& path,
const Version& version) {
180 if (contents->type() != millijson::OBJECT) {
181 throw std::runtime_error(
"each R object should be represented by a JSON object at '" + path +
"'");
183 const auto& map =
static_cast<const millijson::Object*
>(contents)->value();
185 auto tIt = map.find(
"type");
186 if (tIt == map.end()) {
187 throw std::runtime_error(
"missing 'type' property for JSON object at '" + path +
"'");
189 const auto& type_ptr = tIt->second;
190 if (type_ptr->type() != millijson::STRING) {
191 throw std::runtime_error(
"expected a string at '" + path +
"/type'");
193 const auto& type =
static_cast<const millijson::String*
>(type_ptr.get())->value();
195 std::shared_ptr<Base> output;
196 if (type ==
"nothing") {
197 output.reset(Provisioner_::new_Nothing());
199 }
else if (type ==
"external") {
200 auto iIt = map.find(
"index");
201 if (iIt == map.end()) {
202 throw std::runtime_error(
"expected 'index' property for 'external' type at '" + path +
"'");
204 const auto& index_ptr = iIt->second;
205 if (index_ptr->type() != millijson::NUMBER) {
206 throw std::runtime_error(
"expected a number at '" + path +
"/index'");
209 const auto flt_index =
static_cast<const millijson::Number*
>(index_ptr.get())->value();
210 if (flt_index != std::floor(flt_index)) {
211 throw std::runtime_error(
"expected an integer at '" + path +
"/index'");
212 }
else if (flt_index < 0) {
213 throw std::runtime_error(
"expected a non-negative integer at '" + path +
"/index'");
218 index = sanisizer::from_float<std::int32_t>(flt_index);
220 throw std::runtime_error(
"value at '" + path +
"/index' should fit in a 32-bit signed integer");
222 if (sanisizer::is_greater_than_or_equal(index, ext.size())) {
223 throw std::runtime_error(
"external index out of range at '" + path +
"/index'");
225 output.reset(Provisioner_::new_External(ext.get(index)));
227 }
else if (type ==
"integer") {
228 process_array_or_scalar_values(map, path, [&](
const auto& vals,
bool named,
bool scalar) ->
auto {
229 auto ptr = Provisioner_::new_Integer(sanisizer::cast<std::size_t>(vals.size()), named, scalar);
231 extract_integers(vals, ptr, [](std::int32_t) ->
void {}, path, version);
235 }
else if (type ==
"factor" || (version.equals(1, 0) && type ==
"ordered")) {
236 bool ordered =
false;
237 if (type ==
"ordered") {
240 auto oIt = map.find(
"ordered");
241 if (oIt != map.end()) {
242 if (oIt->second->type() != millijson::BOOLEAN) {
243 throw std::runtime_error(
"expected a boolean at '" + path +
"/ordered'");
245 ordered =
static_cast<const millijson::Boolean*
>((oIt->second).get())->value();
249 const std::string levels_name =
"levels";
250 const auto& lvals = extract_array(map, levels_name, path);
251 const auto nlevels = lvals.size();
252 auto fptr = process_array_or_scalar_values(map, path, [&](
const auto& vals,
bool named,
bool scalar) ->
auto {
253 auto ptr = Provisioner_::new_Factor(sanisizer::cast<std::size_t>(vals.size()), named, scalar, sanisizer::cast<std::size_t>(nlevels), ordered);
255 extract_integers(vals, ptr, [&](std::int32_t x) ->
void {
257 throw std::runtime_error(
"factor indices should be non-negative in '" + path +
"/values'");
258 }
else if (sanisizer::is_greater_than_or_equal(x, nlevels)) {
259 throw std::runtime_error(
"factor indices of out of range of levels in '" + path +
"/values'");
265 std::unordered_set<std::string> existing;
266 for (I<
decltype(nlevels)> l = 0; l < nlevels; ++l) {
267 if (lvals[l]->type() != millijson::STRING) {
268 throw std::runtime_error(
"expected strings at '" + path +
"/levels/" + std::to_string(l) +
"'");
271 const auto& level =
static_cast<const millijson::String*
>(lvals[l].get())->value();
272 if (existing.find(level) != existing.end()) {
273 throw std::runtime_error(
"detected duplicate string at '" + path +
"/levels/" + std::to_string(l) +
"'");
275 fptr->set_level(l, level);
276 existing.insert(level);
279 }
else if (type ==
"boolean") {
280 process_array_or_scalar_values(map, path, [&](
const auto& vals,
bool named,
bool scalar) ->
auto {
281 const auto n = vals.size();
282 auto ptr = Provisioner_::new_Boolean(sanisizer::cast<std::size_t>(n), named, scalar);
285 for (I<
decltype(n)> i = 0; i < n; ++i) {
286 if (vals[i]->type() == millijson::NOTHING) {
291 if (vals[i]->type() != millijson::BOOLEAN) {
292 throw std::runtime_error(
"expected a boolean at '" + path +
"/values/" + std::to_string(i) +
"'");
294 ptr->set(i,
static_cast<const millijson::Boolean*
>(vals[i].get())->value());
300 }
else if (type ==
"number") {
301 process_array_or_scalar_values(map, path, [&](
const auto& vals,
bool named,
bool scalar) ->
auto {
302 const auto n = vals.size();
303 auto ptr = Provisioner_::new_Number(sanisizer::cast<std::size_t>(n), named, scalar);
306 for (I<
decltype(n)> i = 0; i < n; ++i) {
307 if (vals[i]->type() == millijson::NOTHING) {
312 if (vals[i]->type() == millijson::NUMBER) {
313 ptr->set(i,
static_cast<const millijson::Number*
>(vals[i].get())->value());
314 }
else if (vals[i]->type() == millijson::STRING) {
315 auto str =
static_cast<const millijson::String*
>(vals[i].get())->value();
317 ptr->set(i, std::numeric_limits<double>::quiet_NaN());
318 }
else if (str ==
"Inf") {
319 ptr->set(i, std::numeric_limits<double>::infinity());
320 }
else if (str ==
"-Inf") {
321 ptr->set(i, -std::numeric_limits<double>::infinity());
323 throw std::runtime_error(
"unsupported string '" + str +
"' at '" + path +
"/values/" + std::to_string(i) +
"'");
326 throw std::runtime_error(
"expected a number at '" + path +
"/values/" + std::to_string(i) +
"'");
333 }
else if (type ==
"string" || (version.equals(1, 0) && (type ==
"date" || type ==
"date-time"))) {
335 if (version.equals(1, 0)) {
336 if (type ==
"date") {
337 format = StringVector::DATE;
338 }
else if (type ==
"date-time") {
339 format = StringVector::DATETIME;
342 auto fIt = map.find(
"format");
343 if (fIt != map.end()) {
344 if (fIt->second->type() != millijson::STRING) {
345 throw std::runtime_error(
"expected a string at '" + path +
"/format'");
347 auto fptr =
static_cast<const millijson::String*
>(fIt->second.get());
348 if (fptr->value() ==
"date") {
349 format = StringVector::DATE;
350 }
else if (fptr->value() ==
"date-time") {
351 format = StringVector::DATETIME;
353 throw std::runtime_error(
"unsupported format '" + fptr->value() +
"' at '" + path +
"/format'");
358 process_array_or_scalar_values(map, path, [&](
const auto& vals,
bool named,
bool scalar) ->
auto {
359 auto ptr = Provisioner_::new_String(sanisizer::cast<std::size_t>(vals.size()), named, scalar, format);
362 if (format == StringVector::NONE) {
363 extract_strings(vals, ptr, [](
const std::string&) ->
void {}, path);
364 }
else if (format == StringVector::DATE) {
365 extract_strings(vals, ptr, [&](
const std::string& x) ->
void {
366 if (!ritsuko::is_date(x.c_str(), x.size())) {
367 throw std::runtime_error(
"dates should follow YYYY-MM-DD formatting in '" + path +
"/values'");
370 }
else if (format == StringVector::DATETIME) {
371 extract_strings(vals, ptr, [&](
const std::string& x) ->
void {
372 if (!ritsuko::is_rfc3339(x.c_str(), x.size())) {
373 throw std::runtime_error(
"date-times should follow the Internet Date/Time format in '" + path +
"/values'");
381 }
else if (type ==
"list") {
382 auto names_ptr = has_names(map, path);
383 const bool has_names = names_ptr != NULL;
385 const std::string values_name =
"values";
386 const auto& vals = extract_array(map, values_name, path);
388 const auto n = vals.size();
389 auto ptr = Provisioner_::new_List(sanisizer::cast<std::size_t>(n), has_names);
392 for (I<
decltype(n)> i = 0; i < n; ++i) {
393 ptr->set(i, parse_object<Provisioner_>(vals[i].get(), ext, path +
"/values/" + std::to_string(i), version));
397 fill_names(names_ptr, ptr, path);
401 throw std::runtime_error(
"unknown object type '" + type +
"' at '" + path +
"/type'");
451template<
class Provisioner_,
class Reader_,
class Externals_>
453 millijson::ParseOptions popt;
456 auto contents = millijson::parse(reader, popt);
459 if (contents->type() == millijson::OBJECT) {
460 const auto& map =
static_cast<const millijson::Object*
>(contents.get())->value();
461 auto vIt = map.find(
"version");
462 if (vIt != map.end()) {
463 if (vIt->second->type() != millijson::STRING) {
464 throw std::runtime_error(
"expected a string in 'version'");
466 const auto& vstr =
static_cast<const millijson::String*
>(vIt->second.get())->value();
467 auto vraw = ritsuko::parse_version_string(vstr.c_str(), vstr.size(),
true);
468 version.major = vraw.major;
469 version.minor = vraw.minor;
473 ExternalTracker etrack(std::move(ext));
474 auto output = parse_object<Provisioner_>(contents.get(), etrack,
"", version);
476 if (options.
strict_list && output->type() != LIST) {
477 throw std::runtime_error(
"top-level object should represent an R list");
481 return ParsedList(std::move(output), std::move(version));
501template<
class Provisioner_,
class Externals_>
503 std::unique_ptr<byteme::Reader> ptr;
530template<
class Provisioner_,
class Externals_>
532 std::unique_ptr<byteme::Reader> ptr;