Naming conventions for hierarchical field data.

edited May 19 in DST Development

The DST guide states that field names should use snake_case and not other naming conventions, such as camelCase (dromedary or bactrian) or kebab-case (/spinal-case/lisp-case/dash-case). This isn't very conducive to hierarchical fields, such as dynamic lists of compound items (i.e. items with multiple fields), as only one separator is available for both path components and to join compound names. I've been considering a couple different solutions, and would especially like to hear from other DST authors, with an eye towards possibly creating a shared naming scheme for dynamic fields. I suspect static hierarchical field names wouldn't be seen in the wild (since static fields generally don't need to be named hierarchically), but it's important to consider the possibility.

Note that here, "static" and "dynamic" are in relation to the DSTs. Static fields are those that are defined by the DST author in the HTML template, while "dynamic" refers to fields that may not exist within the HTML template of a DST and are defined by the player (in DSTs that have such a feature). It does not mean "dynamic" as used in "Dynamic Sheet Template" and "Dynamic Sheet Field", which are only dynamic in respect to Obsidian Portal.

Before going into solutions, it's helpful to consider use-cases, as long as we don't limit ourselves to these cases (even subconsciously) when considering the solutions. Some to consider:

  • skills: besides static skills, a DST may allow a player to add their own, which would have name & rating fields.
  • equipment: items are likely have many fields, some of which may have compound names. A DST may have more specialized examples of this, such as "weapons" or even "melee weapons".
  • dark passions: compound name. Items have a name & rating.
  • special abilities/class abilities: compound name. Items may be simple or compound, or may have dynamic sub-lists of their own (such as a list of effects).
  • user-defined lists: besides adding items to a predefined list, a DST may allow a player to define their own lists of items, adding them to a section. This would allow a DST to be used with some optional rules that add stats, or with some changes in newer editions than the sheet was designed for.
  • user-defined sections: a DST may allow a user to add whole sections, then add sub-sections or lists to those sections, then add items to those lists. This would allow a DST to be used with just about any extension to or newer version of the underlying system.
