DSS-Extensions — JSON exports#

Updated for DSS C-API version 0.13.2

You can open and then run this notebook on Google Colab for a quick overview if you don’t want to set up a local environment: Open in Colab.

Preparation

To rerun this notebook:

  • Install Python; we recommend using miniforge from conda-forge – after installation, either create a dedicated environment or make sure the basics are installed (conda install jupyterlab pandas)

  • Install the DSS-Extensions (python -m pip install dss-python[plot] opendssdirect.py)

  • Download the example files:

    • If you have Git installed (recommended), you can run the following cell.

    • Otherwise, download dss-extensions/electricdss-tst and extract the electricdss-tst-master folder in the same path as this notebook. Rename it to electricdss-tst.

! git clone --depth=1 -q https://github.com/dss-extensions/electricdss-tst
fatal: destination path 'electricdss-tst' already exists and is not an empty directory.

If you are running under Google Colab, running the following cell should install the packages.

import os, subprocess
if os.getenv("COLAB_RELEASE_TAG"):
    print(subprocess.check_output('pip install dss-python[plot] opendssdirect.py[extras] pandas==2', shell=True).decode())

Introduction#

Although this is a Python notebook and uses DSS-Python, the same functions and behavior described here apply to all DSS-Extensions, even if they use different API layouts: OpenDSSDirect.py, OpenDSSDirect.jl, DSS Sharp, DSS MATLAB.

Our alternative OpenDSS engine used in DSS-Extensions, implemented in the DSS C-API library, extends the API to provide some JSON export functions since version 0.12.0. Provided some constraints, most classes and properties could be exported as JSON, but a few properties didn’t work correctly even with these contraints. Warning: This feature is an API Extension and is not available in the official OpenDSS.

Note that manually creating JSON through the classic DSS API (as implemented in the official COM DLL API) is possible but cumbersome, requiring manually tracking and converting strings to the types and several other details. We expect the DSS C-API implementation, being shared across all DSS-Extensions, can achieve better performance and remove this extra processing work.

We will use Pandas to show tables more easily in this document, but it is not required for exporting. In the near future, we plan to release integrated dataframe support for using with Pandas and similar workloads in Python and other languages.

For general interactive use of DSS objects, the work-in-progress Obj and Batch APIs (working names) provides more direct manipulation of objects. Some initial examples are available on DSS-Python’s tests: test_obj.py, test_batch.py. This new API will later be published as a separate module.

We invite users to provide feedback, including feature requests, to better guide future development. The Discussions or Issues features here are good places to give us feedback: dss-extensions/dss-extensions

See also: DSS-Extensions — OpenDSS: Overview of Python APIs

How it works#

The DSS engine maintains an order of each property of each object when they are created and populated. Our DSS engine walks through this list and export each of the properties, applying disambiguation and some other transforms. For exporting a whole class, the results for each object are collected in a JSON array.

Some redundant properties are skipped, exporting the primary value when possible instead.

For classes related to transformers and LineGeometry, there are a few auxiliary properties in DSS (wdg and cond) that are used to iterate the substructures and fill/read the values. On the JSON export, this properties are skipped, and the values that usually refer to the substructure are represented as arrays, effectively returning the values for all substructures. For example, transformers have the kVA property, which in the DSS language and general property usage refer to the kVA of the active winding of a certain transformer. In our JSON export, kVA is an array of all kVA values of the windings. For this example, kVA is turned into the kVAs property, but some properties do not have array-style options:

  • Transformer and XfmrCode: MaxTap, MinTap, RdcOhms, NumTaps, Rneut, Xneut

  • AutoTrans: MaxTap, MinTap, RdcOhms, NumTaps

  • LineGeometry: x, h (note that these are already arrays in LineSpacing in the DSS language)

All these are consequently exported as arrays (lists) in the JSON document.

Only native JSON types are used. Complex numbers are represented as pairs of floating-point numbers.

The redundant properties at marked Redundant with in DSS-Extensions: OpenDSS Commands and Properties.

See a special note about LineGeometry and Line at the end of this document.

Future expectations#

Although the basics of the JSON export feature are done, we may still adjust some aspects for better user experience and uniformity. We do not want to distance the output too far from the official OpenDSS format, but introducing a few extra properties (especially for Lines and Transformers) may lessen some concerns of usability.

