Skip to content
Tyler edited this page Nov 15, 2022 · 5 revisions

Advanced usage

JSON

  • toJson(Object object[, boolean prettyPrint])
    • Converts the specified object (anything) to a JSON string
    • If the optional boolean is true, it will output the string with newlines/spaces (human-readable). If false or not provided, the output will be on one line.
    • By default, agents/populations, variables, SD objects, and analytic objects are outputted; shapes, controls, and space markups are not included.
  • fromJson(String json, Class clazz)
    • Returns the object of the specified class
    • Only applicable to non-agent/population types
  • fromAgentJson(String json, Class agentClazz, Agent anyAgent)
    • Returns an agent of the type "agentClazz"
    • The third argument can be literally any active agent; it is needed to "start" the converted agent (think similar to kindling)
  • fromAgentJson(String json, Class agentClazz, AgentArrayList population)
    • Converts the agent then adds them to the specified (existing) population inside of the owner agent
    • Returns nothing
  • fromPopulationJson(String json, Class agentClazz, Agent anyAgent)
    • Returns an arraylist of agents type "agentClazz"
    • The third argument can be literally any active agent; it is needed to "start" the agents in the population (think similar to kindling)
  • fromPopulationJson(String json, Class agentClazz, AgentArrayList population)
    • Converts the agents in the provided json array, then adds them to the specified (existing) population
    • Returns nothing

JSON Filter

For toJson, a configurable filter is available for customizing what objects are included or excluded from the output. Namely, you can specify objects in the filter by entire Java package, single classes, or by names (specifically regular expressions).

⚠ The filter works as a blacklist -- including a package/class/name will exclude it from the output.

By default, most core libraries are included in the filter, which includes blocks, markup, and presentation elements; some specific classes are as well such as the ones for color, statecharts, scale, and the parent class of events and dynamic events. What remains in the default output should pertain only to what's in your individual model.

ℹ For reference, you can get a list of the defaults in each category by printing the following class variables of the filter: defaultIgnorablePackages, defaultIgnorableClasses, and defaultIgnorableNames.

One other feature concept concerns auto-generated objects. Adding certain model elements and configuring their properties may cause AnyLogic to auto-generate some objects on your behalf. For example, when you add to a Plot using the 'Value' option (as opposed to the 'Data set' option), AnyLogic automatically creates a data set behind the scenes. By default, these auto-generated elements are not included in the filter (i.e., they do get outputted to JSON), but this can be modified.

The following functions are part of the jsonFilter object within the PyCommunicator.

ℹ All the functions that update the filter return the filter object itself. This allows you to chain updates together (see examples below).

  • skipJsonifying([String name], [Class clazz])
    • The 'name' can either be an object's name or a Java package
    • Returns whether the object details you pass will be skipped over when converting an object to JSON
  • isGeneratedIncluded()
    • Returns whether auto-generic objects are included in the filter
  • includeGenerated(boolean value)
    • Update whether to include auto-generated objects in the filter
    • Returns the filter

The following functions can have 'X' replaced with 'Packages', 'Classes', or 'Names' depending on what you want to target

  • getIgnorableX()
    • Returns the set currently part of the filter
    • This is a copy and not intended to be modified
  • includeX([String|Class] elements...)
    • Takes one or more elements you want to include in the filter (i.e., to be omitted from the output)
    • Returns the filter
  • excludeX([String|Class] elements...)
    • Takes one or more elements you want to exclude from the filter (i.e., to allow to be included in the output)
    • Returns the filter
  • clearX()
    • Completely removes all elements inside the current filter (i.e., allowing everything that was previously blocked to be outputted)
    • Returns the filter
  • resetX()
    • Reverts the current filter category back to its default set
      • The default can be checked by printing out defaultIgnorableX
    • Returns the filter

⚠ Be aware that some classes are not setup to be serialized (e.g., blocks) and attempting to convert them may throw model errors.

Example

Take the following model for example.

ss-75

In the button is the following code:

// edit filter
// <EDIT HERE>
// print this agent in JSON form
traceln(pycom.toJson(this));

Depending what - if anything - is put in place of the "// <EDIT HERE>", the outputs may change. Different scenarios are shown below. Some contents are abbreviated with "..." for brevity.

With no edits:

{
  "_ds_flow" : {"ymin" : 0.01, "xmin" : 0.0, "xmax" : 1.0, "ymax" : 0.01, ... },
  "_ds_stock1" : {"ymin" : 99.99, "xmin" : 0.0, "xmax" : 1.0, "ymax" : 100.0, ...},
  "_ds_stock2" : {"ymin" : 0.1, "xmin" : 0.0, "xmax" : 1.0, "ymax" : 0.11, ...},
  "flow" : 0.00999,
  "maxAllowedQueueTime" : 60.0,
  "stock1" : 99.9811,
  "stock2" : 0.1189,
  "timeInQueue" : {"Count" : 2.0, "Mean" : 0.516, ...}
}

With pycom.jsonFilter.includeGenerated(true);:

