Stop storing JSON blobs in XB config entities: improve DX

Created on 25 April 2025, 17 days ago

Overview

Since day 1, it has bothered me that XB's config entities store component trees as JSON blobs. For example, the auto-generated (based on Standard install profile's Block config entities) "Header" PageRegion config entity:

uuid: fefb6b93-d378-49bc-a005-815674e6ca36
langcode: en
status: true
dependencies:
  config:
    - experience_builder.component.block.system_branding_block
    - experience_builder.component.block.views_block.comments_recent-block_1
  theme:
    - olivero
id: olivero.header
region: header
theme: olivero
component_tree:
  tree: '{"a548b48d-58a8-4077-aa04-da9405a6f418":{"0":{"component":"block.system_branding_block","uuid":"3fec9bc4-dc58-42a6-85af-b92268338c3b"},"1":{"component":"block.views_block.comments_recent-block_1","uuid":"870e9006-2d93-4ac2-9294-192d4f6ee724"}}}'
  inputs: '{"3fec9bc4-dc58-42a6-85af-b92268338c3b":{"label":"Site branding","label_display":"0","use_site_logo":true,"use_site_name":true,"use_site_slogan":false},"870e9006-2d93-4ac2-9294-192d4f6ee724":{"label":"asdfsdfsdf","label_display":"visible","views_label":"asdfsdfsdf","items_per_page":"none"}}'

It's not just "ugly" for developers though, it makes certain use cases much more painful!

For example: using config actions β†’ in Recipes to manipulate XB config entities.

If that was not a single JSON blob but a proper tree structure, then it'd be much easier to use config actions!

Proposed resolution