Naming convention:

  • As expected for the internal property names, the JSON exports and Obj/Batch APIs will be updated to match the new internal names. Only the case of the characters will change since that doesn’t affect OpenDSS (OpenDSS is mostly case insensitive). For example nconds may be updated to NConds without negative effects. Since we already have an option to export lower-case names, users could use that if they do not want to worry about this specific change in the future.

  • Since OpenDSS have some properties which are invalid as identifiers on the most popular programming languages, we may add an option to transform the related keys. For example, %LoadLoss could be replaced with pctLoadLoss, as already done to implement the Obj/Batch APIs in Python and C++. This is not a priority.

Alternative layouts:

  • For transformers and related objects, we may provide an option to output and input windings as a list of objects. For lines and related, conductor position objects could be used.

  • For array outputs, when the arrays represent matrices, add an option to use array of arrays ([[1,2], [3,4]]) instead of plain arrays ([1,2,3,4]).

Export and import: Currently, only exporting is supported due to the various ways some classes can be populated, optional fields/properties, and so on.
There are plan to provide a more restricted alternative for importing from various formats to allow easier validation. If the data is uniform, roundtrip as dataframes (export JSON and reimporting it later as dataframes) through batch_new(df=df), e.g. dss.Obj.Line.batch_new(df=df_lines).

GeoJSON: GeoJSON export of the whole circuit, including buses, is expected in a future version. We didn’t want to add too much complexity before confirming the output is correct and reasonable.

Basic usage#

In the classic API, the active element or a whole active class can be exported.

from dss import dss

dss.Text.Command = 'redirect electricdss-tst/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss'
# Select a load
dss.ActiveCircuit.Loads.Name = '670a'
dss.ActiveCircuit.ActiveDSSElement.Name
'Load.670a'
dss.ActiveCircuit.ActiveDSSElement.ToJSON()
'{"Name":"670a","Bus1":"670.1","Phases":1,"Conn":"wye","Model":1,"kV":2.3999999999999999E+000,"kW":1.7000000000000000E+001,"kvar":1.0000000000000000E+001}'

For inspection or post-processing, we could load the JSON string in Python for better formatting:

import json
json.loads(dss.ActiveCircuit.ActiveDSSElement.ToJSON())
{'Name': '670a',
 'Bus1': '670.1',
 'Phases': 1,
 'Conn': 'wye',
 'Model': 1,
 'kV': 2.4,
 'kW': 17.0,
 'kvar': 10.0}
dss.SetActiveClass('Load')
json.loads(dss.ActiveCircuit.ActiveClass.ToJSON())
[{'Name': '671',
  'Bus1': '671.1.2.3',
  'Phases': 3,
  'Conn': 'delta',
  'Model': 1,
  'kV': 4.16,
  'kW': 1155.0,
  'kvar': 660.0},
 {'Name': '634a',
  'Bus1': '634.1',
  'Phases': 1,
  'Conn': 'wye',
  'Model': 1,
  'kV': 0.277,
  'kW': 160.0,
  'kvar': 110.0},
 {'Name': '634b',
  'Bus1': '634.2',
  'Phases': 1,
  'Conn': 'wye',
  'Model': 1,
  'kV': 0.277,
  'kW': 120.0,
  'kvar': 90.0},
 {'Name': '634c',
  'Bus1': '634.3',
  'Phases': 1,
  'Conn': 'wye',
  'Model': 1,
  'kV': 0.277,
  'kW': 120.0,
  'kvar': 90.0},
 {'Name': '645',
  'Bus1': '645.2',
  'Phases': 1,
  'Conn': 'wye',
  'Model': 1,
  'kV': 2.4,
  'kW': 170.0,
  'kvar': 125.0},
 {'Name': '646',
  'Bus1': '646.2.3',
  'Phases': 1,
  'Conn': 'delta',
  'Model': 2,
  'kV': 4.16,
  'kW': 230.0,
  'kvar': 132.0},
 {'Name': '692',
  'Bus1': '692.3.1',
  'Phases': 1,
  'Conn': 'delta',
  'Model': 5,
  'kV': 4.16,
  'kW': 170.0,
  'kvar': 151.0},
 {'Name': '675a',
  'Bus1': '675.1',
  'Phases': 1,
  'Conn': 'wye',
  'Model': 1,
  'kV': 2.4,
  'kW': 485.0,
  'kvar': 190.0},
 {'Name': '675b',
  'Bus1': '675.2',
  'Phases': 1,
  'Conn': 'wye',
  'Model': 1,
  'kV': 2.4,
  'kW': 68.0,
  'kvar': 60.0},
 {'Name': '675c',
  'Bus1': '675.3',
  'Phases': 1,
  'Conn': 'wye',
  'Model': 1,
  'kV': 2.4,
  'kW': 290.0,
  'kvar': 212.0},
 {'Name': '611',
  'Bus1': '611.3',
  'Phases': 1,
  'Conn': 'wye',
  'Model': 5,
  'kV': 2.4,
  'kW': 170.0,
  'kvar': 80.0},
 {'Name': '652',
  'Bus1': '652.1',
  'Phases': 1,
  'Conn': 'wye',
  'Model': 2,
  'kV': 2.4,
  'kW': 128.0,
  'kvar': 86.0},
 {'Name': '670a',
  'Bus1': '670.1',
  'Phases': 1,
  'Conn': 'wye',
  'Model': 1,
  'kV': 2.4,
  'kW': 17.0,
  'kvar': 10.0},
 {'Name': '670b',
  'Bus1': '670.2',
  'Phases': 1,
  'Conn': 'wye',
  'Model': 1,
  'kV': 2.4,
  'kW': 66.0,
  'kvar': 38.0},
 {'Name': '670c',
  'Bus1': '670.3',
  'Phases': 1,
  'Conn': 'wye',
  'Model': 1,
  'kV': 2.4,
  'kW': 117.0,
  'kvar': 68.0}]

