Skip to content

Commit

Permalink
Dictionary() can now take a JSON string vector, as from readFile()
Browse files Browse the repository at this point in the history
  • Loading branch information
bhaller committed Mar 14, 2024
1 parent 0c0afc1 commit e95e6bf
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 14 deletions.
16 changes: 12 additions & 4 deletions EidosScribe/EidosHelpClasses.rtf
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@
\f3\fs18 Dictionary
\f4\fs20 , like
\f3\fs18 DataFrame
\f4\fs20 ) to make a copy, or with a singleton string in JSON format. See the
\f4\fs20 ) to make a copy, or with a string in JSON format. See the
\f3\fs18 Dictionary
\f4\fs20 class for further documentation. However, note that
\f3\fs18 DataFrame
Expand Down Expand Up @@ -567,11 +567,19 @@ Another alternative is to call
\f4\fs20 to copy key-value pairs over; it is just a shorthand for convenience.\
A final alternative is to call
\f3\fs18 Dictionary()
\f4\fs20 with a singleton
\f4\fs20 with a
\f3\fs18 string
\f4\fs20 as its only argument; this creates a new
\f4\fs20 vector as its only argument; this creates a new
\f3\fs18 Dictionary
\f4\fs20 from the string, assuming that it is a data archive in JSON format. Note that a JSON string can be generated from the
\f4\fs20 from the string, assuming that it is a data archive in JSON format. If the
\f3\fs18 string
\f4\fs20 value is not a singleton, its elements will be joined together by newlines to make a singleton
\f3\fs18 string
\f4\fs20 value; this allows the result from
\f3\fs18 readFile()
\f4\fs20 to be passed directly to
\f3\fs18 Dictionary()
\f4\fs20 even for a multiline (prettyprinted) JSON file. Note that a JSON string can be generated from the
\f3\fs18 serialize()
\f4\fs20 method of
\f3\fs18 Dictionary
Expand Down
4 changes: 2 additions & 2 deletions QtSLiM/help/EidosHelpClasses.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<p class="p4">Returns a singleton <span class="s1">string</span> value that represents the receiving object.<span class="Apple-converted-space">  </span>By default, this is simply the name of the class of the receiving object; however, many subclasses of <span class="s1">Object</span> provide a different string representation.<span class="Apple-converted-space">  </span>The value returned by <span class="s1">stringRepresentation()</span> is the same string that would be printed by <span class="s1">print()</span> for the object, so <span class="s1">stringRepresentation()</span> allows the same representation to be used in other contexts such as <span class="s1">paste()</span> and <span class="s1">cat()</span>.</p>
<p class="p1"><b>5.2<span class="Apple-converted-space">  </span>Class DataFrame</b></p>
<p class="p3">(object&lt;DataFrame&gt;$)DataFrame(...)</p>
<p class="p4">The <span class="s1">DataFrame</span> constructor can be called in the same ways as the constructor for <span class="s1">Dictionary</span> (its superclass): with no parameters to make an empty <span class="s1">DataFrame</span>, with key-value pairs, with a singleton <span class="s1">Dictionary</span> (or a subclass of <span class="s1">Dictionary</span>, like <span class="s1">DataFrame</span>) to make a copy, or with a singleton string in JSON format.<span class="Apple-converted-space">  </span>See the <span class="s1">Dictionary</span> class for further documentation.<span class="Apple-converted-space">  </span>However, note that <span class="s1">DataFrame</span> can only use <span class="s1">string</span> keys; <span class="s1">integer</span> keys are not allowed.</p>
<p class="p4">The <span class="s1">DataFrame</span> constructor can be called in the same ways as the constructor for <span class="s1">Dictionary</span> (its superclass): with no parameters to make an empty <span class="s1">DataFrame</span>, with key-value pairs, with a singleton <span class="s1">Dictionary</span> (or a subclass of <span class="s1">Dictionary</span>, like <span class="s1">DataFrame</span>) to make a copy, or with a string in JSON format.<span class="Apple-converted-space">  </span>See the <span class="s1">Dictionary</span> class for further documentation.<span class="Apple-converted-space">  </span>However, note that <span class="s1">DataFrame</span> can only use <span class="s1">string</span> keys; <span class="s1">integer</span> keys are not allowed.</p>
<p class="p2"><i>5.2.1<span class="Apple-converted-space">  </span></i><span class="s1"><i>DataFrame</i></span><i> properties</i></p>
<p class="p3">colNames =&gt; (string)</p>
<p class="p4">A vector containing all of the <span class="s1">string</span> column names in the <span class="s1">DataFrame</span>, in order.<span class="Apple-converted-space">  </span>This property is currently an alias for the <span class="s1">Dictionary</span> property <span class="s1">allKeys</span>.</p>
Expand Down Expand Up @@ -72,7 +72,7 @@
<p class="p4">Creates a new <span class="s1">Dictionary</span> object.<span class="Apple-converted-space">  </span>Called without arguments, as <span class="s1">Dictionary()</span>, this creates a new empty <span class="s1">Dictionary</span>.</p>
<p class="p4">Alternatively, key-value pairs can be passed to set up the initial state of the new <span class="s1">Dictionary</span>.<span class="Apple-converted-space">  </span>These are set, sequentially, on the new <span class="s1">Dictionary</span>, just as <span class="s1">setValue()</span> would do.<span class="Apple-converted-space">  </span>For example, calling <span class="s1">Dictionary("a", 0:3, "b", c("foo", "bar"))</span> is equivalent to calling <span class="s1">Dictionary()</span> and then calling <span class="s1">setValue("a", 0:3)</span> and then <span class="s1">setValue("b", c("foo", "bar"))</span> on it; it is just a shorthand for convenience.<span class="Apple-converted-space">  </span>Keys may be of type <span class="s1">string</span> or <span class="s1">integer</span>, but must all be of the same type; <span class="s1">Dictionary</span> supports using either <span class="s1">string</span> or <span class="s1">integer</span> keys, but they cannot be mixed in a single <span class="s1">Dictionary</span> object.</p>
<p class="p4">Another alternative is to call <span class="s1">Dictionary()</span> with a singleton <span class="s1">Dictionary</span> as its only argument; this creates a new <span class="s1">Dictionary</span> that is a copy of the <span class="s1">Dictionary</span> passed, containing the same keys and values.<span class="Apple-converted-space">  </span>This is equivalent to creating a new empty <span class="s1">Dictionary</span> and then calling <span class="s1">addKeysAndValuesFrom()</span> to copy key-value pairs over; it is just a shorthand for convenience.</p>
<p class="p4">A final alternative is to call <span class="s1">Dictionary()</span> with a singleton <span class="s1">string</span> as its only argument; this creates a new <span class="s1">Dictionary</span> from the string, assuming that it is a data archive in JSON format.<span class="Apple-converted-space">  </span>Note that a JSON string can be generated from the <span class="s1">serialize()</span> method of <span class="s1">Dictionary</span>; together with this way of creating a <span class="s1">Dictionary</span>, this provides the ability to persist arbitrary information to a string (perhaps a file on disk) and back again.<span class="Apple-converted-space">  </span>The recreated <span class="s1">Dictionary</span> should be identical to the original, except that zero length vectors such as <span class="s1">integer(0)</span>, <span class="s1">float(0)</span>, <span class="s1">logical(0)</span>, and <span class="s1">string(0)</span> will all be serialized as <span class="s1">"[]"</span> and recreated as <span class="s1">integer(0)</span> since JSON does not provide a way to specify the type of a zero-length array.</p>
<p class="p4">A final alternative is to call <span class="s1">Dictionary()</span> with a <span class="s1">string</span> vector as its only argument; this creates a new <span class="s1">Dictionary</span> from the string, assuming that it is a data archive in JSON format.<span class="Apple-converted-space">  </span>If the <span class="s1">string</span> value is not a singleton, its elements will be joined together by newlines to make a singleton <span class="s1">string</span> value; this allows the result from <span class="s1">readFile()</span> to be passed directly to <span class="s1">Dictionary()</span> even for a multiline (prettyprinted) JSON file.<span class="Apple-converted-space">  </span>Note that a JSON string can be generated from the <span class="s1">serialize()</span> method of <span class="s1">Dictionary</span>; together with this way of creating a <span class="s1">Dictionary</span>, this provides the ability to persist arbitrary information to a string (perhaps a file on disk) and back again.<span class="Apple-converted-space">  </span>The recreated <span class="s1">Dictionary</span> should be identical to the original, except that zero length vectors such as <span class="s1">integer(0)</span>, <span class="s1">float(0)</span>, <span class="s1">logical(0)</span>, and <span class="s1">string(0)</span> will all be serialized as <span class="s1">"[]"</span> and recreated as <span class="s1">integer(0)</span> since JSON does not provide a way to specify the type of a zero-length array.</p>
<p class="p2"><i>5.3.1<span class="Apple-converted-space">  </span></i><span class="s1"><i>Dictionary</i></span><i> properties</i></p>
<p class="p3">allKeys =&gt; (is)</p>
<p class="p4">A vector containing all of the <span class="s1">string</span> or <span class="s1">integer</span> keys that have been assigned values using <span class="s1">setValue()</span>, in sorted (ascending alphabetic or numeric) order.</p>
Expand Down
1 change: 1 addition & 0 deletions VERSIONS
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ development head (in the master branch):
events and callbacks may now be scheduled for a non-consecutive set of ticks, not just a range; for example, seq() or c() may be used to construct the times at which a script block runs
as a part of this work, rescheduleScriptBlock() no longer makes copies of the script block when non-consecutive ticks are requested; a single script block can now be scheduled non-consecutively
update recipe 17.4 to use this new facility instead of rescheduleScriptBlock(), and add recipe 4.1.10 demonstrating defining constants for parameters and using them for tick scheduling; also update manual sections 20.2 and 26.1 for this
make the Dictionary constructor allow a non-singleton string vector, so the (JSON) result of readFile() can be passed directly to it


version 4.1 (Eidos version 3.1):
Expand Down
29 changes: 21 additions & 8 deletions eidos/eidos_class_Dictionary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1716,16 +1716,26 @@ void EidosDictionaryRetained::ConstructFromEidos(const std::vector<EidosValue_SP
}
else if (p_arguments.size() == 1)
{
// one singleton argument; multiple overloaded meanings
// one argument; multiple overloaded meanings
EidosValue *source_value = p_arguments[0].get();

if (source_value->Count() != 1)
EIDOS_TERMINATION << "ERROR (" << p_caller_name << "): " << p_constructor_name << "(x) requires that x be a singleton (Dictionary, Dictionary subclass, or JSON string)." << EidosTerminate(nullptr);
int source_count = source_value->Count();

if (source_value->Type() == EidosValueType::kValueString)
{
// Construct from a JSON string
std::string json_string = source_value->StringAtIndex_NOCAST(0, nullptr);
// Construct from a JSON string; beginning in SLiM 4.2 this is allowed to be a string vector,
// as returned by readFile(). We just paste the lines back together with newlines. Note that
// if the user tries to put a newline inside a string value, that is actually not legal JSON
// (see https://stackoverflow.com/a/16690186/2752221), and nlohmann will error below.
std::string json_string;

for (int source_index = 0; source_index < source_count; ++source_index)
{
if (source_index > 0)
json_string.append("\n");

json_string.append(source_value->StringAtIndex_NOCAST(source_index, nullptr));
}

nlohmann::json json_rep;

try {
Expand All @@ -1738,11 +1748,14 @@ void EidosDictionaryRetained::ConstructFromEidos(const std::vector<EidosValue_SP
}
else
{
// Construct from a Dictionary or Dictionary subclass
// Construct from a singleton Dictionary or Dictionary subclass
if (source_count != 1)
EIDOS_TERMINATION << "ERROR (" << p_caller_name << "): " << p_constructor_name << "(x) requires that x be a singleton Dictionary (or a singleton subclass of Dictionary), or a string vector." << EidosTerminate(nullptr);

EidosDictionaryUnretained *source = (source_value->Type() != EidosValueType::kValueObject) ? nullptr : dynamic_cast<EidosDictionaryUnretained *>(source_value->ObjectElementAtIndex_NOCAST(0, nullptr));

if (!source)
EIDOS_TERMINATION << "ERROR (" << p_caller_name << "): " << p_constructor_name << "(x) requires that x be a singleton Dictionary (or a singleton subclass of Dictionary)." << EidosTerminate(nullptr);
EIDOS_TERMINATION << "ERROR (" << p_caller_name << "): " << p_constructor_name << "(x) requires that x be a singleton Dictionary (or a singleton subclass of Dictionary), or a string vector." << EidosTerminate(nullptr);

AddKeysAndValuesFrom(source);
}
Expand Down
6 changes: 6 additions & 0 deletions eidos/eidos_test_functions_other.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1359,6 +1359,12 @@ void _RunClassTests(const std::string &temp_path)
EidosAssertScriptSuccess_L("a = Dictionary(); a.setValue('logical_empty', logical(0)); a.setValue('logical_T', T); a.setValue('logical_F', F); a.setValue('logical_vector', c(T, F, T, F)); a.setValue('int_empty', integer(0)); a.setValue('int_singleton', 1); a.setValue('int_vector', 1:3); a.setValue('float_empty', float(0)); a.setValue('float_singleton', 1.0); a.setValue('float_vector', 1.0:3); a.setValue('string_empty', string(0)); a.setValue('string_singleton', 'foo'); a.setValue('string_vector', c('foo', 'bar', 'baz')); sa_json = a.serialize('json'); b = Dictionary(sa_json); sb_json = b.serialize('json'); identical(sa_json,sb_json);", true);
EidosAssertScriptSuccess_L("x = Dictionary('a', 5:7, 'b', 'foo'); x.setValue('c', Dictionary('d', 18)); y = x.serialize('json'); z = Dictionary(y); z = z.serialize('json'); identical(y, z);", true);

EidosAssertScriptSuccess_L("x = Dictionary('a', 1:3, 'b', 2:4); z = Dictionary('{\"a\":[1,2,3],\"b\":[2,3,4]}'); x.identicalContents(z);", true);
EidosAssertScriptSuccess_L("x = Dictionary('a', 1:3, 'b', c('foo', 'bar')); z = Dictionary('{\"a\":[1,2,3],\"b\":[\"foo\",\"bar\"]}'); x.identicalContents(z);", true);
EidosAssertScriptSuccess_L("x = Dictionary('a', 1:3, 'b', c('foo', 'bar')); z = Dictionary('{\\n \"a\":[1,2,3],\\n \"b\":[\"foo\",\"bar\"]\\n}'); x.identicalContents(z);", true); // multiline string singleton
EidosAssertScriptSuccess_L("x = Dictionary('a', 1:3, 'b', c('foo', 'bar')); z = Dictionary(c('{', ' \"a\":[1,2,3],', ' \"b\":[\"foo\",\"bar\"]', '}')); x.identicalContents(z);", true); // string vector, as from readFile()
EidosAssertScriptRaise("Dictionary(c('{', ' \"a\":[1,2,3],', ' \"b\":[\"fo', 'o\",\"bar\"]', '}'));", 0, "valid JSON string"); // line break inside a JSON string value -- illegal

// DataFrame(...)
// identicalContents()
EidosAssertScriptSuccess_L("x = DataFrame(); x.setValue('a', 0:2); x.setValue('b', c('foo', 'bar', 'baz')); x.setValue('c', c(T, F, T)); x.setValue('d', c(1.1, 2.2, 3.3)); y = DataFrame('a', 0:2, 'b', c('foo', 'bar', 'baz'), 'c', c(T, F, T), 'd', c(1.1, 2.2, 3.3)); x.identicalContents(y);", true);
Expand Down

0 comments on commit e95e6bf

Please sign in to comment.