Skip to content

[record_use] Implementation: Organize the normalization in semantic/syntax mapping #3106

Closed

Description

@dcharkes

We currently have two phases in serialization and deserialization:

  1. constants
  2. recordings

However, we are going to add more phases:

  1. loading units ([record_use] Dart API: Loading units as object/class #2979)
  2. definitions ([record_use] Correctness: Support nested const instances #2867)
  3. constants (will require definitions after the above change)
  4. recordings (requires all 3 above)

In order to keep the code clean we should explicitly document/implement the order of serialization/deserialization. Currently the List<Constant> (map int->constant) for deserialization and Map<Constant, int> for serialization are passed around explicitly. This will become a mess if we get 3 context params threaded through the serialization/deserialization.

(N.b., I explored adding references to the json_syntax_generator but that would make the json syntax context-dependent instead of context-free leading to having that layer having to deal with deduplication of objects etc. That seems to be the wrong layer of abstraction.)

Sketch of how it could work:

// Copyright (c) 2026, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

/// This library defines the context objects used for progressive serialization
/// and deserialization of normalized JSON.
///
/// ### Layer Responsibilities
///
/// The **Syntax Layer** is designed to be context-free, acting as a thin wrapper
/// around JSON data. This aligns with the capabilities of standard JSON Schema,
/// which excels at structural validation but does not natively express
/// relational dependencies between disparate branches of a tree.
///
/// The **Semantic Layer** is responsible for identity and relationship
/// resolution. It manages these [DeserializationContext] and
/// [SerializationContext] objects to "thread" the reference pools through the
/// resolution pipeline, ensuring that normalized indices are correctly mapped
/// to (and from) deduplicated semantic objects.
///
/// ### Resolution Order
///
/// The resolution order is identical for both serialization and
/// deserialization, ensuring that all dependencies are available at each step:
///
/// 1. **Loading Units**: Context-free.
/// 2. **Definitions**: Resolved second (available for Constants).
/// 3. **Constants**: Resolved third. May contain references to the
///    Definitions pool.
/// 4. **Recordings**: Resolved last. References Loading Units, Definitions,
///    and Constants.
library;

import 'constant.dart';
import 'definition.dart';
import 'recordings.dart';
import 'reference.dart';

// Note: LoadingUnit is currently represented as a String in the API,
// but will be moved to an object in future iterations.
// For now, we use a placeholder or the existing type.
typedef LoadingUnit = String;

/// Initial state of deserialization where no pools have been resolved.
///
/// This serves as the base class for all deserialization contexts.
base class DeserializationContext {
  const DeserializationContext();
}

/// Context providing access to the resolved [LoadingUnit] pool.
base class LoadingUnitDeserializationContext extends DeserializationContext {
  final List<LoadingUnit> loadingUnits;

  const LoadingUnitDeserializationContext({required this.loadingUnits});
}

/// Context providing access to both [LoadingUnit] and [Definition] pools.
base class DefinitionDeserializationContext
    extends LoadingUnitDeserializationContext {
  final List<Definition> definitions;

  const DefinitionDeserializationContext({
    required super.loadingUnits,
    required this.definitions,
  });

  factory DefinitionDeserializationContext.fromLoadingUnits(
    LoadingUnitDeserializationContext prev,
    List<Definition> definitions,
  ) =>
      DefinitionDeserializationContext(
        loadingUnits: prev.loadingUnits,
        definitions: definitions,
      );
}

/// The final deserialization state where all pools are resolved.
///
/// This context is used to resolve the final [Recordings] and their
/// [Reference]s.
final class FullDeserializationContext
    extends DefinitionDeserializationContext {
  final List<Constant> constants;

  const FullDeserializationContext({
    required super.loadingUnits,
    required super.definitions,
    required this.constants,
  });

  factory FullDeserializationContext.fromDefinitions(
    DefinitionDeserializationContext prev,
    List<Constant> constants,
  ) =>
      FullDeserializationContext(
        loadingUnits: prev.loadingUnits,
        definitions: prev.definitions,
        constants: constants,
      );
}

/// Initial state for serialization before any maps have been built.
///
/// This serves as the base class for all serialization contexts.
base class SerializationContext {
  const SerializationContext();
}

/// Context providing access to the full [LoadingUnit] index map.
base class LoadingUnitSerializationContext extends SerializationContext {
  final Map<LoadingUnit, int> loadingUnits;

  const LoadingUnitSerializationContext({required this.loadingUnits});
}

/// Context providing access to both [LoadingUnit] and [Definition] index maps.
base class DefinitionSerializationContext
    extends LoadingUnitSerializationContext {
  final Map<Definition, int> definitions;

  const DefinitionSerializationContext({
    required super.loadingUnits,
    required this.definitions,
  });

  factory DefinitionSerializationContext.fromLoadingUnits(
    LoadingUnitSerializationContext prev,
    Map<Definition, int> definitions,
  ) =>
      DefinitionSerializationContext(
        loadingUnits: prev.loadingUnits,
        definitions: definitions,
      );
}

/// The final serialization state where all index maps are available.
///
/// This context is used to convert the final [Recordings] and their
/// [Reference]s to syntax.
final class FullSerializationContext extends DefinitionSerializationContext {
  final Map<Constant, int> constants;

  const FullSerializationContext({
    required super.loadingUnits,
    required super.definitions,
    required this.constants,
  });

  factory FullSerializationContext.fromDefinitions(
    DefinitionSerializationContext prev,
    Map<Constant, int> constants,
  ) =>
      FullSerializationContext(
        loadingUnits: prev.loadingUnits,
        definitions: prev.definitions,
        constants: constants,
      );
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

Relationships

None yet

Development

No branches or pull requests

Issue actions