Or we could load into a dataframe. Some classes fit dataframes well, some not so much.

Users should be aware that the order of properties can change the final result for some classes. Transformers and lines use indexed structures that don’t fit the tabular format by default.

import pandas as pd
pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON())
/tmp/ipykernel_17431/4005930840.py:2: FutureWarning: Passing literal json to 'read_json' is deprecated and will be removed in a future version. To read from a literal string, wrap it in a 'StringIO' object.
  pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON())
Name Bus1 Phases Conn Model kV kW kvar
0 671 671.1.2.3 3 delta 1 4.160 1155.0 660.0
1 634a 634.1 1 wye 1 0.277 160.0 110.0
2 634b 634.2 1 wye 1 0.277 120.0 90.0
3 634c 634.3 1 wye 1 0.277 120.0 90.0
4 645 645.2 1 wye 1 2.400 170.0 125.0
5 646 646.2.3 1 delta 2 4.160 230.0 132.0
6 692 692.3.1 1 delta 5 4.160 170.0 151.0
7 675a 675.1 1 wye 1 2.400 485.0 190.0
8 675b 675.2 1 wye 1 2.400 68.0 60.0
9 675c 675.3 1 wye 1 2.400 290.0 212.0
10 611 611.3 1 wye 5 2.400 170.0 80.0
11 652 652.1 1 wye 2 2.400 128.0 86.0
12 670a 670.1 1 wye 1 2.400 17.0 10.0
13 670b 670.2 1 wye 1 2.400 66.0 38.0
14 670c 670.3 1 wye 1 2.400 117.0 68.0
dss.SetActiveClass('Transformer')
pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON())
/tmp/ipykernel_17431/303292550.py:2: FutureWarning: Passing literal json to 'read_json' is deprecated and will be removed in a future version. To read from a literal string, wrap it in a 'StringIO' object.
  pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON())
Name Phases X12 Bus Conn kV kVA pctR Bank pctLoadLoss
0 sub 3 0.008 [sourcebus, 650] [delta, wye] [114.99999999999999, 4.16] [5000.0, 5000.0] [0.0005, 0.0005] NaN NaN
1 reg1 1 0.010 [650.1, rg60.1] NaN [2.399999999999999, 2.399999999999999] [1666.0, 1666.0] NaN reg1 0.01
2 reg2 1 0.010 [650.2, rg60.2] NaN [2.399999999999999, 2.399999999999999] [1666.0, 1666.0] NaN reg1 0.01
3 reg3 1 0.010 [650.3, rg60.3] NaN [2.399999999999999, 2.399999999999999] [1666.0, 1666.0] NaN reg1 0.01
4 xfm1 3 2.000 [633, 634] [wye, wye] [4.16, 0.4799999999999999] [500.0, 500.0] [0.55, 0.55] NaN NaN

This NaN values above should be the empty/non-filled properties. For example, check the JSON for reg1 directly:

dss.ActiveCircuit.Transformers.Name = 'reg1'
json.loads(dss.ActiveCircuit.ActiveDSSElement.ToJSON())
{'Name': 'reg1',
 'Phases': 1,
 'Bank': 'reg1',
 'X12': 0.01,
 'kVA': [1666.0, 1666.0],
 'Bus': ['650.1', 'rg60.1'],
 'kV': [2.4, 2.4],
 'pctLoadLoss': 0.01}

For reg1, the properties windings, conn and %R were not provided. The order of the properties in this previous cell output are in the same order as the original DSS script.
We can check how the properties were used in the input file for comparison:

