Repository: NREL/OpenStudio (language: python)

Issue:
`runner.getArgumentValues` does not honor argument data type
Issue overview
--------------
Very simple OSW to demonstrate the issue:
[test.zip](https://github.com/NREL/OpenStudio/files/15313179/test.zip)

Run it w/ OpenStudio 3.8 as follows:
`openstudio run -m -w test.osw`

![image](https://github.com/NREL/OpenStudio/assets/5861765/77e3389f-8a9e-47ea-bd55-78fa664ad3d3)

It will print out `{:double_value=>40}` instead of `{:double_value=>40.0}`. This caused a bug in our software because we ended up doing integer division instead of float division. Since the argument is of type Double, `runner.getArgumentValues` should be able to return the value as a double, right?

(Also, I'm seeing that `val=1` is being output when I use the `run -m` flag. Maybe some debug code in OS that wasn't removed?)

@joseph-robertson @jmarrec 
`runner.getArgumentValues` does not honor argument data type
Issue overview
--------------
Very simple OSW to demonstrate the issue:
[test.zip](https://github.com/NREL/OpenStudio/files/15313179/test.zip)

Run it w/ OpenStudio 3.8 as follows:
`openstudio run -m -w test.osw`

![image](https://github.com/NREL/OpenStudio/assets/5861765/77e3389f-8a9e-47ea-bd55-78fa664ad3d3)

It will print out `{:double_value=>40}` instead of `{:double_value=>40.0}`. This caused a bug in our software because we ended up doing integer division instead of float division. Since the argument is of type Double, `runner.getArgumentValues` should be able to return the value as a double, right?

(Also, I'm seeing that `val=1` is being output when I use the `run -m` flag. Maybe some debug code in OS that wasn't removed?)

@joseph-robertson @jmarrec 
`runner.getArgumentValues` does not honor argument data type
Issue overview
--------------
Very simple OSW to demonstrate the issue:
[test.zip](https://github.com/NREL/OpenStudio/files/15313179/test.zip)

Run it w/ OpenStudio 3.8 as follows:
`openstudio run -m -w test.osw`

![image](https://github.com/NREL/OpenStudio/assets/5861765/77e3389f-8a9e-47ea-bd55-78fa664ad3d3)

It will print out `{:double_value=>40}` instead of `{:double_value=>40.0}`. This caused a bug in our software because we ended up doing integer division instead of float division. Since the argument is of type Double, `runner.getArgumentValues` should be able to return the value as a double, right?

(Also, I'm seeing that `val=1` is being output when I use the `run -m` flag. Maybe some debug code in OS that wasn't removed?)

@joseph-robertson @jmarrec 


Discussion / hints:
I may be seeing that in one of the measures in https://github.com/NREL/openstudio-calibration-gem/pull/66

So I guess a question is putting in 3.8.0. Is this low risk and easy enough to fix today to put in release package tomorrow, or if there is a work around to bypass the issue, do we wait.
MCVE

```ruby
arg = OpenStudio::Measure::OSArgument::makeDoubleArgument("double", true)
arg.setValue(10.0)

arg.valueAsJSON
=> 10
arg.valueAsJSON.class
=> Integer
```
I may be seeing that in one of the measures in https://github.com/NREL/openstudio-calibration-gem/pull/66

So I guess a question is putting in 3.8.0. Is this low risk and easy enough to fix today to put in release package tomorrow, or if there is a work around to bypass the issue, do we wait.
MCVE

```ruby
arg = OpenStudio::Measure::OSArgument::makeDoubleArgument("double", true)
arg.setValue(10.0)

arg.valueAsJSON
=> 10
arg.valueAsJSON.class
=> Integer
```
I may be seeing that in one of the measures in https://github.com/NREL/openstudio-calibration-gem/pull/66

So I guess a question is putting in 3.8.0. Is this low risk and easy enough to fix today to put in release package tomorrow, or if there is a work around to bypass the issue, do we wait.
MCVE

```ruby
arg = OpenStudio::Measure::OSArgument::makeDoubleArgument("double", true)
arg.setValue(10.0)

arg.valueAsJSON
=> 10
arg.valueAsJSON.class
=> Integer
```

Fix patch:
diff --git a/src/utilities/core/jsoncpp.i b/src/utilities/core/jsoncpp.i
index 4ebf7aa1c51..5f57c61e54c 100644
--- a/src/utilities/core/jsoncpp.i
+++ b/src/utilities/core/jsoncpp.i
@@ -16,29 +16,27 @@
   SWIGINTERN PyObject* SWIG_From_JsonValue(const Json::Value& value) {
     // PyErr_WarnEx(PyExc_UserWarning, "Translating a Json::Value to a PyObject", 1);  // Debugging
 
-    if (value.isNull()) {
+    switch(value.type()) {
+    case Json::nullValue:
       return Py_None;
-    }
 
-    if (value.isBool()) {
+    case Json::booleanValue:
       return value.asBool() ? Py_True : Py_False;
-    }
 
-    if (value.isIntegral()) {
+    case Json::intValue:
+    case Json::uintValue:
       return PyLong_FromLongLong(value.asInt64());
-    }
 
-    if (value.isNumeric()) {
-       return PyFloat_FromDouble(value.asDouble());
-    }
+    case Json::realValue:
+      return PyFloat_FromDouble(value.asDouble());
 
-    if (value.isString()) {
+    case Json::stringValue: {
       // return PyUnicode_FromString(value.asCString());
       const auto str = value.asString();
       return SWIG_FromCharPtrAndSize(str.data(), str.size());
     }
 
-    if (value.isArray()) {
+    case Json::arrayValue:{
       PyObject* result = PyList_New(value.size());
       Py_ssize_t idx = 0;
       for( const auto& arrayElement : value) {
@@ -50,7 +48,7 @@
       return result;
     }
 
-    if (value.isObject()) {
+    case Json::objectValue: {
       PyObject* result = PyDict_New();
       for( const auto& id : value.getMemberNames()) {
         // recursive call
@@ -61,6 +59,10 @@
       return result;
     }
 
+    default:
+      assert(false);
+    }
+
     return Py_None;
   }
 }
@@ -76,42 +78,42 @@
 %fragment("JsonToDict","header", fragment="SWIG_FromCharPtrAndSize") {
   SWIGINTERN VALUE SWIG_From_JsonValue(const Json::Value& value) {
 
-    if (value.isNull()) {
+    switch(value.type()) {
+    case Json::nullValue:
       return Qnil;
-    }
 
-    if (value.isBool()) {
+    case Json::booleanValue:
       return value.asBool() ? Qtrue : Qfalse;
-    }
 
-    if (value.isIntegral()) {
+    case Json::intValue:
+    case Json::uintValue:
       return INT2NUM(value.asInt64());
-    }
 
-    if (value.isNumeric()) {
-     return DOUBLE2NUM(value.asDouble());
-    }
+    case Json::realValue:
+      return DOUBLE2NUM(value.asDouble());
 
-    if (value.isString()) {
+    case Json::stringValue: {
+      // return PyUnicode_FromString(value.asCString());
       const auto str = value.asString();
       return SWIG_FromCharPtrAndSize(str.data(), str.size());
     }
-
-    if (value.isArray()) {
+    case Json::arrayValue: {
       VALUE result = rb_ary_new2(value.size());
       for( const auto& arrayElement : value) {
         rb_ary_push(result, SWIG_From_JsonValue(arrayElement));
       }
       return result;
     }
-
-    if (value.isObject()) {
+    case Json::objectValue: {
       VALUE result = rb_hash_new();
       for( const auto& id : value.getMemberNames()) {
         rb_hash_aset(result, ID2SYM(rb_intern(id.data())), SWIG_From_JsonValue(value[id]));
       }
       return result;
     }
+    default:
+      assert(false);
+    }
 
     return Qnil;
   }


Test patch:
diff --git a/python/test/test_swigjson.py b/python/test/test_swigjson.py
new file mode 100644
index 00000000000..f7fb06d3772
--- /dev/null
+++ b/python/test/test_swigjson.py
@@ -0,0 +1,49 @@
+from pathlib import Path
+
+import openstudio
+
+
+def test_osargument_value_as_json():
+    boolArgument = openstudio.measure.OSArgument.makeBoolArgument("bool")
+    valNotAssigned = boolArgument.valueAsJSON()
+    assert valNotAssigned is None
+    boolArgument.setValue(True)
+    val = boolArgument.valueAsJSON()
+    assert isinstance(val, bool)
+    assert val
+
+    doubleArgument = openstudio.measure.OSArgument.makeDoubleArgument("double")
+    valNotAssigned = doubleArgument.valueAsJSON()
+    assert valNotAssigned is None
+    doubleArgument.setValue(10.0)
+    val = doubleArgument.valueAsJSON()
+    assert isinstance(val, float)
+    assert val == 10.0
+
+    integerArgument = openstudio.measure.OSArgument.makeIntegerArgument("integer")
+    valNotAssigned = integerArgument.valueAsJSON()
+    assert valNotAssigned is None
+    integerArgument.setValue(10)
+    val = integerArgument.valueAsJSON()
+    assert isinstance(val, int)
+    assert val == 10
+
+    stringArgument = openstudio.measure.OSArgument.makeStringArgument("string")
+    valNotAssigned = stringArgument.valueAsJSON()
+    assert valNotAssigned is None
+    stringArgument.setValue("hello")
+    val = stringArgument.valueAsJSON()
+    assert isinstance(val, str)
+    assert val == "hello"
+
+    choices_to_display_values_map = {
+        "handle1": "choice1",
+        "handle2": "choice2",
+    }
+    choiceArgument = openstudio.measure.OSArgument.makeChoiceArgument("choice", choices_to_display_values_map)
+    valNotAssigned = choiceArgument.valueAsJSON()
+    assert valNotAssigned is None
+    choiceArgument.setValue("handle1")
+    val = choiceArgument.valueAsJSON()
+    assert isinstance(val, str)
+    assert val == "handle1"
diff --git a/ruby/test/SwigJson_Test.rb b/ruby/test/SwigJson_Test.rb
new file mode 100644
index 00000000000..77a59543541
--- /dev/null
+++ b/ruby/test/SwigJson_Test.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+########################################################################################################################
+#  OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
+#  See also https://openstudio.net/license
+########################################################################################################################
+
+require 'openstudio'
+require 'minitest/autorun'
+
+class SwigJSONTest < Minitest::Test
+  def test_osargument_value_as_json
+    boolArgument = OpenStudio::Measure::OSArgument.makeBoolArgument('bool')
+    valNotAssigned = boolArgument.valueAsJSON
+    assert valNotAssigned.nil?
+    boolArgument.setValue(true)
+    val = boolArgument.valueAsJSON
+    assert val.is_a?(TrueClass) || val.is_a?(FalseClass)
+    assert val
+
+    doubleArgument = OpenStudio::Measure::OSArgument.makeDoubleArgument('double')
+    valNotAssigned = doubleArgument.valueAsJSON
+    assert valNotAssigned.nil?
+    doubleArgument.setValue(10.0)
+    val = doubleArgument.valueAsJSON
+    assert_kind_of(Float, val)
+    assert_equal(10.0, val)
+
+    integerArgument = OpenStudio::Measure::OSArgument.makeIntegerArgument('integer')
+    valNotAssigned = integerArgument.valueAsJSON
+    assert valNotAssigned.nil?
+    integerArgument.setValue(10)
+    val = integerArgument.valueAsJSON
+    assert_kind_of(Integer, val)
+    assert_equal(10, val)
+
+    stringArgument = OpenStudio::Measure::OSArgument.makeStringArgument('string')
+    valNotAssigned = stringArgument.valueAsJSON
+    assert valNotAssigned.nil?
+    stringArgument.setValue('hello')
+    val = stringArgument.valueAsJSON
+    assert_kind_of(String, val)
+    assert_equal('hello', val)
+
+    choices_to_display_values_map = {
+      'handle1' => 'choice1',
+      'handle2' => 'choice2'
+    }
+
+    choiceArgument = OpenStudio::Measure::OSArgument.makeChoiceArgument('choice', choices_to_display_values_map)
+    valNotAssigned = choiceArgument.valueAsJSON
+    assert valNotAssigned.nil?
+    choiceArgument.setValue('handle1')
+    val = choiceArgument.valueAsJSON
+    assert_kind_of(String, val)
+    assert_equal('handle1', val)
+  end
+end
diff --git a/src/measure/test/OSArgument_GTest.cpp b/src/measure/test/OSArgument_GTest.cpp
index c57d7a072ed..ba79795d8d0 100644
--- a/src/measure/test/OSArgument_GTest.cpp
+++ b/src/measure/test/OSArgument_GTest.cpp
@@ -8,6 +8,7 @@
 
 #include "../OSArgument.hpp"
 
+#include <json/value.h>
 #include <vector>
 #include <map>
 
@@ -213,3 +214,60 @@ TEST_F(MeasureFixture, OSArgument_MakeChoiceArgumentFromMap) {
   EXPECT_EQ(required, choiceArgument.required());
   EXPECT_EQ(modelDependent, choiceArgument.modelDependent());
 }
+
+TEST_F(MeasureFixture, OSArgument_valueAsJSON) {
+
+  {
+    OSArgument boolArgument = OSArgument::makeBoolArgument("bool");
+    Json::Value valNotAssigned = boolArgument.valueAsJSON();
+    EXPECT_TRUE(valNotAssigned.isNull());
+    boolArgument.setValue(true);
+    Json::Value val = boolArgument.valueAsJSON();
+    EXPECT_TRUE(val.isBool());
+    EXPECT_TRUE(val.asBool());
+  }
+
+  {
+    OSArgument doubleArgument = OSArgument::makeDoubleArgument("double");
+    Json::Value valNotAssigned = doubleArgument.valueAsJSON();
+    EXPECT_TRUE(valNotAssigned.isNull());
+    doubleArgument.setValue(10.0);
+    Json::Value val = doubleArgument.valueAsJSON();
+    EXPECT_TRUE(val.isDouble());
+    EXPECT_DOUBLE_EQ(10.0, val.asDouble());
+  }
+
+  {
+    OSArgument integerArgument = OSArgument::makeIntegerArgument("integer");
+    Json::Value valNotAssigned = integerArgument.valueAsJSON();
+    EXPECT_TRUE(valNotAssigned.isNull());
+    integerArgument.setValue(10);
+    Json::Value val = integerArgument.valueAsJSON();
+    EXPECT_TRUE(val.isInt());
+    EXPECT_EQ(10, val.asInt());
+  }
+
+  {
+    OSArgument stringArgument = OSArgument::makeStringArgument("string");
+    Json::Value valNotAssigned = stringArgument.valueAsJSON();
+    EXPECT_TRUE(valNotAssigned.isNull());
+    stringArgument.setValue("hello");
+    Json::Value val = stringArgument.valueAsJSON();
+    EXPECT_TRUE(val.isString());
+    EXPECT_EQ("hello", val.asString());
+  }
+
+  {
+    const std::map<std::string, std::string> choices_to_display_values_map{
+      {"handle1", "choice1"},
+      {"handle2", "choice2"},
+    };
+    OSArgument choiceArgument = OSArgument::makeChoiceArgument("choice", choices_to_display_values_map);
+    Json::Value valNotAssigned = choiceArgument.valueAsJSON();
+    EXPECT_TRUE(valNotAssigned.isNull());
+    choiceArgument.setValue("handle1");
+    Json::Value val = choiceArgument.valueAsJSON();
+    EXPECT_TRUE(val.isString());
+    EXPECT_EQ("handle1", val.asString());
+  }
+}