Skip to the content.

JSON Format for ProcessSystem and ProcessModel

This page documents the actual JSON format consumed by ProcessSystem.fromJson(...) and ProcessSystem.fromJsonAndRun(...).

For exporting a live ProcessSystem or ProcessModel to portable JSON, including E300-characterized fluids, stream-specific fluids, recycle settings, and MCP checks, see Process JSON Export and E300 Fluid Characterization.

Quick answer: is there a JSON verifier?

Yes:

Use validation as a pre-flight check before calling fromJson() or fromJsonAndRun().

1. Root JSON structure (builder input)

The builder expects a root object with these common keys:

Minimal valid structure:

{
  "fluid": {
    "model": "SRK",
    "temperature": 298.15,
    "pressure": 50.0,
    "mixingRule": "classic",
    "components": {
      "methane": 0.85,
      "ethane": 0.10,
      "propane": 0.05
    }
  },
  "process": [
    {
      "type": "Stream",
      "name": "feed",
      "properties": {
        "flowRate": [50000.0, "kg/hr"]
      }
    }
  ]
}

Important: current builder input uses process + properties + inlet/inlets patterns.

2. Unit definition format

Each object in process should include:

Example:

{
  "type": "Compressor",
  "name": "Comp",
  "inlet": "HP Sep.gasOut",
  "properties": {
    "outletPressure": [80.0, "bara"]
  }
}

3. Stream addressing and ports

References are by unit name, optionally with port suffix:

The resolver trims surrounding whitespace.

Common aliases accepted by the builder and validator:

Equipment/output Accepted references
Stream or generic outlet Unit, Unit.out, Unit.outStream, Unit.outlet
Separator gas Separator.gasOut, Separator.gasOutStream, Separator.gas
Separator liquid Separator.liquidOut, Separator.liquidOutStream, Separator.liquid
Three-phase liquid outlets Separator.oilOut, Separator.oil, Separator.waterOut, Separator.water
Splitter/manifold Splitter.split0, Splitter.split1, Splitter.splitStream_0, Splitter.splitStream_1
Heat exchanger HX.outlet, HX.outlet0, HX.outlet1, HX.hx0, HX.hx1

Standalone sources and connected product streams

A Stream without inlet or inlets is a standalone source. The builder clones the root fluid (or the named fluidRef) for that source, then applies its stream properties. A Stream with an inlet is different: it is a named product-stream wrapper around the resolved upstream outlet. It keeps that outlet’s live fluid identity instead of creating an independent source fluid.

The iterative wiring pass also resolves forward references once the referenced upstream outlet is available. Use inlet or inlets for physical process wiring. The optional root connections array records topology metadata for interchange and diagrams; it does not replace those wiring fields.

This complete example creates an independent feed, cools it to 20 °C, and exposes the cooler outlet as the connected Product stream:

{
  "fluid": {
    "model": "SRK",
    "temperature": 300.0,
    "pressure": 50.0,
    "components": {
      "methane": 1.0
    }
  },
  "autoRun": true,
  "process": [
    {
      "type": "Stream",
      "name": "Feed",
      "properties": {
        "flowRate": [1000.0, "kg/hr"]
      }
    },
    {
      "type": "Cooler",
      "name": "Cooler",
      "inlet": "Feed",
      "properties": {
        "outTemperature": [20.0, "C"]
      }
    },
    {
      "type": "Stream",
      "name": "Product",
      "inlet": "Cooler.outlet"
    }
  ]
}

After the build and run, Feed still owns its cloned source fluid, while Product and Cooler.outlet refer to the same live fluid. This makes Product suitable as the named inlet for subsequent equipment without duplicating the process state.

3.1 Supported equipment types

The JSON builder delegates equipment creation to EquipmentFactory, so aliases and support evolve with that factory. The current core JSON path covers these commonly used process types:

Unsupported or very specialized fields can still be carried as metadata, dataConnections, or inside advisory equipmentDesign sections while the physical model is built from the supported topology and property fields.

3.2 Compressor driver power limits and curves

Compressor units can include a nested driver object in properties. This configures the live CompressorDriver model used by compressor capacity checks, including maximum power capacity and speed-dependent maximum power curves.

Polynomial power curve example:

{
  "type": "Compressor",
  "name": "Export Compressor",
  "inlet": "HP Sep.gasOut",
  "properties": {
    "outletPressure": [120.0, "bara"],
    "speed": 6000.0,
    "driver": {
      "type": "VFD_MOTOR",
      "ratedPower": 5000.0,
      "maxPower": 5500.0,
      "ratedSpeed": 5000.0,
      "minSpeed": 1000.0,
      "maxSpeed": 6000.0,
      "maxPowerCurveCoefficients": [0.0, 1.0, 0.0]
    }
  }
}

Tabular power curve example:

"driver": {
  "type": "GAS_TURBINE",
  "ratedPower": 40500.0,
  "ratedSpeed": 7383.0,
  "ambientTemperature": 288.15,
  "maxPowerSpeedCurve": {
    "speeds": [4922.0, 6000.0, 7383.0],
    "powers": [21.8, 32.0, 44.4],
    "powerUnit": "MW"
  }
}

Supported driver.type values are ELECTRIC_MOTOR, VFD_MOTOR, GAS_TURBINE, STEAM_TURBINE, RECIPROCATING_ENGINE, and EXPANDER_DRIVE. The exported JSON uses kW for stored tabular power points, but the builder accepts kW, MW, or W in powerUnit.

4. Large process JSON example (splitter + mixer + recycle)

{
  "fluid": {
    "model": "SRK",
    "temperature": 298.15,
    "pressure": 70.0,
    "mixingRule": "classic",
    "components": {
      "methane": 0.88,
      "ethane": 0.08,
      "propane": 0.04
    }
  },
  "autoRun": true,
  "process": [
    {"type": "Stream", "name": "feed", "properties": {"flowRate": [250000.0, "kg/hr"]}},

    {"type": "Splitter", "name": "Feed Splitter", "inlet": "feed",
      "properties": {"splitFactors": [0.60, 0.40]}},

    {"type": "Cooler", "name": "Train A Cooler", "inlet": "Feed Splitter.splitStream_0",
      "properties": {"outTemperature": [20.0, "C"]}},

    {"type": "ThrottlingValve", "name": "Train B Valve", "inlet": "Feed Splitter.splitStream_1",
      "properties": {"outletPressure": [65.0, "bara"]}},

    {"type": "Mixer", "name": "Combined Mixer",
      "inlets": ["Train A Cooler.out", "Train B Valve.out"]},

    {"type": "Separator", "name": "HP Sep", "inlet": "Combined Mixer.out"},

    {"type": "Compressor", "name": "Recycle Comp", "inlet": "HP Sep.gasOut",
      "properties": {"outletPressure": [72.0, "bara"]}},

    {"type": "Recycle", "name": "Gas Recycle", "inlet": "Recycle Comp.out"},

    {"type": "Mixer", "name": "Feed + Recycle",
      "inlets": ["feed", "Gas Recycle.out"]}
  ]
}

4.1 Optional explicit connections metadata

You can include a root-level connections array to persist explicit topology metadata. These are recorded on ProcessSystem.getConnections() and are useful for interchange, PFD export, and topology analysis.

"connections": [
  {"from": "feed", "to": "Sep", "sourcePort": "outlet", "targetPort": "inlet", "type": "MATERIAL"}
]

Supported types map to ProcessConnection.ConnectionType values (MATERIAL, ENERGY, SIGNAL). Unknown types default to MATERIAL with a warning.

4.2 Design capacity and advanced equipment design metadata

Use designCapacities for deterministic equipment capacity inputs that should be applied to live equipment during build:

"designCapacities": {
  "HP Sep": {
    "internalDiameter": 2.0,
    "separatorLength": 6.0,
    "designGasLoadFactor": 0.08
  },
  "Export Compressor": {
    "ratedPower": 5000.0,
    "maxSpeed": 12000.0
  },
  "Export Cooler": {
    "maxDesignDutyMW": 12.0
  }
}

Supported normalized capacity properties are:

Equipment Capacity keys
Separator / scrubber internalDiameter, separatorLength, designGasLoadFactor
Compressor ratedPower in kW, maxSpeed
Heater / cooler maxDesignDuty in W, maxDesignDutyKW, maxDesignDutyMW
Pump maxDesignPower in kW, maxDesignVolumeFlow in actual m³/hr

The same normalized capacity values can be applied to an existing Java or JPype process with ProcessSystem.applyDesignCapacities(Map<String, Map<String, Object>>). The multi-area ProcessModel.applyDesignCapacities(...) method requires area::equipment keys. Both return per-equipment EquipmentDesignData.ApplyResult reports; multi-area report names are also qualified.

These map methods validate all targets and values before changing any design value. Unknown or ambiguous equipment, unsupported or empty property maps, non-numeric values, and values that are not finite and positive in native units cause IllegalArgumentException. Use exactly one heater duty unit key. A same-name custom constraint with incompatible units is also rejected before any area is changed: pump power requires kW, pump flow actual m³/hr (m3/hr), heater duty W, compressor speed RPM, and separator gas-load factor m/s. Keep differently based custom restrictions under separate constraint names. The multi-area method validates every area before applying any area, and rejects aliases that configure the same equipment instance twice. Keys address direct process units; nested modules are not expanded. Names containing :: cannot be used in the multi-area map.