with open('electricdss-tst/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss', 'r') as ckt_file:
    for text_line in ckt_file:
        if 'transformer.reg1' in text_line.lower():
            # Found the transformer
            print(text_line, end='')
            
            #...but need to check for the MORE command:
            text_line = next(ckt_file)
            while text_line.startswith('~'):
                print(text_line, end='')
                text_line = next(ckt_file)

            break
New Transformer.Reg1 phases=1 bank=reg1 XHL=0.01 kVAs=[1666 1666]
~ Buses=[650.1 RG60.1] kVs=[2.4  2.4] %LoadLoss=0.01

From this, note that Buses was reported in the bus field as an array, as well as kVAs and kVs were exported as a kVA and kV arrays, respectively. This is done by disambiguation process.

As a sidenote, the dataframe can be a little better with Pandas 2.0+, using NA instead of NaN (which forces converting integers to floats):

pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON(), dtype_backend='pyarrow')
/tmp/ipykernel_17431/2594549408.py:1: FutureWarning: Passing literal json to 'read_json' is deprecated and will be removed in a future version. To read from a literal string, wrap it in a 'StringIO' object.
  pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON(), dtype_backend='pyarrow')
Name Phases X12 Bus Conn kV kVA pctR Bank pctLoadLoss
0 sub 3 0.008 [sourcebus, 650] [delta, wye] [114.99999999999999, 4.16] [5000.0, 5000.0] [0.0005, 0.0005] <NA> <NA>
1 reg1 1 0.01 [650.1, rg60.1] NaN [2.399999999999999, 2.399999999999999] [1666.0, 1666.0] NaN reg1 0.01
2 reg2 1 0.01 [650.2, rg60.2] NaN [2.399999999999999, 2.399999999999999] [1666.0, 1666.0] NaN reg1 0.01
3 reg3 1 0.01 [650.3, rg60.3] NaN [2.399999999999999, 2.399999999999999] [1666.0, 1666.0] NaN reg1 0.01
4 xfm1 3 2.0 [633, 634] [wye, wye] [4.16, 0.4799999999999999] [500.0, 500.0] [0.55, 0.55] <NA> <NA>

Export option flags#

Some flags are currently implemented to adjust the output:

  • Full: Exports all properties, even if not filled by the user. This can be useful for debugging or just checking some default properties.

  • EnumAsInt: Try to use integer representation for enums. Note that the integer values may be specific to a version of the engine — that is, don’t use the integer values for long-term storage. The integer values may be useful to avoid the work of converting the strings to integer on user code and so on. The enums should be available in the DSS.Obj classes, and are exported from the DSS_ExtractSchema low-level function.

  • SkipRedundant: Avoid outputting redundant properties.

  • FullNames: Prefer listing object full names, including the class name, even where it is not required.

  • Pretty: Pretty-format the output (as much as the default implementation of fpjson in Free Pascal allows). The output is a bit more readable without requiring loading the JSON.

  • ExcludeDisabled: When exporting a whole class, skip disabled elements.

  • SkipDSSClass: By default, a JSON field “DSSClass” is added with the name of the DSS class for each element. This may not always be required or useful, so we can skip it sometimes.

  • LowercaseKeys: Use all lowercase keys instead of the internal variants for all keys. “DSSClass” is also converted to “dssclass”.

These are bit flags, so you have to bitwise-or them together when multiple flags are used.

from dss import DSSJSONFlags
?? DSSJSONFlags

Let’s print the Pretty, LowercaseKeys and Full outputs with for reg1:

dss.ActiveCircuit.Transformers.Name = 'reg1'
print(dss.ActiveCircuit.ActiveDSSElement.ToJSON())
{"Name":"reg1","Phases":1,"Bank":"reg1","X12":1.0000000000000000E-002,"kVA":[1.6660000000000000E+003,1.6660000000000000E+003],"Bus":["650.1","rg60.1"],"kV":[2.3999999999999999E+000,2.3999999999999999E+000],"pctLoadLoss":1.0000000000000000E-002}
dss.ActiveCircuit.Transformers.Name = 'reg1'
print(dss.ActiveCircuit.ActiveDSSElement.ToJSON(DSSJSONFlags.Pretty))
{
  "Name" : "reg1",
  "Phases" : 1,
  "Bank" : "reg1",
  "X12" : 1.0000000000000000E-002,
  "kVA" : [
    1.6660000000000000E+003,
    1.6660000000000000E+003
  ],
  "Bus" : [
    "650.1",
    "rg60.1"
  ],
  "kV" : [
    2.3999999999999999E+000,
    2.3999999999999999E+000
  ],
  "pctLoadLoss" : 1.0000000000000000E-002
}
dss.ActiveCircuit.Transformers.Name = 'reg1'
print(dss.ActiveCircuit.ActiveDSSElement.ToJSON(DSSJSONFlags.LowercaseKeys))
{"Name":"reg1","phases":1,"bank":"reg1","x12":1.0000000000000000E-002,"kva":[1.6660000000000000E+003,1.6660000000000000E+003],"bus":["650.1","rg60.1"],"kv":[2.3999999999999999E+000,2.3999999999999999E+000],"%loadloss":1.0000000000000000E-002}
dss.ActiveCircuit.Transformers.Name = 'reg1'
json.loads(dss.ActiveCircuit.ActiveDSSElement.ToJSON(DSSJSONFlags.Full))
{'Name': 'reg1',
 'Phases': 1,
 'Bus': ['650.1', 'rg60.1'],
 'Conn': ['wye', 'wye'],
 'kV': [2.4, 2.4],
 'kVA': [1666.0, 1666.0],
 'Tap': [1.0, 1.05625],
 'pctR': [0.005, 0.005],
 'RNeut': [-1.0, -1.0],
 'XNeut': [0.0, 0.0],
 'Buses': ['650.1', 'rg60.1'],
 'Conns': ['wye', 'wye'],
 'kVs': [2.4, 2.4],
 'kVAs': [1666.0, 1666.0],
 'Taps': [1.0, 1.05625],
 'XHL': 0.01,
 'XHT': 35.0,
 'XLT': 30.0,
 'XSCArray': [0.01],
 'Thermal': 2.0,
 'n': 0.8,
 'm': 0.8,
 'FLRise': 65.0,
 'HSRise': 15.0,
 'pctLoadLoss': 0.01,
 'pctNoLoadLoss': 0.0,
 'NormHkVA': 1832.6,
 'EmergHkVA': 2499.0,
 'Sub': False,
 'MaxTap': [1.1, 1.1],
 'MinTap': [0.9, 0.9],
 'NumTaps': [32, 32],
 'SubName': '',
 'pctIMag': 0.0,
 'ppm_Antifloat': 1.0,
 'pctRs': [0.005, 0.005],
 'Bank': 'reg1',
 'XfmrCode': None,
 'XRConst': False,
 'X12': 0.01,
 'X13': 35.0,
 'X23': 30.0,
 'LeadLag': 'Lag',
 'WdgCurrents': '594.2557, (-28.695), 562.6086, (151.31), ',
 'Core': 'shell',
 'RDCOhms': [0.0001469387755102041, 0.0001469387755102041],
 'Ratings': [1100.0],
 'FaultRate': 0.007,
 'pctPerm': 0.0,
 'Repair': 0.0,
 'BaseFreq': 60.0,
 'Enabled': True,
 'Like': ''}
dss.SetActiveClass('Transformer')
pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON(DSSJSONFlags.SkipDSSClass | DSSJSONFlags.EnumAsInt))
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[19], line 2
      1 dss.SetActiveClass('Transformer')
----> 2 pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON(DSSJSONFlags.SkipDSSClass | DSSJSONFlags.EnumAsInt))

AttributeError: type object 'DSSJSONFlags' has no attribute 'SkipDSSClass'
dss.SetActiveClass('Line')
pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON(DSSJSONFlags.SkipDSSClass))
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[20], line 2
      1 dss.SetActiveClass('Line')
----> 2 pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON(DSSJSONFlags.SkipDSSClass))

AttributeError: type object 'DSSJSONFlags' has no attribute 'SkipDSSClass'
dss.SetActiveClass('LineCode')
pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON(DSSJSONFlags.SkipDSSClass)).head().T
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[21], line 2
      1 dss.SetActiveClass('LineCode')
----> 2 pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON(DSSJSONFlags.SkipDSSClass)).head().T

AttributeError: type object 'DSSJSONFlags' has no attribute 'SkipDSSClass'
dss.SetActiveClass('LineCode')
pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON(DSSJSONFlags.SkipDSSClass | DSSJSONFlags.SkipRedundant | DSSJSONFlags.Full)).head().T
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[22], line 2
      1 dss.SetActiveClass('LineCode')
----> 2 pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON(DSSJSONFlags.SkipDSSClass | DSSJSONFlags.SkipRedundant | DSSJSONFlags.Full)).head().T

AttributeError: type object 'DSSJSONFlags' has no attribute 'SkipDSSClass'

Using OpenDSSDirect.py#

from opendssdirect import dss as odd

odd.Basic.SetActiveClass('Capacitor')
odd.ActiveClass.ToJSON()
'[{"Name":"cap1","Bus1":"675","Phases":3,"kvar":[6.0000000000000000E+002],"kV":4.1600000000000001E+000},{"Name":"cap2","Bus1":"611.3","Phases":1,"kvar":[1.0000000000000000E+002],"kV":2.3999999999999999E+000}]'
odd.Loads.First()
odd.Element.ToJSON()
'{"Name":"671","Bus1":"671.1.2.3","Phases":3,"Conn":"delta","Model":1,"kV":4.1600000000000001E+000,"kW":1.1550000000000000E+003,"kvar":6.6000000000000000E+002}'
json.loads(odd.Element.ToJSON(DSSJSONFlags.Full))
{'Name': '671',
 'Phases': 3,
 'Bus1': '671.1.2.3',
 'kV': 4.16,
 'kW': 1155.0,
 'PF': 0.8682431421244591,
 'Model': 1,
 'Yearly': None,
 'Daily': None,
 'Duty': None,
 'Growth': None,
 'Conn': 'delta',
 'kvar': 660.0,
 'RNeut': -1.0,
 'XNeut': 0.0,
 'Status': 'Variable',
 'Class': 1,
 'VMinpu': 0.95,
 'VMaxpu': 1.05,
 'VMinNorm': 0.0,
 'VMinEmerg': 0.0,
 'XfkVA': 0.0,
 'AllocationFactor': 0.5,
 'kVA': 1330.2725284692608,
 'pctMean': 50.0,
 'pctStdDev': 10.0,
 'CVRWatts': 1.0,
 'CVRVars': 2.0,
 'kWh': 0.0,
 'kWhDays': 30.0,
 'CFactor': 4.0,
 'CVRCurve': None,
 'NumCust': 1,
 'ZIPV': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
 'pctSeriesRL': 50.0,
 'RelWeight': 1.0,
 'VLowpu': 0.5,
 'puXHarm': 0.0,
 'XRHarm': 6.0,
 'Spectrum': 'defaultload',
 'BaseFreq': 60.0,
 'Enabled': True,
 'Like': ''}

Using the Obj and Batch APIs#

Both simple objects and batch collections have the to_json() method. The same option flags are accepted.

Load = dss.Obj.Load
Load[1].to_json()
/tmp/ipykernel_17431/120103453.py:1: DeprecationWarning: Obj identifier is deprecated; use the `to_altdss()` method instead, or import AltDSS directly ("from altdss import altdss") when using it stand-alone.
  Load = dss.Obj.Load
'{"Name":"634a","Bus1":"634.1","Phases":1,"Conn":"wye","Model":1,"kV":2.7700000000000002E-001,"kW":1.6000000000000000E+002,"kvar":1.1000000000000000E+002}'

For the following cell, a batch of loads that use delta connections are selected and then exported to JSON.

from dss.IObj import Connection
print(Load.batch(conn=Connection.delta).to_json(DSSJSONFlags.Pretty))
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[27], line 1
----> 1 from dss.IObj import Connection
      2 print(Load.batch(conn=Connection.delta).to_json(DSSJSONFlags.Pretty))

ModuleNotFoundError: No module named 'dss.IObj'

Line and LineGeometry: special cases for conductors#

LineGeometry objects present some difficulties even in the official OpenDSS in DSS scripts, when using save circuit.
To workaround it, we expose all conductors in the wire property, and leave wires as the original wires array. That is, we can interpret wire in the JSON output as the full array of conductors. The class names are included, so there is no ambiguity of what it actually contains.

For Line, cncables and tscables are suppressed, and we use wires for all data instead.

dss.AllowEditor = False
dss.Text.Command = 'redirect ./electricdss-tst/Test/IEEE13_Assets.dss'
dss.SetActiveClass('WireData')
pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON())
/tmp/ipykernel_17431/3701825667.py:2: FutureWarning: Passing literal json to 'read_json' is deprecated and will be removed in a future version. To read from a literal string, wrap it in a 'StringIO' object.
  pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON())
Name NormAmps Radius GMRAC RDC RUnits RadUnits GMRUnits RAC
0 acsr_556_5 730.0 0.4635 0.37320 0.035227 kft in in NaN
1 acsr_4/0 340.0 0.2815 0.09768 0.112121 kft in in NaN
2 acsr_1/0 230.0 0.1990 0.05352 0.212121 kft in in NaN
3 cu_1/0 100.0 0.1840 0.13356 NaN mi in in 0.607
dss.SetActiveClass('LineSpacing')
pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON())
/tmp/ipykernel_17431/24329383.py:2: FutureWarning: Passing literal json to 'read_json' is deprecated and will be removed in a future version. To read from a literal string, wrap it in a 'StringIO' object.
  pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON())
Name NPhases Units X H
0 500 3 ft [-4.0, -1.0, 3.0, 0.0] [28.0, 28.0, 28.0, 24.0]
1 505 2 ft [-4.0, 3.0, 0.0] [28.0, 28.0, 24.0]
2 510 1 ft [0.5, 0.0] [29.0, 24.0]
dss.SetActiveClass('LineGeometry')
pd.set_option('display.max_colwidth', None)
pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON(), dtype_backend='pyarrow')
/tmp/ipykernel_17431/4129479670.py:3: FutureWarning: Passing literal json to 'read_json' is deprecated and will be removed in a future version. To read from a literal string, wrap it in a 'StringIO' object.
  pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON(), dtype_backend='pyarrow')
Name NConds NPhases Reduce Spacing Conductors X H Units
0 601 4 3 True 500 [WireData.acsr_556_5, WireData.acsr_556_5, WireData.acsr_556_5, WireData.acsr_4/0] NaN NaN NaN
1 602 4 3 True 500 [WireData.acsr_4/0, WireData.acsr_4/0, WireData.acsr_4/0, WireData.acsr_4/0] NaN NaN NaN
2 603 3 2 True 505 [WireData.acsr_1/0, WireData.acsr_1/0, WireData.acsr_1/0] NaN NaN NaN
3 604 3 2 True 505 [WireData.acsr_1/0, WireData.acsr_1/0, WireData.acsr_1/0] NaN NaN NaN
4 605 2 1 True 510 [WireData.acsr_1/0, WireData.acsr_1/0] NaN NaN NaN
5 606 3 3 True <NA> [CNData.cn_250, CNData.cn_250, CNData.cn_250] [-0.5, 0.0, 0.5] [-4.0, -4.0, -4.0] [ft, ft, ft]
6 607 2 1 True <NA> [TSData.ts_1/0, WireData.cu_1/0] [0.0, 0.25] [-4.0, -4.0] [ft, ft]

Since some of the LineGeometry objects above use LineSpacing objects to fill x and h, we would need to inspect the data from the LineSpacing objects to get all the info.
In this case, using DSSJSONFlags.Full can help. Notice in the next cell output that all LineGeometry have x and h filled.
Manually inspecting the properties through the classic DSS property API or our new Obj API would also return the correct values.

pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON(DSSJSONFlags.Full | DSSJSONFlags.FullNames), dtype_backend='pyarrow')
/tmp/ipykernel_17431/205243968.py:1: FutureWarning: Passing literal json to 'read_json' is deprecated and will be removed in a future version. To read from a literal string, wrap it in a 'StringIO' object.
  pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON(DSSJSONFlags.Full | DSSJSONFlags.FullNames), dtype_backend='pyarrow')
Name NConds NPhases Wire X H Units NormAmps EmergAmps Reduce Spacing Conductors Ratings LineType Like
0 601 4 3 [WireData.acsr_556_5, WireData.acsr_556_5, WireData.acsr_556_5, WireData.acsr_4/0] [-4.0, -1.0, 3.0, 0.0] [28.0, 28.0, 28.0, 24.0] [ft, ft, ft, ft] 730.0 1095.0 True LineSpacing.500 [WireData.acsr_556_5, WireData.acsr_556_5, WireData.acsr_556_5, WireData.acsr_4/0] [0.0] oh
1 602 4 3 [WireData.acsr_4/0, WireData.acsr_4/0, WireData.acsr_4/0, WireData.acsr_4/0] [-4.0, -1.0, 3.0, 0.0] [28.0, 28.0, 28.0, 24.0] [ft, ft, ft, ft] 340.0 510.0 True LineSpacing.500 [WireData.acsr_4/0, WireData.acsr_4/0, WireData.acsr_4/0, WireData.acsr_4/0] [0.0] oh
2 603 3 2 [WireData.acsr_1/0, WireData.acsr_1/0, WireData.acsr_1/0] [-4.0, 3.0, 0.0] [28.0, 28.0, 24.0] [ft, ft, ft] 230.0 345.0 True LineSpacing.505 [WireData.acsr_1/0, WireData.acsr_1/0, WireData.acsr_1/0] [0.0] oh
3 604 3 2 [WireData.acsr_1/0, WireData.acsr_1/0, WireData.acsr_1/0] [-4.0, 3.0, 0.0] [28.0, 28.0, 24.0] [ft, ft, ft] 230.0 345.0 True LineSpacing.505 [WireData.acsr_1/0, WireData.acsr_1/0, WireData.acsr_1/0] [0.0] oh
4 605 2 1 [WireData.acsr_1/0, WireData.acsr_1/0] [0.5, 0.0] [29.0, 24.0] [ft, ft] 230.0 345.0 True LineSpacing.510 [WireData.acsr_1/0, WireData.acsr_1/0] [0.0] oh
5 606 3 3 [CNData.cn_250, CNData.cn_250, CNData.cn_250] [-0.5, 0.0, 0.5] [-4.0, -4.0, -4.0] [ft, ft, ft] 260.0 390.0 True <NA> [CNData.cn_250, CNData.cn_250, CNData.cn_250] [0.0] oh
6 607 2 1 [TSData.ts_1/0, WireData.cu_1/0] [0.0, 0.25] [-4.0, -4.0] [ft, ft] 165.0 247.5 True <NA> [TSData.ts_1/0, WireData.cu_1/0] [0.0] oh
dss.ActiveCircuit.Lines.Name = '684611'
json.loads(dss.ActiveCircuit.ActiveDSSElement.ToJSON())
{'Name': '684611',
 'Phases': 1,
 'Bus1': '684.3',
 'Bus2': '611.3',
 'Spacing': '510',
 'Conductors': ['WireData.acsr_1/0', 'WireData.acsr_1/0'],
 'Length': 300.0,
 'Units': 'ft',
 'Ratings': [400.0],
 'NormAmps': 230.0,
 'EmergAmps': 345.0}
dss.Text.Command = 'redirect electricdss-tst/Test/IEEE13_LineAndCableSpacing.dss'
dss.SetActiveClass('Line')
pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON(), dtype_backend='pyarrow').T
/tmp/ipykernel_17431/776559013.py:2: FutureWarning: Passing literal json to 'read_json' is deprecated and will be removed in a future version. To read from a literal string, wrap it in a 'StringIO' object.
  pd.read_json(dss.ActiveCircuit.ActiveClass.ToJSON(), dtype_backend='pyarrow').T
0 1 2 3 4 5 6 7 8 9 10 11
Name 650632 632670 670671 671680 632633 632645 645646 671684 684611 692675 684652 671692
Bus1 rg60.1.2.3 632.1.2.3 670.1.2.3 671.1.2.3 632.1.2.3 632.3.2 645.3.2 671.1.3 684.3 692.1.2.3 684.1 671
Bus2 632.1.2.3 670.1.2.3 671.1.2.3 680.1.2.3 633.1.2.3 645.3.2 646.3.2 684.1.3 611.3 675.1.2.3 652.1 692
Spacing 500 500 500 500 500 505 505 505 510 515 520 <NA>
Conductors [WireData.acsr_556_5, WireData.acsr_556_5, WireData.acsr_556_5, WireData.acsr_4/0] [WireData.acsr_556_5, WireData.acsr_556_5, WireData.acsr_556_5, WireData.acsr_4/0] [WireData.acsr_556_5, WireData.acsr_556_5, WireData.acsr_556_5, WireData.acsr_4/0] [WireData.acsr_556_5, WireData.acsr_556_5, WireData.acsr_556_5, WireData.acsr_4/0] [WireData.acsr_4/0, WireData.acsr_4/0, WireData.acsr_4/0, WireData.acsr_4/0] [WireData.acsr_1/0, WireData.acsr_1/0, WireData.acsr_1/0] [WireData.acsr_1/0, WireData.acsr_1/0, WireData.acsr_1/0] [WireData.acsr_1/0, WireData.acsr_1/0, WireData.acsr_1/0] [WireData.acsr_1/0, WireData.acsr_1/0] [CNData.cn_250, CNData.cn_250, CNData.cn_250] [TSData.ts_1/0, WireData.cu_1/0] NaN
Length 2000.0 667.0 1333.0 1000.0 500.0 500.0 300.0 300.0 300.0 500.0 800.0 0.001
Units ft ft ft ft ft ft ft ft ft ft ft none
Ratings [400.0] [400.0] [400.0] [400.0] [400.0] [400.0] [400.0] [400.0] [400.0] [400.0] [400.0] NaN
NormAmps -1 -1 -1 -1 -1 -1 -1 -1 -1 260 165 <NA>
EmergAmps -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 390.0 247.5 <NA>
Phases <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> 3 1 3
Switch <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> True
R1 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> 0.0001
R0 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> 0.0001
X1 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> 0
X0 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> 0
C1 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> 0
C0 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> 0