Post edited by neqis on


  • neqis
    Posts: 18 edited May 19

    Returning to the topic, a simple solution is to avoid compound names in hierarchical fields, reserving underscores for separating path components. This wouldn't be a very complete solution, as it would require some way of distinguishing hierarchical fields from non-hierarchical, and it's not always possible to avoid compound names (such as with "dark passions").

    A couple of solutions would involve breaking the naming conventions, such as using underscores/snake_case for path components and either camelCase or kebab-case for compound names, or hyphenate path components and use snake_case for compound names. As some examples, in the template HTML the DSFs could have names like:

    • dsf_custom_superPower_powerSource
    • dsf_equipment_02_label
    • dsf_equipment-02_label
    • dsf-melee_01-num_attacks
    • dsf_specialAbilities_03_effect_01_maxTargets
    • dsf_specialAbilities03_effect01_maxTargets
    • dsf-special_abilities-03-effect-01-max_targets
    • dsf_specialAbilities-03_effect-01_maxTargets

    The two-convention schemes are simple to parse, as path components are separated by a single, unique character. The downside of any use of kebab-case is that dot notation couldn't be used to access fields as properties, such as in dynamic_sheet_attrs. When it comes to dynamic fields, this is less of an issue, as the field name is likely to be generated rather than static, and thus bracket notation will be required. It would crop up for static hierarchical fields, but as stated earlier, I expect them to be rare (and bracket syntax is perfectly functional). The only significant issue of using snake_case for hierarchy and camelCase for compound names is that it goes against the current naming conventions.

    The last example uses all three conventions, which is probably too much but does offer some interesting parsing possibilities using some simple regexes. The path components can be separated with /[-_]/, while splitting on /_/ keeps the indices attached to their collections; just the names can be extracted by splitting on /[-_]\d+(?:_|$)/. While you could perform the same operations on the other schemes, for the scheme in the last example they are represented within the syntax itself.

    Another solution specific to dynamic lists relies on list items being numbered, and each item including the number in the field name: use the numbers as indicators of separate path components. This (or something like it) is the approach currently taken in DSTs with dynamic/user defined fields, though the exact schemes vary. Some example DSFs currently in use:

    • dsf_specialty_value_01 (scheme: dsf_{name}_{name}_{index})

      Note: this case is a little different than all others path examples in this post in that it's subfield-first, rather than index-first; in terms of accessing values in JS, it would translate as specialty.value[0] (or specialty_value[0]), rather than specialty[0].value. This scheme probably wouldn't match the HTML structure of the dynamic lists in a DST, since in most cases dynamic fields that belong to the same item are all in the same DST element (which represents the item); the implication of the HTML structure matching this scheme is discussed below.

    • dsf_bg3 (scheme: dsf_{name}{index})
    • dsf_bg3_expanded2 (scheme: dsf_{name}{index}_{name}{index})
    • dsf_dark_passion_04_name (scheme: dsf_{name}_{index}_{name})
    • dsf_section_01_list_03_02_name (name of the 2nd item of the 3rd (unnamed) list of section 1; scheme: dsf((_{name})?_{index))+; JS: section[0].list[2][1].name; CSS: .section:nth-of-type(1) > .list:nth-of-type(3) > *:nth-of-type(2) )

    Separating indices from names with underscores, as is done in the 1st and last example, allows for names that include numbers (e.g. "top10"). Without separating the index from the name, the number would be interpreted as part of the name. The last scheme is only slightly more complex to parse than the mixed-convention schemes; the path components are relatively easily separated by splitting using the regex /_(\d+)(?:_|$)/.

    The first example suggests an interesting structure: multiple separate lists with related fields:

    	<div class="specialty">
    <ul class="types">
    <li><span class="dsf dsf_specialty_type_01"></span></li>
    <li><span class="dsf dsf_specialty_type_02"></span></li>

    <ul class="values">
    <li><span class="dsf dsf_specialty_value_01"></span></li>
    <li><span class="dsf dsf_specialty_value_02"></span></li>

    This poses a problem for some schemes compatible with current naming conventions, as the numeric index doesn't separate path components. On the other hand, it seems unlikely to appear in the wild. Moreover, document structure is arguably distinct from data structure, so the structure of field names doesn't need to match the document structure. The above 2-ul example could use names like "dsf_specialty_02_value"; similarly, a single-list structure with multiple fields per item could just as well use names like "dsf_specialty_value_01". What's more important is that the name scheme makes sense for the structure of the data.

    One last solution, which I haven't yet observed in my admittedly small survey of DSTs but is compatible with current naming conventions is to use a double-underscore as a path separator, with a single underscore for compound names. Even though it fits conventions and can be used with arbitrary naming hierarchies, it wouldn't be my personal preference as it's a little more verbose, not as readable, and exposes the internals more.

    Post edited by neqis on
  • neqis
    Posts: 18

    Besides distinguishing path components and parts of words, it's useful to distinguish dynamic fields from static (such as when creating the elements to hold the dynamic fields). One option is to prefix dynamic field names with "dyn_" to make this easy. Note for DSF classes, this would go immediately after the "dsf_" prefix; for example, a field named "dyn_equipment_01_label" would map to an element with class "dsf_dyn_equipment_01_label". An argument against this is the word "dynamic" is (over-)used, as "dsf" already means "dynamic sheet field" (even though "dynamic" applies to the sheet and not the fields, which are often static). A similar option would be to use "udf_" (short for "user defined field") for a field name prefix.

    What do other folks think about dynamic names for fields, both naming schemes and distinguishing dynamic from static field names?

  • neqis
    Posts: 18

    Another approach (used in Chainsaw XIV's sheet libraries) is to avoid hierarchical fields entirely. Instead, related hierarchical fields are stored using JSON in a single field. This gives more control to the DST author, but at the same time requires the DST to assume management of the fields.

  • ChainsawXIV
    Posts: 530 edited June 19

    Interesting topic, and worth discussing I think.

    Here's my take, coming from the perspective of someone who's mostly a designer:

    The function of the field naming convention is to make it easier for multiple authors working on sheets for the same system to create sheets which are compatible with one another.

    Crucially, this is much more relevant for static data than for dynamic data, because - in the absence of a much more elaborate underlying system - only static fields offer any likelihood that the same field will contain data describing the same gameplay element (for a dynamic skill list for example, we can only know that a given field is the first, second, third, or so on, with no guarantees that each time someone populates a field with a particular skill it will reside in the same index).

    As a result, cross-compatibility for dynamically defined fields is only relevant at the top level, to ensure that a list of skills (for example) on one sheet maps to the equivalent list of skills on another, as a whole, not that any given entry in that list corresponds to a particular entry in another.

    In the 1.0 version of my sheet libraries I used many numbered fields to store dynamic list entries, but I switched over to the single field approach in the 2.0 version in part for this reason. It's more viable for a well structured JSON blob to be shared by a variety of script implementations, since it only requires a basic understanding of a self-evident schema, and doesn't require those scripts to manage the structure of the data itself or assume any correspondence between numbered fields representing different data fields within a more complex dynamic entry such as a skill name and rating pair or multiple properties of an attack table entry (as implied in a naming-convention based approach).

    Beyond this, I think it's worth keeping in mind that while common conventions are good as a matter of form, in practice this use case is rarely relevant. Few systems have multiple sheets by multiple authors, and where they do it's common for later authors to template their work on the naming conventions established by earlier ones - particularly as there is frequently room for ambiguity within the convention even for entirely static fields, let alone dynamic ones.

    With that in mind, I think it's more valuable for you (or any individual author) to use a convention and approach that's comfortable and helps you be productive than to impose a global convention.

    Post edited by ChainsawXIV on
Sign In or Register to comment.

June 2022
Baldur's Gate

Read the feature post on the blog
Return to Obsidian Portal

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!