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

Custom mappings for @JsonSerialize #498

Closed
andrewbents opened this issue Jun 30, 2020 · 8 comments
Closed

Custom mappings for @JsonSerialize #498

andrewbents opened this issue Jun 30, 2020 · 8 comments
Labels
Milestone

Comments

@andrewbents
Copy link

I checked #47 and #60 but still can't figure out a solution for the following problem.

I have a class where a field is annotated with @JsonSerialize(using = IdSerializer.class):

// excerpt
public class Contract {
    @JsonSerialize(using = IdSerializer.class)
    private Project project;
}

which results in

{
  "project": { "id": "someId" }
}

However, the generated type for that field is still Project. Is there an existing way to map types for fields annotated in such way? Note that Project is used elsewhere, so I can't use customTypeMappings here to map it entirely

@jdussouillez
Copy link

jdussouillez commented Jul 1, 2020

I would like to have this feature as well. It would be even better if you could register Jackson serializers directly in the configuration (sadly I guess it's very difficult/impossible to implement)

Use case : I have an API project where I register a custom objet mapper used by Jersey when it (de)serializes data between my API and my frontend.

I register it using this :

@Provider
public class JsonMapperProvider implements ContextResolver<ObjectMapper> {

    @Override
    public ObjectMapper getContext(final Class<?> type) {
        return new ObjectMapper()
            // Register other modules
            .registerModule(getLocalDateTimeModule());
    }

    private static SimpleModule getLocalDateTimeModule() {
        var module = new SimpleModule(
            "LocalDateTime",
            Version.unknownVersion()
        );
        module.addSerializer(new LocalDateTimeJsonSerializer()); // LocalDateTimeJsonSerializer extends StdSerializer
        module.addDeserializer(LocalDateTime.class, new LocalDateTimeJsonDeserializer()); // LocalDateTimeJsonDeserializer extends StdDeserializer
        return module;
    }
}

Instead of mapping the type manually in the configuration, I could register the serializers to do the job (advantage : no differences between TS generated types and generated JSON)

vojtechhabarta added a commit that referenced this issue Jul 20, 2020
- `serializerTypeMappings` and `deserializerTypeMappings` parameters
@vojtechhabarta
Copy link
Owner

@jdussouillez there are two problems: getting list of serializers and getting TypeScript type they produce. I looked into it and I don't think this is reasonable.

@andrewbents
I implemented possibility to map serializers and deserializers used on properties to TypeScript type. New configuration parameters serializerTypeMappings and deserializerTypeMappings are inside jackson2Configuration because this is specific for Jackson library.

Here is Maven example for your case:

<configuration>
    <jsonLibrary>jackson2</jsonLibrary>
    <jackson2Configuration>
        <serializerTypeMappings>
            <mapping>org.example.IdSerializer:{ id: string }</mapping>
        </serializerTypeMappings>
    </jackson2Configuration>
    ...
</configuration>

@jdussouillez
Copy link

I looked into it and I don't think this is reasonable.

Yeah I knew it would be very difficult to do. It's not a big deal, I will use the serializerTypeMappings option instead. Thanks for the new feature !

@jdussouillez
Copy link

@vojtechhabarta Do you have a release date for this ? I would like to use this option in my project.

Thanks

@vojtechhabarta vojtechhabarta added this to the 2.25 milestone Aug 5, 2020
@vojtechhabarta
Copy link
Owner

@jdussouillez sorry for late response, good news is that it is released in v2.25.695.

@pcdv
Copy link

pcdv commented Aug 14, 2020

Any idea how the proposed jackson2Configuration configuration would translate to a Gradle build?

Thanks!

EDIT: found it, I did something like:

  jackson2Configuration = [
          serializerTypeMappings: [
                  "com.foo.ArgSerializer:string"
          ]
  ]

@vojtechhabarta
Copy link
Owner

@pcdv thanks for sharing your solution

@richturner
Copy link
Contributor

Although applying @JsonSerialize(using=...) is not a possibility supporting @JsonSerialize(as=...) and @JsonSerialize(converter=...) can be; I needed this for fields of my bean classes so implemented a custom extension but could be better integrated with class level annotation support, here's the custom extension:

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.util.Converter;
import cz.habarta.typescript.generator.Extension;
import cz.habarta.typescript.generator.compiler.ModelCompiler;
import cz.habarta.typescript.generator.compiler.ModelTransformer;
import cz.habarta.typescript.generator.compiler.SymbolTable;
import cz.habarta.typescript.generator.emitter.EmitterExtensionFeatures;
import cz.habarta.typescript.generator.emitter.TsModel;
import cz.habarta.typescript.generator.parser.BeanModel;
import cz.habarta.typescript.generator.parser.Model;
import cz.habarta.typescript.generator.parser.PropertyModel;

import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * Extension for applying {@link JsonSerialize} annotation. Supports:
 * <ul>
 * <li>{@link JsonSerialize#as}</li>
 * <li>{@link JsonSerialize#converter}</li>
 * </ul>
 */
public class CustomExtension extends Extension {

    @Override
    public EmitterExtensionFeatures getFeatures() {
        return new EmitterExtensionFeatures();
    }

    @Override
    public List<TransformerDefinition> getTransformers() {
        return Arrays.asList(
            new TransformerDefinition(ModelCompiler.TransformationPhase.BeforeTsModel, new ModelTransformer() {
                @Override
                public TsModel transformModel(SymbolTable symbolTable, TsModel model) {
                    return model;
                }

                @Override
                public Model transformModel(SymbolTable symbolTable, Model model) {
                    List<BeanModel> beans = model.getBeans();
                    beans.replaceAll(bean -> {
                        AtomicBoolean modified = new AtomicBoolean(false);

                        // Look for @JsonSerialize annotation and modify the property type accordingly
                        List<PropertyModel> properties = bean.getProperties().stream().map(p -> {
                            Member member = p.getOriginalMember();
                            JsonSerialize jsonSerialize = null;

                            if (member instanceof Field) {
                                Field field = (Field)member;
                                jsonSerialize = field.getAnnotation(JsonSerialize.class);
                            } else if (member instanceof Method) {
                                Method method = (Method)member;
                                jsonSerialize = method.getAnnotation(JsonSerialize.class);
                            }

                            if (jsonSerialize != null) {
                                // TODO: Add support for other options
                                if (jsonSerialize.as() != Void.class) {
                                    modified.set(true);
                                    return new PropertyModel(p.getName(), jsonSerialize.as(), p.isOptional(), p.getAccess(), p.getOriginalMember(), p.getPullProperties(), p.getContext(), p.getComments());
                                }
                                if (jsonSerialize.converter() != Converter.None.class) {
                                    // Type info is not accessible with reflection so instantiate the converter
                                    Method convertMethod = Arrays.stream(jsonSerialize.converter().getMethods()).filter(m -> m.getName().equals("convert")).findFirst().orElse(null);
                                    if (convertMethod != null) {
                                        modified.set(true);
                                        return new PropertyModel(p.getName(), convertMethod.getGenericReturnType(), p.isOptional(), p.getAccess(), p.getOriginalMember(), p.getPullProperties(), p.getContext(), p.getComments());
                                    }
                                }
                            }

                            return p;
                        }).collect(Collectors.toList());

                        if (modified.get()) {
                            return new BeanModel(bean.getOrigin(), bean.getParent(), bean.getTaggedUnionClasses(), bean.getDiscriminantProperty(), bean.getDiscriminantLiteral(), bean.getInterfaces(), properties, bean.getComments());
                        }

                        return bean;
                    });

                    return model;
                }
            })
        );
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants