Typedoc: Serializer plugin not working as expected

Created on 27 Dec 2018  路  5Comments  路  Source: TypeStrong/typedoc

I could be approaching this incorrectly, but I have been tirelessly trying to get this to work to no avail. My aim is to add a custom key/value to the generateJson output schema. I started by adding the key to the reflection using a converter plugin on the Converter.EVENT_CREATE_DECLARATION event. See below for simplified version:

declare class decReflection extends Reflection {
  test: string;
}

@Component({ name: "test-plugin" })
export class TestPlugin extends ConverterComponent {


  initialize() {
    this.listenTo(this.owner, {
      [Converter.EVENT_CREATE_DECLARATION]: this.onDeclaration,
    });
  }

  private onDeclaration(
    context: Context,
    reflection: decReflection,
    node?: any
  ) {
    if (!node) {
      return;
    }

    reflection.test = 'test value';
  }
}

It wasn't until I noticed that the output did not contain my custom key/value that, by process of elimination, I narrowed the problem down to serialization, which (apparently) determines the output schema.

I feel like I am doing everything correctly when creating the serialization plugin, starting with adding the component. It seems as though for whatever reason the support function is not getting processed as it is advertised in the /lib/serialization/components.ts file.

Like [[Converter]] plugins each [[Serializer]] plugin defines a predicate that instructs if an
object can be serialized by it, this is done dynamically at runtime via a supports method.

So here is my attempt at getting the serializer plugin to work, starting with the index file which adds the component:

module.exports = function(PluginHost: any) {
  var app = PluginHost.owner;

  app.converter.addComponent("test-plugin", TestPlugin);
  app.serializer.addComponent("serializer:test-plugin", TestPluginSerializer);
};

then the serializer plugin class:

@Component({ name: "serializer:test-plugin" })
export class TestPluginSerializer extends SerializerComponent<TestReflection> {
  static PRIORITY = 1000;

  serializeGroup = (instance: unknown): boolean => {
    return instance instanceof TestReflection;
  };

  supports = (t: unknown) => {
    return true;
  };

  serializeGroupSymbol = TestReflection;

  toObject(testReflection: TestReflection, obj?: any): any {
    obj = obj || {};

    if (testReflection.test) {
      obj.test = testReflection.test;
    }

    return obj;
  }
}

After much digging, I can see that the serializer gets added as a component but for whatever reason the support function is not called.

Again, I am simply trying to add a key/value to the json schema generated by the generateJson method. I could be approaching this completely wrong, but if the serializer plugin is required then it is a lot less intuitive than it needs to be or something is buggy.

Any insight/help would be appreciated!

bug

All 5 comments

I believe this is an issue with serialization not being correctly used by TypeDoc. While debugging another problem a month or so ago I noticed that serializers sometimes aren't called, and we are still using the .toObject methods on reflections instead. I haven't had the time to go back and figure out what went wrong. It looks to me that your code should work as expected.

Found this issue. It came down to v.symbol === component.serializeGroupSymbol in the below always returning falsey...
https://github.com/TypeStrong/typedoc/blob/1c4c5151cec938ef03cae79c33c12be8d587ea74/src/lib/serialization/serializer.ts#L39

The intention is simply to check if the serialize group already exists so the plugin can be added to it. I fixed with v.symbol.toString() === component.serializeGroupSymbol.toString() in PR #932.

I haven't tested a custom serializer yet (on my list for tomorrow), but I spent a few hours today going through the serialization code to get rid of any and (hopefully) make it easier to follow. The fix/serializer-plugin branch has my progress so far.

In particular for this issue, I determined that the serializeGroupSymbol was unnecessary (all functionality could be preserved by just checking serializeGroup). This should make it simpler to create a serializer.

I also noticed that you are checking for TestReflection in your serializer plugin. This reflection isn't present in TypeDoc & thus isn't serialized by default, so some other serializer would need to start the serialization for it.

To add a key/value to all reflection objects, you should be able to do something like this (untested, tomorrow):

@Component({ name: "serializer:test-plugin" })
export class TestPluginSerializer extends SerializerComponent<Reflection> {
  static PRIORITY = 1000;

  serializeGroup = (instance: unknown): boolean => {
    return instance instanceof Reflection;
  };

  supports = (t: unknown) => {
    return true;
  };

  toObject(testReflection: TestReflection, obj?: any): any {
    obj = obj || {};
    obj.test = testReflection.test;

    return obj;
  }
}

Well, it took a bit more poking around than I expected, but I was able to confirm that the above code works (and can be changed a bit to make it more type safe)

This experience has convinced me that we should be publishing the dist/lib directory, not ., but I think that change might need to wait until the Component hierarchy has been removed.

import { Reflection, Application } from 'typedoc'
import { JSONOutput } from 'typedoc/dist/lib/serialization/schema'
import { Component } from 'typedoc/dist/lib/utils'
import { SerializerComponent } from 'typedoc/dist/lib/serialization'

declare module 'typedoc/dist/lib/serialization/schema' {
    export namespace JSONOutput {
        export interface Reflection {
            test: boolean;
        }
    }
}

@Component({ name: 'serializer:test-plugin' })
class TestPluginSerializer extends SerializerComponent<Reflection> {
    static PRIORITY = 1000;

    serializeGroup(instance: unknown): boolean {
        return instance instanceof Reflection;
    };

    supports(_t: unknown) {
        return true;
    };

    toObject(
        _reflection: Reflection,
        obj?: Partial<JSONOutput.Reflection>
    ): Partial<JSONOutput.Reflection> {
        return {
            ...obj,
            test: true
        }
    }
}

export = function(host: Application['plugins']) {
    host.application.serializer.addComponent('serializer:test-plugin', new TestPluginSerializer(host.application.serializer));
}

I'll submit a PR with the serialization updates soon for another pair of eyes and we can hopefully get these changes into the next release.

Well this took far too long to get merged, but it is now merged, and will be included in v0.16.0.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Bibliofile picture Bibliofile  路  3Comments

woppa684 picture woppa684  路  3Comments

nidsharm picture nidsharm  路  3Comments

atomsoftwarestudios picture atomsoftwarestudios  路  4Comments

unsafecode picture unsafecode  路  4Comments