1- # Read RDS files in C++
1+ # Read RDS/RDA files in C++
22
33![ Unit tests] ( https://github.com/LTLA/rds2cpp/actions/workflows/run-tests.yaml/badge.svg )
44![ Documentation] ( https://github.com/LTLA/rds2cpp/actions/workflows/doxygenate.yaml/badge.svg )
55
66## Overview
77
8- This repository contains a header-only C++ library for reading and writing RDS files (created with ` saveRDS() ` ) without the need to link to R's libraries.
8+ This repository contains a header-only C++ library for reading and writing RDS files (created with ` saveRDS() ` ) or RDA files (created with ` save() ` ) without the need to link to R's libraries.
99In this manner, we can use RDS as a flexible data exchange format across different frameworks that have C++ bindings,
1010e.g., [ Python] ( https://github.com/biocpy/rds2py ) , [ Javascript (via Wasm)] ( https://github.com/jkanche/scran.js ) .
1111We currently support most user-visible data structures such as atomic vectors, lists, environments and S4 classes.
@@ -19,7 +19,7 @@ Given a path to an RDS file, the `parse_rds()` function will return a pointer to
1919
2020// Returns an object containing the file information,
2121// e.g., R version used to read/write the file.
22- auto file_info = rds2cpp::parse_rds(fpath, {} );
22+ auto file_info = rds2cpp::parse_rds(fpath, rds2cpp::ParseRdsOptions() );
2323
2424// Get the pointer to the actual R object.
2525const auto & ptr = file_info->object;
@@ -31,8 +31,7 @@ For example, if we wanted to process integer vectors:
3131``` cpp
3232if (ptr->type () == rds2cpp::SEXPType::INT) {
3333 auto iptr = static_cast<const rds2cpp::IntegerVector*>(ptr.get());
34- const auto& values = iptr->data; // vector of int32_t's.
35- const auto& attr_names = iptr->attributes.names; // vector of attribute names.
34+ const std::vector<std::int32_t>& values = iptr->data;
3635}
3736```
3837
@@ -48,16 +47,26 @@ if (ptr->type() == rds2cpp::SEXPType::VEC) {
4847 auto lptr = static_cast<const rds2cpp::GenericVector*>(ptr.get());
4948 const auto& elements = lptr->data; // vector of pointers to list elements.
5049
51- const auto& attr = lptr->attributes;
52- const auto& attr_names = sptr->attributes.names;
53- const auto& attr_values = sptr->attributes.values;
54-
55- // Scanning for the list names.
56- auto nIt = std::find(attr_names.begin(), attr_names.end(), std::string("names"));
57- if (nIt != attr_names.end()) {
58- size_t nindex = nIt - attr_names.begin();
59- if (attr_values[nindex]->type() == rds2cpp::SEXPType::STR) {
60- auto nptr = static_cast<const rds2cpp::StringVector*>(attr_values[nindex].get());
50+ for (const auto& attr : lptr->attributes) {
51+ // Symbols are referenced by their position in the 'symbols' vector.
52+ const auto& attr_name = file_info.symbols[attr.name.index].name;
53+
54+ if (attr_name == "names") {
55+ if (attr.value->type() != rds2cpp::SEXPType::STR) {
56+ // Just adding some protection for weird objects.
57+ throw std::runtime_error("oops, names should be strings!");
58+ }
59+
60+ auto nptr = static_cast<const rds2cpp::StringVector*>(attr.value.get());
61+ for (const auto& str : nptr->value) {
62+ if (!str.value.has_value()) {
63+ throw std::runtime_error("oops, names should not be missing!");
64+ }
65+
66+ const std::string& str_value = *(str.value); // value of the string.
67+ const auto& str_enc = str.encoding; // encoding of the string.
68+ // Do something with the list names...
69+ }
6170 }
6271 }
6372}
@@ -71,8 +80,11 @@ if (ptr->type() == rds2cpp::SEXPType::S4) {
7180 auto sptr = static_cast<const rds2cpp::S4Object*>(ptr.get());
7281 sptr->class_name;
7382 sptr->package_name;
74- const auto& slot_names = sptr->attributes.names;
75- const auto& slot_values = sptr->attributes.values;
83+
84+ for (const auto& slot : sptr->attributes) {
85+ const auto& slot_name = file_info.symbols[slot.name.index].name;
86+ const auto& slot_val = *(slot.value); // Do something with the slot value...
87+ }
7688}
7789```
7890
@@ -81,9 +93,13 @@ These should be treated as file-specific globals that may be referenced one or m
8193
8294``` cpp
8395if (ptr->type () == rds2cpp::SEXPType::ENV) {
84- const auto& env = file_info->environments[eptr->index];
85- const auto& vnames = env.variable_names;
86- const auto& vvalues = env.variable_values;
96+ auto eptr = static_cast<const rds2cpp::EnvironmentIndex*>(ptr.get());
97+ const auto& env = file_info.environments[eptr->index];
98+
99+ for (const auto& var = env.variables) {
100+ const auto& var_name = file_info.symbols[var.name.index].name;
101+ const auto& var_value = *(var.value); // Do something with the variable...
102+ }
87103}
88104```
89105
@@ -107,47 +123,64 @@ auto vec = new rds2cpp::IntegerVector;
107123file_info.object.reset(vec);
108124
109125// Storing data in the integer vector.
110- vec->data = std::vector<int32_t >{ 0, 1, 2, 3, 4, 5 };
126+ vec->data = std::vector<std:: int32_t >{ 0, 1, 2, 3, 4, 5 };
111127
112- rds2cpp::write_rds (file_info, "some_file_path.rds");
128+ rds2cpp::write_rds (file_info, "some_file_path.rds", rds2cpp::WriteRdsOptions() );
113129```
114130
115131Here's a more complicated example that saves a sparse matrix (as a `dgCMatrix` from the **Matrix** package) to file.
116132
117133```cpp
118134rds2cpp::RdsFile file_info;
119- auto ptr = new rds2cpp::S4Object;
120- file_info.object.reset(ptr);
121- auto& obj = *ptr;
135+ auto ptr = std::make_unique<rds2cpp::S4Object>();
122136
137+ auto& obj = *ptr;
123138obj.class_name = "dgCMatrix";
124139obj.package_name = "Matrix";
125140
126- auto ivec = new rds2cpp::IntegerVector;
127- obj.attributes.add("i", ivec);
128- ivec->data = std::vector<int32_t>{ 6, 8, 0, 3, 5, 6, 0, 1, 3, 7 };
129-
130- auto pvec = new rds2cpp::IntegerVector;
131- obj.attributes.add("p", pvec);
132- pvec->data = std::vector<int32_t>{ 0, 0, 2, 3, 4, 5, 6, 8, 8, 8, 10 };
133-
134- auto xvec = new rds2cpp::DoubleVector;
135- obj.attributes.add("x", xvec);
136- xvec->data = std::vector<double>{ 0.96, -0.34, 0.82, -2, -0.72, 0.39, 0.16, 0.36, -1.5, -0.47 };
137-
138- auto dims = new rds2cpp::IntegerVector;
139- obj.attributes.add("Dim", dims);
141+ auto ivec = std::make_unique<rds2cpp::IntegerVector>();
142+ ivec->data = std::vector<std::int32_t>{ 6, 8, 0, 3, 5, 6, 0, 1, 3, 7 };
143+ obj.attributes.emplace_back(
144+ rds2cpp::register_symbol("i", rds2cpp::StringEncoding::UTF8, file_info.symbols),
145+ std::move(ivec)
146+ );
147+
148+ auto pvec = std::make_unique<rds2cpp::IntegerVector>();
149+ pvec->data = std::vector<std::int32_t>{ 0, 0, 2, 3, 4, 5, 6, 8, 8, 8, 10 };
150+ obj.attributes.emplace_back(
151+ rds2cpp::register_symbol("p", rds2cpp::StringEncoding::UTF8, file_info.symbols),
152+ std::move(pvec)
153+ );
154+
155+ auto xvec = std::make_unique<rds2cpp::DoubleVector>();
156+ xvec->data = std::vector<double>{ .96, -.34, .82, -2., -.72, .39, .16, .36, -1.5, -.47 };
157+ obj.attributes.emplace_back(
158+ rds2cpp::register_symbol("x", rds2cpp::StringEncoding::UTF8, file_info.symbols),
159+ std::move(xvec)
160+ );
161+
162+ auto dims = std::make_unique<rds2cpp::IntegerVector>();
140163dims->data = std::vector<int32_t>{ 10, 10 };
141-
142- auto dimnames = new rds2cpp::GenericVector;
143- obj.attributes.add("Dimnames", dimnames);
144- dimnames->data.emplace_back(new rds2cpp::Null);
145- dimnames->data.emplace_back(new rds2cpp::Null);
146-
147- auto factors = new rds2cpp::GenericVector;
148- obj.attributes.add("factors", factors);
149-
150- rds2cpp::write_rds(file_info, "my_matrix.rds");
164+ obj.attributes.emplace_back(
165+ rds2cpp::register_symbol("Dim", rds2cpp::StringEncoding::UTF8, file_info.symbols),
166+ std::move(dims)
167+ );
168+
169+ auto dimnames = std::make_unique<rds2cpp::GenericVector>();
170+ dimnames->data.emplace_back(new Null);
171+ dimnames->data.emplace_back(new Null);
172+ obj.attributes.emplace_back(
173+ rds2cpp::register_symbol("Dimnames", rds2cpp::StringEncoding::UTF8, file_info.symbols),
174+ std::move(dimnames)
175+ );
176+
177+ obj.attributes.add(
178+ rds2cpp::register_symbol("factors", rds2cpp::StringEncoding::UTF8, file_info.symbols),
179+ std::make_unique<rds2cpp::GenericVector>()
180+ );
181+
182+ file_info.object = std::move(ptr);
183+ rds2cpp::write_rds(file_info, "my_matrix.rds", {});
151184```
152185
153186We can also create environments by registering the environment before creating indices to it.
@@ -156,20 +189,74 @@ We can also create environments by registering the environment before creating i
156189rds2cpp::RdsFile file_info;
157190
158191// Creating an environment with a 'foo' variable containing c('bar', NA, 'whee')
159- file_info.environments.resize( 1 );
160- auto & current_env = file_info.environments[ 0 ] ;
192+ file_info.environments.emplace_back( );
193+ auto & current_env = file_info.environments.back() ;
161194
162- auto sptr = new rds2cpp::StringVector;
163- current_env.add(" foo" , sptr);
164- sptr->add ("bar");
165- sptr->add(); // NA string
166- sptr->add("whee");
195+ auto sptr = std::make_unique<rds2cpp::StringVector>();
196+ sptr->data.emplace_back(" bar" , rds2cpp::StringEncoding::UTF8 );
197+ sptr->data.emplace_back(); // NA string.
198+ sptr->data.emplace_back(" whee" , rds2cpp::StringEncoding::ASCII );
167199
168- // Referencing the environment:
169- auto eptr = new rds2cpp::EnvironmentIndex(0);
170- file_info.object.reset(eptr);
200+ // The object is just a reference to the first environment:
201+ file_info.object.reset(new rds2cpp::EnvironmentIndex(0 ));
171202
172- rds2cpp::write_rds(file_info, "my_env.rds");
203+ rds2cpp::write_rds (file_info, "my_env.rds", {});
204+ ```
205+
206+ ## Reading/writing RDA files
207+
208+ RDA files (a.k.a., Rdata) use the same serialization format as RDS files.
209+ The only difference is that the object is always a list of name/object pairs.
210+ We can read these objects into memory with the `parse_rda()` function:
211+
212+ ```cpp
213+ auto file_info = rds2cpp::parse_rda(fpath, rds2cpp::ParseRdaOptions());
214+
215+ for (const auto& obj : file_info.objects) {
216+ const auto& obj_name = file_info.symbols[obj.name.index].name;
217+ switch (obj.value->type()) {
218+ case rds2cpp::SEXPType::INT:
219+ // This is an integer vector...
220+ break;
221+ case rds2cpp::SEXPType::STR:
222+ // This is a character vector...
223+ break;
224+ default:
225+ // and so on...
226+ }
227+ }
228+ ```
229+
230+ Similarly, we can write name/object pairs into an RDA file.
231+
232+ ``` cpp
233+ #include < numeric>
234+
235+ auto ivec = std::make_unique<rds2cpp::IntegerVector>(5 );
236+ std::iota (ivec->data.begin(), ivec->data.end(), 1);
237+
238+ auto list = std::make_unique< rds2cpp::GenericVector > (2);
239+ list->data[ 0] .reset(new Null);
240+ list->data[ 1] .reset(new rds2cpp::LogicalVector(10));
241+
242+ auto svec = std::make_unique< rds2cpp::StringVector > (1);
243+ svec->data[ 0] .value = "FOOBAR";
244+
245+ rds2cpp::RdaFile file_info;
246+ file_info.objects.emplace_back(
247+ rds2cpp::register_symbol("alpha", rds2cpp::StringEncoding::UTF8, file_info.symbols),
248+ std::move(ivec)
249+ );
250+ file_info.objects.emplace_back(
251+ rds2cpp::register_symbol ("bravo", rds2cpp::StringEncoding::UTF8, file_info.symbols),
252+ std::move(list)
253+ );
254+ file_info.objects.emplace_back(
255+ rds2cpp::register_symbol ("charlie", rds2cpp::StringEncoding::UTF8, file_info.symbols),
256+ std::move(svec)
257+ );
258+
259+ rds2cpp::write_rda (file_info, "my_env.Rda", rds2cpp::WriteRdaOptions());
173260```
174261
175262## Building projects
@@ -221,9 +308,8 @@ target_link_libraries(mylib INTERFACE ltla::rds2cpp)
221308
222309If you're not using CMake, the simple approach is to just copy the files in the [ ` include/ ` ] ( include ) subdirectory -
223310either directly or with Git submodules - and include their path during compilation with, e.g., GCC's ` -I ` .
224-
225- You'll also need to add the [ ** byteme** ] ( https://github.com/LTLA/byteme ) header-only library to the compiler's search path.
226- Normally, when using CMake, this is automatically linked to Zlib; this will now need to be done manually.
311+ You'll need to add the various dependencies listed in [ ` extern/CMakeLists.txt ` ] ( extern/CMakeLists.txt ) to the compiler's search path.
312+ You'll also need to link to Zlib.
227313
228314## Known limitations
229315
0 commit comments