Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

package cz.habarta.typescript.generator;


public enum OptionalProperties {
useSpecifiedAnnotations, useLibraryDefinition, all
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

package cz.habarta.typescript.generator;

import com.fasterxml.jackson.databind.Module;
import cz.habarta.typescript.generator.emitter.Emitter;
import cz.habarta.typescript.generator.emitter.EmitterExtension;
import cz.habarta.typescript.generator.emitter.EmitterExtensionFeatures;
Expand Down Expand Up @@ -30,7 +31,8 @@ public class Settings {
public String umdNamespace = null;
public JsonLibrary jsonLibrary = null;
private Predicate<String> excludeFilter = null;
public boolean declarePropertiesAsOptional = false;
@Deprecated public boolean declarePropertiesAsOptional = false;
public OptionalProperties optionalProperties; // default is OptionalProperties.useSpecifiedAnnotations
public boolean declarePropertiesAsReadOnly = false;
public String removeTypeNamePrefix = null;
public String removeTypeNameSuffix = null;
Expand Down Expand Up @@ -69,7 +71,9 @@ public class Settings {
public Map<String, String> npmPackageDependencies = new LinkedHashMap<>();
public String typescriptVersion = "^2.4";
public boolean displaySerializerWarning = true;
public boolean disableJackson2ModuleDiscovery = false;
@Deprecated public boolean disableJackson2ModuleDiscovery = false;
public boolean jackson2ModuleDiscovery = false;
public List<Class<? extends Module>> jackson2Modules = new ArrayList<>();
public ClassLoader classLoader = null;

private boolean defaultStringEnumsOverriddenByExtension = false;
Expand Down Expand Up @@ -123,6 +127,12 @@ public void loadOptionalAnnotations(ClassLoader classLoader, List<String> option
}
}

public void loadJackson2Modules(ClassLoader classLoader, List<String> jackson2Modules) {
if (jackson2Modules != null) {
this.jackson2Modules = loadClasses(classLoader, jackson2Modules, Module.class);
}
}

public static Map<String, String> convertToMap(List<String> mappings) {
final Map<String, String> result = new LinkedHashMap<>();
if (mappings != null) {
Expand Down Expand Up @@ -235,6 +245,16 @@ public void validate() {
throw new RuntimeException("'npmName' and 'npmVersion' is only applicable when generating NPM 'package.json'.");
}
}

if (declarePropertiesAsOptional) {
System.out.println("Warning: Parameter 'declarePropertiesAsOptional' is deprecated. Use 'optionalProperties' parameter.");
if (optionalProperties == null) {
optionalProperties = OptionalProperties.all;
}
}
if (disableJackson2ModuleDiscovery) {
System.out.println("Warning: Parameter 'disableJackson2ModuleDiscovery' was removed. See 'jackson2ModuleDiscovery' and 'jackson2Modules' parameters.");
}
}

private static void reportConfigurationChange(String extensionName, String parameterName, String parameterValue) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ private void emitProperty(TsPropertyModel property) {
emitComments(property.getComments());
final TsType tsType = property.getTsType();
final String readonly = property.readonly ? "readonly " : "";
final String questionMark = settings.declarePropertiesAsOptional || (tsType instanceof TsType.OptionalType) ? "?" : "";
final String questionMark = tsType instanceof TsType.OptionalType ? "?" : "";
writeIndentedLine(readonly + quoteIfNeeded(property.getName(), settings) + questionMark + ": " + tsType.format(settings) + ";");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import cz.habarta.typescript.generator.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Member;
import java.lang.reflect.Type;
import java.util.*;
Expand Down Expand Up @@ -48,6 +49,8 @@ private BeanModel parseBean(SourceType<Class<?>> sourceClass) {
final BeanHelper beanHelper = getBeanHelper(sourceClass.type);
if (beanHelper != null) {
for (BeanPropertyWriter beanPropertyWriter : beanHelper.getProperties()) {
final Member propertyMember = beanPropertyWriter.getMember().getMember();
checkMember(propertyMember, beanPropertyWriter.getName(), sourceClass.type);
Type propertyType = beanPropertyWriter.getGenericPropertyType();
if (propertyType == JsonNode.class) {
propertyType = Object.class;
Expand All @@ -65,13 +68,7 @@ private BeanModel parseBean(SourceType<Class<?>> sourceClass) {
continue;
}
}
boolean optional = false;
for (Class<? extends Annotation> optionalAnnotation : settings.optionalAnnotations) {
if (beanPropertyWriter.getAnnotation(optionalAnnotation) != null) {
optional = true;
break;
}
}
final boolean optional = isAnnotatedPropertyOptional((AnnotatedElement) propertyMember);
final Member originalMember = beanPropertyWriter.getMember().getMember();
properties.add(processTypeAndCreateProperty(beanPropertyWriter.getName(), propertyType, optional, sourceClass.type, originalMember, null));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
Expand All @@ -38,9 +39,16 @@ public Jackson2Parser(Settings settings, TypeProcessor typeProcessor) {

public Jackson2Parser(Settings settings, TypeProcessor typeProcessor, boolean useJaxbAnnotations) {
super(settings, typeProcessor);
if (!settings.disableJackson2ModuleDiscovery) {
if (settings.jackson2ModuleDiscovery) {
objectMapper.registerModules(ObjectMapper.findModules(settings.classLoader));
}
for (Class<? extends Module> moduleClass : settings.jackson2Modules) {
try {
objectMapper.registerModule(moduleClass.newInstance());
} catch (ReflectiveOperationException e) {
throw new RuntimeException(String.format("Cannot instantiate Jackson2 module '%s'", moduleClass.getName()), e);
}
}
if (useJaxbAnnotations) {
AnnotationIntrospector introspector = new JaxbAnnotationIntrospector(objectMapper.getTypeFactory());
objectMapper.setAnnotationIntrospector(introspector);
Expand All @@ -63,6 +71,7 @@ private BeanModel parseBean(SourceType<Class<?>> sourceClass) {
if (beanHelper != null) {
for (BeanPropertyWriter beanPropertyWriter : beanHelper.getProperties()) {
final Member propertyMember = beanPropertyWriter.getMember().getMember();
checkMember(propertyMember, beanPropertyWriter.getName(), sourceClass.type);
Type propertyType = getGenericType(propertyMember);
if (propertyType == JsonNode.class) {
propertyType = Object.class;
Expand All @@ -80,13 +89,9 @@ private BeanModel parseBean(SourceType<Class<?>> sourceClass) {
continue;
}
}
boolean optional = false;
for (Class<? extends Annotation> optionalAnnotation : settings.optionalAnnotations) {
if (beanPropertyWriter.getAnnotation(optionalAnnotation) != null) {
optional = true;
break;
}
}
final boolean optional = settings.optionalProperties == OptionalProperties.useLibraryDefinition
? !beanPropertyWriter.isRequired()
: isAnnotatedPropertyOptional((AnnotatedElement) propertyMember);
// @JsonUnwrapped
PropertyModel.PullProperties pullProperties = null;
final Member originalMember = beanPropertyWriter.getMember().getMember();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
import cz.habarta.typescript.generator.compiler.EnumKind;
import cz.habarta.typescript.generator.compiler.SymbolTable;
import cz.habarta.typescript.generator.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;

Expand Down Expand Up @@ -79,6 +83,30 @@ private Model parseQueue() {

protected abstract DeclarationModel parseClass(SourceType<Class<?>> sourceClass);

protected static void checkMember(Member propertyMember, String propertyName, Class<?> sourceClass) {
if (!(propertyMember instanceof Field) && !(propertyMember instanceof Method)) {
throw new RuntimeException(String.format(
"Unexpected member type '%s' in property '%s' in class '%s'",
propertyMember != null ? propertyMember.getClass().getName() : null,
propertyName,
sourceClass.getName()));
}
}

protected boolean isAnnotatedPropertyOptional(AnnotatedElement annotatedProperty) {
if (settings.optionalProperties == OptionalProperties.all) {
return true;
}
if (settings.optionalProperties == null || settings.optionalProperties == OptionalProperties.useSpecifiedAnnotations) {
for (Class<? extends Annotation> optionalAnnotation : settings.optionalAnnotations) {
if (annotatedProperty.getAnnotation(optionalAnnotation) != null) {
return true;
}
}
}
return false;
}

protected static DeclarationModel parseEnum(SourceType<Class<?>> sourceClass) {
final List<EnumMemberModel> values = new ArrayList<>();
if (sourceClass.type.isEnum()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.xml.bind.annotation.XmlElement;
import org.junit.Assert;
import org.junit.Test;

Expand Down Expand Up @@ -109,4 +110,68 @@ public static void main(String[] args) throws JsonProcessingException {
System.out.println(new ObjectMapper().writeValueAsString(new SubTypeDiscriminatedByName4()));
}

@Test
public void testOptionalJsonProperty() {
final Settings settings = TestUtils.settings();
settings.optionalProperties = OptionalProperties.useLibraryDefinition;
final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(ClassWithOptionals.class));
Assert.assertTrue(output.contains("oname1?: string"));
// Assert.assertTrue(output.contains("oname2?: string")); // uncomment on Java 8
Assert.assertTrue(output.contains("jname1?: string"));
Assert.assertTrue(output.contains("jname2?: string"));
Assert.assertTrue(output.contains("jname3: string"));
Assert.assertTrue(output.contains("jname4: string"));
Assert.assertTrue(output.contains("xname1?: string"));
Assert.assertTrue(output.contains("xname2?: string"));
Assert.assertTrue(output.contains("xname3?: string"));
Assert.assertTrue(output.contains("xname4?: string"));
}

@Test
public void testOptionalXmlElement() {
final Settings settings = TestUtils.settings();
settings.jsonLibrary = JsonLibrary.jaxb;
settings.optionalProperties = OptionalProperties.useLibraryDefinition;
final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(ClassWithOptionals.class));
Assert.assertTrue(output.contains("oname1?: string"));
// Assert.assertTrue(output.contains("oname2?: string")); // uncomment on Java 8
Assert.assertTrue(output.contains("jname1?: string"));
Assert.assertTrue(output.contains("jname2?: string"));
Assert.assertTrue(output.contains("jname3?: string"));
Assert.assertTrue(output.contains("jname4?: string"));
Assert.assertTrue(output.contains("xname1?: string"));
Assert.assertTrue(output.contains("xname2?: string"));
Assert.assertTrue(output.contains("xname3: string"));
Assert.assertTrue(output.contains("xname4: string"));
}

public static class ClassWithOptionals {
public String oname1;
// public Optional<String> oname2; // uncomment on Java 8

@JsonProperty
public String jname1;
@JsonProperty(required = false)
public String jname2;
@JsonProperty(required = true)
public String jname3;
private String jname4;
@JsonProperty(required = true)
public String getJname4() {
return jname4;
}

@XmlElement
public String xname1;
@XmlElement(required = false)
public String xname2;
@XmlElement(required = true)
public String xname3;
private String xname4;
@XmlElement(required = true)
public String getXname4() {
return xname4;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public class GenerateTask extends DefaultTask {
public List<String> excludeClassPatterns;
public List<String> includePropertyAnnotations;
public JsonLibrary jsonLibrary;
public boolean declarePropertiesAsOptional;
@Deprecated public boolean declarePropertiesAsOptional;
public OptionalProperties optionalProperties;
public boolean declarePropertiesAsReadOnly;
public String removeTypeNamePrefix;
public String removeTypeNameSuffix;
Expand Down Expand Up @@ -62,7 +63,9 @@ public class GenerateTask extends DefaultTask {
public String npmVersion;
public StringQuotes stringQuotes;
public boolean displaySerializerWarning = true;
public boolean disableJackson2ModuleDiscovery;
@Deprecated public boolean disableJackson2ModuleDiscovery;
public boolean jackson2ModuleDiscovery;
public List<String> jackson2Modules;
public boolean debug;

@TaskAction
Expand Down Expand Up @@ -101,6 +104,7 @@ public void generate() throws Exception {
settings.setExcludeFilter(excludeClasses, excludeClassPatterns);
settings.jsonLibrary = jsonLibrary;
settings.declarePropertiesAsOptional = declarePropertiesAsOptional;
settings.optionalProperties = optionalProperties;
settings.declarePropertiesAsReadOnly = declarePropertiesAsReadOnly;
settings.removeTypeNamePrefix = removeTypeNamePrefix;
settings.removeTypeNameSuffix = removeTypeNameSuffix;
Expand Down Expand Up @@ -137,6 +141,8 @@ public void generate() throws Exception {
settings.setStringQuotes(stringQuotes);
settings.displaySerializerWarning = displaySerializerWarning;
settings.disableJackson2ModuleDiscovery = disableJackson2ModuleDiscovery;
settings.jackson2ModuleDiscovery = jackson2ModuleDiscovery;
settings.loadJackson2Modules(classLoader, jackson2Modules);
settings.classLoader = classLoader;
final File output = outputFile != null
? getProject().file(outputFile)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,26 @@ public class GenerateMojo extends AbstractMojo {
private JsonLibrary jsonLibrary;

/**
* If true declared properties will be optional.
* Deprecated, use <code>optionalProperties</code> parameter.
*/
@Deprecated
@Parameter
private boolean declarePropertiesAsOptional;

/**
* Specifies how properties are defined to be optional.
* Supported values are:
* <ul>
* <li><code>useSpecifiedAnnotations</code> - annotations specified using <code>optionalAnnotations</code> parameter</li>
* <li><code>useLibraryDefinition</code> - examples: <code>@JsonProperty(required = false)</code> when using <code>jackson2</code> library
* or <code>@XmlElement(required = false)</code> when using <code>jaxb</code> library</li>
* <li><code>all</code> - all properties are optional</li>
* </ul>
* Default value is <code>useSpecifiedAnnotations</code>.
*/
@Parameter
private OptionalProperties optionalProperties;

/**
* If true declared properties will be <code>readonly</code>.
*/
Expand Down Expand Up @@ -404,11 +419,24 @@ public class GenerateMojo extends AbstractMojo {
private boolean displaySerializerWarning;

/**
* Turns off Jackson2 automatic module discovery.
* Deprecated, see <code>jackson2ModuleDiscovery</code> and <code>jackson2Modules</code> parameters.
*/
@Deprecated
@Parameter
private boolean disableJackson2ModuleDiscovery;

/**
* Turns on Jackson2 automatic module discovery.
*/
@Parameter
private boolean jackson2ModuleDiscovery;

/**
* Specifies Jackson2 modules to use.
*/
@Parameter
private List<String> jackson2Modules;

/**
* Turns on verbose output for debugging purposes.
*/
Expand Down Expand Up @@ -447,6 +475,7 @@ public void execute() {
settings.setExcludeFilter(excludeClasses, excludeClassPatterns);
settings.jsonLibrary = jsonLibrary;
settings.declarePropertiesAsOptional = declarePropertiesAsOptional;
settings.optionalProperties = optionalProperties;
settings.declarePropertiesAsReadOnly = declarePropertiesAsReadOnly;
settings.removeTypeNamePrefix = removeTypeNamePrefix;
settings.removeTypeNameSuffix = removeTypeNameSuffix;
Expand Down Expand Up @@ -483,6 +512,8 @@ public void execute() {
settings.setStringQuotes(stringQuotes);
settings.displaySerializerWarning = displaySerializerWarning;
settings.disableJackson2ModuleDiscovery = disableJackson2ModuleDiscovery;
settings.jackson2ModuleDiscovery = jackson2ModuleDiscovery;
settings.loadJackson2Modules(classLoader, jackson2Modules);
settings.classLoader = classLoader;
final File output = outputFile != null
? outputFile
Expand Down