{
  "flow" : 0.00999,
  "maxAllowedQueueTime" : 60.0,
  "stock1" : 99.9811,
  "stock2" : 0.1189,
  "timeInQueue" : {"Count" : 2.0, "Mean" : 0.516, ...}
}

With pycom.jsonfilter.includeClasses(StatisticsDiscrete.class).includeNames("stock.*");:

{
  "_ds_flow" : {"ymin" : 0.01, "xmin" : 0.0, "xmax" : 1.0, "ymax" : 0.01, ... },
  "_ds_stock1" : {"ymin" : 99.99, "xmin" : 0.0, "xmax" : 1.0, "ymax" : 100.0, ...},
  "_ds_stock2" : {"ymin" : 0.1, "xmin" : 0.0, "xmax" : 1.0, "ymax" : 0.11, ...},
  "flow" : 0.00999,
  "maxAllowedQueueTime" : 60.0
}

If you chain the last 2 examples and include .excludeClasses(Color.class):

{
  "flow" : 0.00999,
  "maxAllowedQueueTime" : 60.0,
  "backgroundColor" : {"red" : 255, "green" : 239, "blue" : 213, "colorSpace": {"type": 5, "profile": {"data": "AAAa3GxjbXMCMAAAbW50clJHQiBY...", ...}, ...}, ...}
}

Elaboration on the "from" JSON functions

When you're wanting to create a "live" agent (or agents) from a JSON string, the specific choice of function will depend on your usage.

Before deciding this though, first validate the JSON format is correct. In JSON, individual agents are to be represented by a dictionary, with parameter names as keys and the parameter values as the values; omitted parameters will be assigned to default values and unmatched parameters will print a one-time warning to the runtime console. Agent populations are represented by a list, with each element as a dictionary (representing an agent in that population).

Note 1: If any parameter types have units (e.g., Rate, Time), the JSON values will represent whatever units the parameter's default is.

Note 2: If any parameter types are intended to be a list, your agent's parameter will need to use a non-primitive List class (e.g., ArrayList); i.e., primitive arrays are not (currently) supported. For example, the agent's parameter will need to use ArrayList<Double> as opposed to double[].

Your decision between fromAgentJSON and fromPopulationJSON will then be dependent on whether your JSON is a dictionary or list-of-dictionaries (respectively).

Both functions and their variants share the first two arguments:

  1. Your JSON (a String)
  2. The Java class of the agent type (in the format TYPENAME.class; e.g., Worker.class)

The third argument will depend on your intended usage of the newly created agent(s).

  • If you want the agent(s) returned as an object in your code, pass in any agent. This is needed to "start" the agent(s); internally, it's used to call the createAndStart function (as described in this AnyLogic help article).
    • This is useful when you have a global variable for a single agent (used so that it persists outside the code block), or if you only needed the converted agent in the context of the single code block.
    • Note that while this argument is technically optional, if you omit it or pass null, the agent won't get added to the underlying Engine or be able to be meaningfully interacted with.
  • If you want the agent to be automatically incorporated into an existing population, pass in the name of the desired population. In this case, nothing will be returned from the function.

Example use cases

Say we have a Worker agent type with parameters of various types (as shown in the image below).

image

Our model assets consists of one JSON file for the boss of our company and another JSON file with a list of employees. In our model - namely, our Main agent - we might want to keep the boss separated from our workers (let's pretend that's important for the model logic). To do this, we'll use a variable and an agent population as the intended eventual destinations for our converted agents. Samples of the files and model objects can be seen in the image below.

image

By using the Text File object, we can easily read the text context from both of these files and store the content in two String variables: jsonTextBoss and jsonTextEmps.

In a desirable location (e.g., the "On startup" code of our Main agent), we can now update the boss variable with the converted agent from the anyAgent variant of fromAgentJson and load the employees into the population via the population variant of fromPopulationJson:

// Assign the variable with the converted agent
boss = pyCommunicator.fromAgentJson(jsonTextBoss, // JSON text 
				    Worker.class, // object type
				    this); // referring to the Main agent
			      
// Load JSON for employee list directly into the population
pyCommunicator.fromPopulationJson(jsonTextEmps, // JSON text
				  Worker.class, // individual's class
				  employees); // target population

One use case of the "other" version of fromAgentJson (where the population is the last argument) is if we later had additional JSON - possibly retrieved from other files or even web requests - that represented an individual employee that we wanted to have automatically added to the population.

Additionally, we might use the "other" version of fromPopulationJson (where any agent is the last argument) if we only needed an agent list in a limited context. For example, if our Worker agent had a discrete event flowchart inside them for individually processing a Task agent, which would be loaded in batches and distributed to free workers. In this case, since we only need the list of Tasks to pass them into the workers immediately after converting from JSON, there's no reason to use a population.

To play around with a working example extremely similar to the scenario described here, download the following demo: Demo - From JSON to Agent/Population.zip (check the various buttons/objects for comments, which should help explain what's going on).