For compatibility, JSON building retains its advisory behavior: missing and unsupported equipment are reported in designDataApplied, and the build can continue. If a custom pump power/flow, heater duty or compressor speed constraint has incompatible units, the JSON application preserves that override and adds an advisory message to its application report; the equipment design input is still applied. For supported positive inputs, JSON and the map methods use the same application utility and produce the same values and units. Supplied values replace prior values; omitted properties remain unchanged. Existing mechanical-design objects are preserved. Reapplying values refreshes cached pump power/flow, compressor speed and heater duty ratings while preserving existing constraint objects, enabled states, warning thresholds, provenance and unrelated limits. Compressor speed remains capped by an active chart maximum. These calls configure data only: they do not run, size, or qualify the process. The strict preflight prevents invalid-input partial updates, but unexpected equipment setter failures during application are propagated without rollback.

Java (given an existing process containing HP Sep):

Map<String, Object> separatorDesign = new LinkedHashMap<String, Object>();
separatorDesign.put("internalDiameter", 2.0); // m
separatorDesign.put("separatorLength", 6.0); // m
Map<String, Map<String, Object>> capacities = new LinkedHashMap<String, Map<String, Object>>();
capacities.put("HP Sep", separatorDesign);
Map<String, EquipmentDesignData.ApplyResult> applied = process.applyDesignCapacities(capacities);

Python / JPype (given an existing model containing area Separation and unit HP Sep):

from jpype import JClass

LinkedHashMap = JClass("java.util.LinkedHashMap")
separator_design = LinkedHashMap()
separator_design.put("internalDiameter", 2.0)
separator_design.put("separatorLength", 6.0)
capacities = LinkedHashMap()
capacities.put("Separation::HP Sep", separator_design)
applied = model.applyDesignCapacities(capacities)
report = applied.get("Separation::HP Sep").toJson().toString()

Pump maxDesignPower input is kW and maxDesignVolumeFlow is actual m³/hr. The shared application utility preserves the pump’s native internal power convention; callers must not convert kW to W before passing maxDesignPower. The native pump power constraint now reports both current power and design rating in its declared kW unit; older versions reported W values under a kW label. The dimensionless utilization ratio is unchanged. Compressor native power is a normalized percentage of currently available power (including driver speed dependence), and now carries % instead of the former kW label. Its design value is 100%; use compressor.getPower("kW") for the physical demand and ratedPower for the configured rating in kW. The public compressor.updatePowerConstraint(ratingKW) method updates the driver and mechanical fallback rating while preserving the normalized 100% design and 110% overload values; it rejects invalid ratings and incompatible same-name power constraints. Existing explicit driver maximum-power settings and speed curves remain authoritative and are not rescaled by a rated-power change. Configuring these values does not establish complete constraint coverage or engineering feasibility; inspect the configured constraints and their enabled state after running the process.

Large extracted process models can also include a richer equipmentDesign object. It is preserved in SimulationResult.getMetadata() and the builder applies the deterministic parts it understands:

The result metadata contains the original advisory sections plus a designDataApplied report. This lets API callers learn which parts were applied, which unit names were missing, and which sections were retained only as metadata.

4.3 Data connections

Use dataConnections for non-physical integration metadata such as historian tags, API endpoint bindings, tag quality rules, or model-variable address mappings. The builder does not create network clients from this section. It preserves the JSON in SimulationResult.getMetadata() so downstream automation code can bind the running process to plant data without losing the source mapping.

Java:

String json = ...;
ProcessJsonValidator.ValidationReport report = ProcessSystem.validateJson(json);
if (!report.isValid()) {
  throw new IllegalArgumentException("Invalid process JSON: " + report.getErrors());
}
SimulationResult result = ProcessSystem.fromJsonAndRun(json);

Python (via JPype wrapper):

report = ns.ProcessSystem.validateJson(json_text)
if not report.isValid():
    raise ValueError(str(report.getErrors()))
result = ns.ProcessSystem.fromJsonAndRun(json_text)

6. Importing E300 fluids (supported)

The JSON builder supports loading fluid thermodynamics from Eclipse E300 data using e300FilePath in the fluid object.

Example:

{
  "fluid": {
    "model": "PR_LK",
    "temperature": 310.15,
    "pressure": 50.0,
    "e300FilePath": "/absolute/path/to/fluid.e300"
  },
  "process": [
    {"type": "Stream", "name": "feed", "properties": {"flowRate": [10000.0, "kg/hr"]}}
  ]
}

Notes:

7. What validator checks today

Errors:

Warnings:

8. ProcessModel lifecycle JSON

ProcessModel.toJson() exports builder JSON with a top-level areas object. Each area value is a regular ProcessSystem builder JSON object, and model-level iteration settings are included at the root. Shared live streams between areas are exported as interAreaLinks where needed.

Minimal shape:

{
  "areas": {
    "separation": {
      "fluid": {"model": "SRK", "components": {"methane": 1.0}},
      "process": []
    },
    "compression": {
      "fluid": {"model": "SRK", "components": {"methane": 1.0}},
      "process": []
    }
  },
  "maxIterations": 100,
  "flowTolerance": 1.0e-6
}

Use ProcessModel.fromJson(json) or ProcessModel.fromJsonAndRun(json) for the builder JSON path. The same ProcessJsonValidator.validate(...) preflight accepts both single process roots and model-level areas roots.

After building a multi-area model, use model.toDOT() or model.exportToGraphviz("plant.dot") to create one common Graphviz DOT graph with each area shown as a cluster. Use model.exportAreaDOT(Paths.get("plant-diagrams")) when you want one DOT file per contained ProcessSystem area. Cross-area DOT edges are drawn for shared live streams, including streams recreated from interAreaLinks during JSON import.

8.1 Lifecycle JSON

For lifecycle snapshots/versioning of full ProcessModel and ProcessSystem states, see:

Builder input JSON (fromJson) and lifecycle snapshot JSON (ProcessModelState) have different purposes.

9. Export and full JSON round-trip workflows

9.1 Builder JSON round-trip (ProcessSystem -> JSON -> ProcessSystem)

Use this for interoperability and API payloads.

// Build or run a process
ProcessSystem process = ...;
process.run();

// Export to builder JSON
String json = process.toJson();

// Rebuild from exported JSON
SimulationResult rebuilt = ProcessSystem.fromJson(json);
if (!rebuilt.isSuccess()) {
  throw new RuntimeException(rebuilt.toString());
}

// Optional: run rebuilt process
rebuilt.getProcessSystem().run();

Python pattern:

json_text = process.toJson()
rebuild = ns.ProcessSystem.fromJson(json_text)
if not rebuild.isSuccess():
    raise RuntimeError(str(rebuild))
rebuilt_process = rebuild.getProcessSystem()
rebuilt_process.run()

9.2 Lifecycle JSON round-trip (state snapshots)

Use ProcessSystemState and ProcessModelState for Git-friendly model snapshots, versioning, comparison, and lifecycle workflows.

// Single ProcessSystem snapshot
ProcessSystemState state = ProcessSystemState.fromProcessSystem(process);
state.saveToFile("model_state.json");
ProcessSystemState loaded = ProcessSystemState.loadFromFile("model_state.json");
ProcessSystem restored = loaded.toProcessSystem();

// Multi-area ProcessModel snapshot
ProcessModelState modelState = ProcessModelState.fromProcessModel(model);
modelState.saveToFile("plant_state.json");
ProcessModelState loadedModelState = ProcessModelState.loadFromFile("plant_state.json");
ProcessModel restoredModel = loadedModelState.toProcessModel();

Choose the round-trip type by purpose:

10. Coverage note

Not every possible NeqSim feature is currently expressible in concise JSON form. The JSON builder supports the most commonly used process/equipment workflows, and coverage continues to evolve. For advanced behavior, you can:

Round-trip tests should use the builder JSON path, not report JSON:

11. JSON + optimization loop example (process + sizing variables)

You can tune both process variables and equipment sizing variables directly from JSON payloads. A common pattern is:

  1. Optimizer proposes a payload (e.g., separator pressure + mechanical design knobs).
  2. Validate JSON with ProcessSystem.validateJson(...).
  3. Build/run with ProcessSystem.fromJsonAndRun(...).
  4. Evaluate objective (energy, CAPEX proxy, emissions, etc.).

Example payload fragment:

{
  "process": [
    {"type": "Separator", "name": "HP Sep", "inlet": "feed",
      "properties": {
        "pressure": [70.0, "bara"],
        "mechanicalDesign": {
          "gasLoadFactor": 0.107,
          "retentionTime": 120.0,
          "calcDesign": true
        }
      }
    }
  ]
}

Python pseudo-loop:

for candidate in candidates:
    payload = make_payload(candidate)
    report = ns.ProcessSystem.validateJson(payload)
    if not report.isValid():
        continue
    result = ns.ProcessSystem.fromJsonAndRun(payload)
    if not result.isSuccess():
        continue
    process = result.getProcessSystem()
    # Example objective: combine process KPI + design KPI
    score = objective(process)