TBD. Ideas:

  1. Use ConfigEvents::STORAGE_TRANSFORM_(IM|EX)PORT to transform the at-rest representation. But will that work with config actions? πŸ€”
    (See the PoC linked from #3460232-7: Support XB config entities with component trees that reference File/Media, use target_uuid and embed β€” aka: self-contained content entity dependencies β†’ for an example of what you can do with it.)
  2. We succeeded in (largely) modeling/validating JSON schema structure using config schema validation in πŸ› js_component examples and enum do not respect the prop type Active . See config/schema/experience_builder.json_schema.yml. Can we do something similar here?
  3. Something else!?

User interface changes

πŸ“Œ Task
Status

Active

Version

0.0

Component

Config management

Created by

πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @wim leers
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    Clarified the goal.

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί
  • πŸ‡¬πŸ‡§United Kingdom catch

    Thank you so much for opening this. Translation from JSON to YAML and back seems hard, but incredibly worth it if the config entities can be treated as normal config entities.

  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    I don't think this will affect config actions much, either way. They are severely limited in their ability to manipulate complex arrays, and regardless of how the data is stored at rest, we'll need a slew of custom config actions to make XB useful to recipes.

    But it'd certainly improve DX to have the tree be a YAML structure. I personally don't see any reason why it needs to be a JSON blob, since YAML is a superset of JSON. I don't understand why it would be hard to move back and forth between JSON and YAML if we wanted to -- if you can decode some YAML to a PHP array, you can re-encode it as JSON.

  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    I asked @effulgentsia if there was any particular reason why the XB trees are stored as JSON blobs. His answer on Slack:

    I'm not aware of any good reason to keep them as JSON blobs. I think the only reason we didn't embed them as a proper structure within the config to begin with is just at that time not wanting to deal with config schema and validation. But now is the time to deal with that. In that issue.

  • Merge request !959Convert `inputs` to an array β†’ (Merged) created by phenaproxima
  • Pipeline finished with Failed
    14 days ago
    Total: 622s
    #484171
  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    I think that, to not break everything everywhere all at once, we might want to do this in multiple issues (first deal with inputs, then tree in another issue).

    And I'm also thinking that, until πŸ“Œ Allow field types to control how properties are mapped to and from storage Needs work is done, we might want to have the ComponentTreeStructure and ComponentInputs data types handle both arrays and JSON blobs. That would ease the difficulty of the transition without blocking us, it looks like.

  • Pipeline finished with Failed
    14 days ago
    #484188
  • Pipeline finished with Failed
    13 days ago
    Total: 648s
    #484771
  • Pipeline finished with Failed
    13 days ago
    Total: 640s
    #484794
  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    Okay, I made inputs a standard array -- does this look more or less correct? (I know that ignore is the wrong config schema type, but the tree and its inputs are validated as a whole, so I'm not sure it needs to be anything more specific).

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    A few questions, but this definitely looks to be on the right way! πŸ‘

    Next up:

    • also tackling tree, not just inputs
    • triggering a deprecation error for a JSON blobs for config entities (allows smooth upgrade, but ensures we as XB developers area ware)

    Actually, hard disallowing seems fine given we're in alpha. Thoughts? :)

  • Pipeline finished with Failed
    13 days ago
    Total: 867s
    #484963
  • Pipeline finished with Failed
    13 days ago
    Total: 640s
    #485007
  • Pipeline finished with Canceled
    13 days ago
    Total: 165s
    #485032
  • Pipeline finished with Canceled
    13 days ago
    Total: 484s
    #485035
  • Pipeline finished with Canceled
    13 days ago
    Total: 698s
    #485043
  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    OK, I think I mostly have tree using regular arrays now, too.

    The lone exception, and the reason the BC layer that converts between strings and arrays is needed, is that field items on content entities won't be storable as JSON transparently until πŸ“Œ Allow field types to control how properties are mapped to and from storage Needs work , so until then, we probably need to keep supporting strings of JSON. But we definitely don't seem to need those for storing trees in config.

  • Pipeline finished with Failed
    13 days ago
    Total: 815s
    #485049
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    I'm shocked by how simple this turned out to be πŸ€―πŸ™ƒ

    Awesome work! πŸ‘

    Two things remain:

    1. removing as many ::encodeXBData() calls as possible
    2. #10's second bullet β€” which will reveal all ::encodeXBData() calls that MUST be removed

    I know that this is kinda boring, but … it makes the entire codebase easier to work in, and keeps the entire codebase consistent. The MR in its current state removes some ::encodeXBData() calls, but on my first try I got tests to pass with more of those calls removed. So if this MR doesn't make things as consistent as they are in HEAD, it's going to be confusing for everybody else: "when do we need this ::encodeXBData() thing vs not?".

  • Pipeline finished with Success
    12 days ago
    Total: 658s
    #485472
  • Pipeline finished with Canceled
    12 days ago
    #485775
  • Pipeline finished with Failed
    12 days ago
    #485786
  • Pipeline finished with Failed
    12 days ago
    #485904
  • Pipeline finished with Failed
    12 days ago
    #485937
  • Pipeline finished with Canceled
    12 days ago
    #485946
  • Pipeline finished with Running
    12 days ago
    #485970
  • Pipeline finished with Canceled
    12 days ago
    #485962
  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    I've removed as many json_encode() calls as I could find. I don't think we should remove the BC layer yet because when the value is pulled from the database as a JSON string (when loading the fields), the data type plugins need to be able to handle that...right?

  • Pipeline finished with Running
    12 days ago
    #486054
  • πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

    Update: FieldTypeUninstallValidatorTest now passes everywhere except on MariaDB. I imagine this is not a blocker, but worth calling out.

  • Pipeline finished with Failed
    12 days ago
    Total: 2766s
    #486092
  • Pipeline finished with Failed
    11 days ago
    Total: 587s
    #486788
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί
  • Pipeline finished with Failed
    3 days ago
    Total: 577s
    #493003
  • Pipeline finished with Success
    3 days ago
    Total: 704s
    #493012
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί
  • Pipeline finished with Failed
    3 days ago
    Total: 906s
    #493031
  • Pipeline finished with Success
    3 days ago
    Total: 638s
    #493050
  • Pipeline finished with Failed
    3 days ago
    Total: 814s
    #493210
  • Pipeline finished with Failed
    3 days ago
    Total: 780s
    #493254
  • Pipeline finished with Canceled
    3 days ago
    Total: 587s
    #493271
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    As a byproduct, the TestDataUtilitiesTrait that @tedbow introduced in πŸ“Œ Convert test cases to use PHP arrays instead of JSON strings where possible Fixed to ease test development pain during initial development of XB, will largely be obsolete β€” that's what I asked for in #13, and which @phenaproxima almost completely finished! 🀩

    In fact … since I'm feeling under the weather and my mind doesn't allow me to tackle anything complex right now, I just persevered and refactored it away: TestDataUtilitiesTrait is GONE! No more need to remember to wrap an array in a ::encodeXBData()! πŸ₯³ All thanks to the one crucial insight by @phenaproxima! πŸ‘

    So: we'll not only make interacting with XB config less painful for site builders/XB users, but also for us developing XB, and hence improve development velocity slightly :)

    P.S.: I'd still don't like that ->getValue(), TRUE has 7 matches in the codebase, but that's far better than the hundreds this MR removed. So, for the sake of iterative progress, didn't expand scope to that. Also: once πŸ“Œ Allow field types to control how properties are mapped to and from storage Needs work lands, XB won't have to work around that flaw in Drupal core anymore. If somebody wants to take that on: see attached PoC patch to get you going.

  • Pipeline finished with Success
    3 days ago
    Total: 842s
    #493280
  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί
  • Pipeline finished with Success
    3 days ago
    Total: 799s
    #493289
  • Pipeline finished with Skipped
    3 days ago
    #493300
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    FYI: with this done, I think it's time to re-assess πŸ“Œ ServerClientConversionTrait Active and friends, aka make a decision on whether to keep ClientServerConversionTrait because it is good enough, or whether to transition to something else/better.

Production build 0.71.5 2024