Code review comment: Instead of passing in this emptyMap(), let's create a ConfigContext and pass it in here. This map should be constructed inside the JsonRenderer.

Code under review:
public SamzaPipelineResult run(Pipeline pipeline) {
    
    
    if (!ExperimentalOptions.hasExperiment(pipeline.getOptions(), "beam_fn_api")) {
      SplittableParDo.convertReadBasedSplittableDoFnsToPrimitiveReadsIfNecessary(pipeline);
    }

    MetricsEnvironment.setMetricsSupported(true);

    if (LOG.isDebugEnabled()) {
      LOG.debug(
          "Pre-processed Beam pipeline in dot format:\n{}",
          PipelineDotRenderer.toDotString(pipeline));
      LOG.debug(
          "Pre-processed Beam pipeline in json format:\n{}",
          PipelineJsonRenderer.toJsonString(pipeline, Collections.emptyMap()));
    }

    pipeline.replaceAll(SamzaTransformOverrides.getDefaultOverrides());

    final Map<PValue, String> idMap = PViewToIdMapper.buildIdMap(pipeline);
    final Set<String> nonUniqueStateIds = StateIdParser.scan(pipeline);
    final Map<String, Map.Entry<String, String>> transformIOMap =
        SamzaPipelineTranslator.buildTransformIOMap(pipeline, options, idMap, nonUniqueStateIds);

    final String dotGraph = PipelineDotRenderer.toDotString(pipeline);
    LOG.info("Beam pipeline DOT graph:\n{}", dotGraph);

    final String jsonGraph = PipelineJsonRenderer.toJsonString(pipeline, transformIOMap);
    LOG.info("Beam pipeline JSON graph:\n{}", jsonGraph);

    final ConfigBuilder configBuilder = new ConfigBuilder(options);
    SamzaPipelineTranslator.createConfig(
        pipeline, options, idMap, nonUniqueStateIds, configBuilder);
    configBuilder.put(BEAM_DOT_GRAPH, dotGraph);
    configBuilder.put(BEAM_JSON_GRAPH, jsonGraph);

    final Config config = configBuilder.build();
    options.setConfigOverride(config);

    if (listener != null) {
      listener.onInit(config, options);
    }

    final SamzaExecutionContext executionContext = new SamzaExecutionContext(options);
    final Map<String, MetricsReporterFactory> reporterFactories = getMetricsReporters();

    final StreamApplication app =
        appDescriptor -> {
          appDescriptor.withApplicationContainerContextFactory(executionContext.new Factory());
          appDescriptor.withMetricsReporterFactories(reporterFactories);

          SamzaPipelineTranslator.translate(
              pipeline, new TranslationContext(appDescriptor, idMap, nonUniqueStateIds, options));
        };

    
    
    SamzaPipelineOptionsValidator.validate(options);
    ApplicationRunner runner = runSamzaApp(app, config);
    return new SamzaPipelineResult(runner, executionContext, listener, config);
  }

Line commented on: PipelineJsonRenderer.toJsonString(pipeline, Collections.emptyMap()));

Revised code:
public SamzaPipelineResult run(Pipeline pipeline) {
    
    
    if (!ExperimentalOptions.hasExperiment(pipeline.getOptions(), "beam_fn_api")) {
      SplittableParDo.convertReadBasedSplittableDoFnsToPrimitiveReadsIfNecessary(pipeline);
    }

    MetricsEnvironment.setMetricsSupported(true);

    if (LOG.isDebugEnabled()) {
      LOG.debug(
          "Pre-processed Beam pipeline in dot format:\n{}",
          PipelineDotRenderer.toDotString(pipeline));
    }

    pipeline.replaceAll(SamzaTransformOverrides.getDefaultOverrides());
    final Map<PValue, String> idMap = PViewToIdMapper.buildIdMap(pipeline);
    final Set<String> nonUniqueStateIds = StateIdParser.scan(pipeline);
    final ConfigContext configCtx = new ConfigContext(idMap, nonUniqueStateIds, options);

    final String dotGraph = PipelineDotRenderer.toDotString(pipeline);
    LOG.info("Beam pipeline DOT graph:\n{}", dotGraph);

    final String jsonGraph = PipelineJsonRenderer.toJsonString(pipeline, configCtx);
    LOG.info("Beam pipeline JSON graph:\n{}", jsonGraph);

    final ConfigBuilder configBuilder = new ConfigBuilder(options);
    SamzaPipelineTranslator.createConfig(pipeline, configCtx, configBuilder);
    configBuilder.put(BEAM_DOT_GRAPH, dotGraph);
    configBuilder.put(BEAM_JSON_GRAPH, jsonGraph);

    final Config config = configBuilder.build();
    options.setConfigOverride(config);

    if (listener != null) {
      listener.onInit(config, options);
    }

    final SamzaExecutionContext executionContext = new SamzaExecutionContext(options);
    final Map<String, MetricsReporterFactory> reporterFactories = getMetricsReporters();

    final StreamApplication app =
        appDescriptor -> {
          appDescriptor.withApplicationContainerContextFactory(executionContext.new Factory());
          appDescriptor.withMetricsReporterFactories(reporterFactories);

          SamzaPipelineTranslator.translate(
              pipeline, new TranslationContext(appDescriptor, idMap, nonUniqueStateIds, options));
        };

    
    
    SamzaPipelineOptionsValidator.validate(options);
    ApplicationRunner runner = runSamzaApp(app, config);
    return new SamzaPipelineResult(runner, executionContext, listener, config);
  }