Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serializer plugin not working as expected #930

Closed
tilersmyth opened this issue Dec 27, 2018 · 5 comments · Fixed by #936
Closed

Serializer plugin not working as expected #930

tilersmyth opened this issue Dec 27, 2018 · 5 comments · Fixed by #936
Labels
bug Functionality does not match expectation

Comments

@tilersmyth
Copy link

tilersmyth commented Dec 27, 2018

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!

@aciccarello aciccarello added the question Question about functionality label Dec 27, 2018
@Gerrit0
Copy link
Collaborator

Gerrit0 commented Dec 27, 2018

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.

@tilersmyth
Copy link
Author

Found this issue. It came down to v.symbol === component.serializeGroupSymbol in the below always returning falsey...

match = Array.from(this.router.values()).find( v => v.symbol === component.serializeGroupSymbol)

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.

@Gerrit0
Copy link
Collaborator

Gerrit0 commented Jan 2, 2019

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;
  }
}

@Gerrit0
Copy link
Collaborator

Gerrit0 commented Jan 2, 2019

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.

@Gerrit0 Gerrit0 added bug Functionality does not match expectation and removed question Question about functionality labels Dec 30, 2019
@Gerrit0
Copy link
Collaborator

Gerrit0 commented Jan 12, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Functionality does not match expectation
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants