Skip to content

Commit

Permalink
Add option to specify custom fragment separator characters (#697)
Browse files Browse the repository at this point in the history
* Add option to specify separator characters to be used to split non standard JSON Pointers
* Fixes from review
  • Loading branch information
tovkal authored and joelittlejohn committed Mar 2, 2017
1 parent 9061e13 commit 2736b8c
Show file tree
Hide file tree
Showing 17 changed files with 177 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ public class Jsonschema2PojoTask extends Task implements GenerationConfig {

private boolean formatDateTimes = false;

private String refFragmentPathDelimiters = "#/.";

/**
* Execute this task (it's expected that all relevant setters will have been
Expand Down Expand Up @@ -696,6 +697,17 @@ public void setFormatDateTime(boolean formatDateTimes) {
this.formatDateTimes = formatDateTimes;
}

/**
* Sets the 'refFragmentPathDelimiters' property of this class
*
* @param refFragmentPathDelimiters A string containing any characters that should act as path delimiters when
* resolving $ref fragments. By default, #, / and . are used in an attempt
* to support JSON Pointer and JSON Path.
*/
public void setRefFragmentPathDelimiters(String refFragmentPathDelimiters) {
this.refFragmentPathDelimiters = refFragmentPathDelimiters;
}

@Override
public boolean isGenerateBuilders() {
return generateBuilders;
Expand Down Expand Up @@ -938,4 +950,8 @@ public boolean isFormatDateTimes() {
return formatDateTimes;
}

@Override
public String getRefFragmentPathDelimiters() {
return refFragmentPathDelimiters;
}
}
7 changes: 7 additions & 0 deletions jsonschema2pojo-ant/src/site/Jsonschema2PojoTask.html
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,13 @@ <h3>Parameters</h3>
</td>
<td align="center" valign="top">No (default <code>false</code>)</td>
</tr>
<tr>
<td valign="top">refFragmentPathDelimiters</td>
<td valign="top">A string containing any characters that should act as path delimiters when resolving $ref
fragments. By default, #, / and . are used in an attempt to support JSON Pointer and JSON Path.
</td>
<td align="center" valign="top">No (default <code>#/.</code>)</td>
</tr>
</table>

<h3>Examples</h3>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ public class Arguments implements GenerationConfig {

@Parameter(names = { "-fdt", "--format-date-times" }, description = "Whether the fields of type `date-time` have the `@JsonFormat` annotation with pattern set to the default value of `yyyy-MM-dd'T'HH:mm:ss.SSS` and timezone set to default value of `UTC`")
private boolean formatDateTimes = false;

@Parameter(names = {"-rpd", "--ref-fragment-path-delimiters"}, description = "A string containing any characters that should act as path delimiters when resolving $ref fragments. By default, #, / and . are used in an attempt to support JSON Pointer and JSON Path.")
private String refFragmentPathDelimiters = "#/.";

private static final int EXIT_OKAY = 0;
private static final int EXIT_ERROR = 1;
Expand Down Expand Up @@ -418,4 +421,9 @@ public boolean isFormatDateTimes() {
return formatDateTimes;
}

@Override
public String getRefFragmentPathDelimiters() {
return refFragmentPathDelimiters;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -332,4 +332,12 @@ public String getTimeType() {
public boolean isFormatDateTimes() {
return false;
}

/**
* @return "#/."
*/
@Override
public String getRefFragmentPathDelimiters() {
return "#/.";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@

public class FragmentResolver {

public JsonNode resolve(JsonNode tree, String path) {
public JsonNode resolve(JsonNode tree, String path, String refFragmentPathDelimiters) {

return resolve(tree, new ArrayList<String>(asList(split(path, "#/."))));
return resolve(tree, new ArrayList<String>(asList(split(path, refFragmentPathDelimiters))));

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,4 +441,12 @@ public interface GenerationConfig {
*/
boolean isFormatDateTimes();

/**
* Gets the `refFragmentPathDelimiters` configuration option.
*
* @return A string containing any characters that should act as path delimiters when resolving $ref fragments.
* By default, #, / and . are used in an attempt to support JSON Pointer and JSON Path.
*/
String getRefFragmentPathDelimiters();

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,18 @@ public class SchemaStore {
*
* @param id
* the id of the schema being created
* @param refFragmentPathDelimiters A string containing any characters
* that should act as path delimiters when resolving $ref fragments.
* @return a schema object containing the contents of the given path
*/
public synchronized Schema create(URI id) {
public synchronized Schema create(URI id, String refFragmentPathDelimiters) {

if (!schemas.containsKey(id)) {

JsonNode content = contentResolver.resolve(removeFragment(id));

if (id.toString().contains("#")) {
JsonNode childContent = fragmentResolver.resolve(content, '#' + id.getFragment());
JsonNode childContent = fragmentResolver.resolve(content, '#' + id.getFragment(), refFragmentPathDelimiters);
schemas.put(id, new Schema(id, childContent, content));
} else {
schemas.put(id, new Schema(id, content, content));
Expand All @@ -74,10 +76,12 @@ protected URI removeFragment(URI id) {
* the relative path of this schema (will be used to create a
* complete URI by resolving this path against the parent
* schema's id)
* @param refFragmentPathDelimiters A string containing any characters
* that should act as path delimiters when resolving $ref fragments.
* @return a schema object containing the contents of the given path
*/
@SuppressWarnings("PMD.UselessParentheses")
public Schema create(Schema parent, String path) {
public Schema create(Schema parent, String path, String refFragmentPathDelimiters) {

if (!path.equals("#")) {
// if path is an empty string then resolving it below results in jumping up a level. e.g. "/path/to/file.json" becomes "/path/to"
Expand Down Expand Up @@ -109,11 +113,11 @@ public Schema create(Schema parent, String path) {
}

if (selfReferenceWithoutParentFile(parent, path) || substringBefore(stringId, "#").isEmpty()) {
schemas.put(id, new Schema(id, fragmentResolver.resolve(parent.getParentContent(), path), parent.getParentContent()));
schemas.put(id, new Schema(id, fragmentResolver.resolve(parent.getParentContent(), path, refFragmentPathDelimiters), parent.getParentContent()));
return schemas.get(id);
}

return create(id);
return create(id, refFragmentPathDelimiters);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ private Schema getSuperSchema(JsonNode node, Schema schema, boolean followRefs)
path = "#" + schema.getId().getFragment() + "/extends";
}

Schema superSchema = ruleFactory.getSchemaStore().create(schema, path);
Schema superSchema = ruleFactory.getSchemaStore().create(schema, path, ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters());

if (followRefs) {
superSchema = resolveSchemaRefsRecursive(superSchema);
Expand All @@ -342,7 +342,7 @@ private Schema getSuperSchema(JsonNode node, Schema schema, boolean followRefs)
private Schema resolveSchemaRefsRecursive(Schema schema) {
JsonNode schemaNode = schema.getContent();
if (schemaNode.has("$ref")) {
schema = ruleFactory.getSchemaStore().create(schema, schemaNode.get("$ref").asText());
schema = ruleFactory.getSchemaStore().create(schema, schemaNode.get("$ref").asText(), ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters());
return resolveSchemaRefsRecursive(schema);
}
return schema;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ private void formatAnnotation(JFieldVar field, JsonNode node) {

private JsonNode resolveRefs(JsonNode node, Schema parent) {
if (node.has("$ref")) {
Schema refSchema = ruleFactory.getSchemaStore().create(parent, node.get("$ref").asText());
Schema refSchema = ruleFactory.getSchemaStore().create(parent, node.get("$ref").asText(), ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters());
JsonNode refNode = refSchema.getContent();
return resolveRefs(refNode, parent);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ protected SchemaRule(RuleFactory ruleFactory) {
public JType apply(String nodeName, JsonNode schemaNode, JClassContainer generatableType, Schema schema) {

if (schemaNode.has("$ref")) {
schema = ruleFactory.getSchemaStore().create(schema, schemaNode.get("$ref").asText());
schema = ruleFactory.getSchemaStore().create(schema, schemaNode.get("$ref").asText(), ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters());
schemaNode = schema.getContent();

if (schema.isGenerated()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public void hashResolvesToRoot() {
root.set("child2", root.objectNode());
root.set("child3", root.objectNode());

assertThat((ObjectNode) resolver.resolve(root, "#"), is(sameInstance(root)));
assertThat((ObjectNode) resolver.resolve(root, "#", "#/."), is(sameInstance(root)));

}

Expand Down Expand Up @@ -71,17 +71,17 @@ public void slashDelimitedWordsResolveToChildNodes() {
z.set("1", _1);
z.set("2", _2);

assertThat((ObjectNode) resolver.resolve(root, "#/a"), is(sameInstance(a)));
assertThat((ObjectNode) resolver.resolve(root, "#/b"), is(sameInstance(b)));
assertThat((ObjectNode) resolver.resolve(root, "#/c"), is(sameInstance(c)));
assertThat((ObjectNode) resolver.resolve(root, "#/a", "#/."), is(sameInstance(a)));
assertThat((ObjectNode) resolver.resolve(root, "#/b", "#/."), is(sameInstance(b)));
assertThat((ObjectNode) resolver.resolve(root, "#/c", "#/."), is(sameInstance(c)));

assertThat((ObjectNode) resolver.resolve(root, "#/a/x"), is(sameInstance(x)));
assertThat((ObjectNode) resolver.resolve(root, "#/a/y"), is(sameInstance(y)));
assertThat((ObjectNode) resolver.resolve(root, "#/a/z"), is(sameInstance(z)));
assertThat((ObjectNode) resolver.resolve(root, "#/a/x", "#/."), is(sameInstance(x)));
assertThat((ObjectNode) resolver.resolve(root, "#/a/y", "#/."), is(sameInstance(y)));
assertThat((ObjectNode) resolver.resolve(root, "#/a/z", "#/."), is(sameInstance(z)));

assertThat((ObjectNode) resolver.resolve(root, "#/a/z/0"), is(sameInstance(_0)));
assertThat((ObjectNode) resolver.resolve(root, "#/a/z/1"), is(sameInstance(_1)));
assertThat((ObjectNode) resolver.resolve(root, "#/a/z/2"), is(sameInstance(_2)));
assertThat((ObjectNode) resolver.resolve(root, "#/a/z/0", "#/."), is(sameInstance(_0)));
assertThat((ObjectNode) resolver.resolve(root, "#/a/z/1", "#/."), is(sameInstance(_1)));
assertThat((ObjectNode) resolver.resolve(root, "#/a/z/2", "#/."), is(sameInstance(_2)));

}

Expand All @@ -97,9 +97,9 @@ public void pathCanReferToArrayContentsByIndex() {
a.add(root.objectNode());
a.add(root.objectNode());

assertThat(resolver.resolve(root, "#/a/0"), is(sameInstance(a.get(0))));
assertThat(resolver.resolve(root, "#/a/1"), is(sameInstance(a.get(1))));
assertThat(resolver.resolve(root, "#/a/2"), is(sameInstance(a.get(2))));
assertThat(resolver.resolve(root, "#/a/0", "#/."), is(sameInstance(a.get(0))));
assertThat(resolver.resolve(root, "#/a/1", "#/."), is(sameInstance(a.get(1))));
assertThat(resolver.resolve(root, "#/a/2", "#/."), is(sameInstance(a.get(2))));

}

Expand All @@ -111,9 +111,9 @@ public void pathCanReferToArrayContentsAtTheDocumentRoot() {
root.add(root.objectNode());
root.add(root.objectNode());

assertThat(resolver.resolve(root, "#/0"), is(sameInstance(root.get(0))));
assertThat(resolver.resolve(root, "#/1"), is(sameInstance(root.get(1))));
assertThat(resolver.resolve(root, "#/2"), is(sameInstance(root.get(2))));
assertThat(resolver.resolve(root, "#/0", "#/."), is(sameInstance(root.get(0))));
assertThat(resolver.resolve(root, "#/1", "#/."), is(sameInstance(root.get(1))));
assertThat(resolver.resolve(root, "#/2", "#/."), is(sameInstance(root.get(2))));

}

Expand All @@ -122,7 +122,7 @@ public void missingPathThrowsIllegalArgumentException() {

ObjectNode root = new ObjectMapper().createObjectNode();

resolver.resolve(root, "#/a/b/c");
resolver.resolve(root, "#/a/b/c", "#/.");

}

Expand All @@ -134,7 +134,7 @@ public void attemptToUsePropertyNameOnArrayNodeThrowsIllegalArgumentException()
ArrayNode a = root.arrayNode();
root.set("a", a);

resolver.resolve(root, "#/a/b");
resolver.resolve(root, "#/a/b", "#/.");

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public void createWithAbsolutePath() throws URISyntaxException {

URI schemaUri = getClass().getResource("/schema/address.json").toURI();

Schema schema = new SchemaStore().create(schemaUri);
Schema schema = new SchemaStore().create(schemaUri, "#/.");

assertThat(schema, is(notNullValue()));
assertThat(schema.getId(), is(equalTo(schemaUri)));
Expand All @@ -51,8 +51,8 @@ public void createWithRelativePath() throws URISyntaxException {
URI addressSchemaUri = getClass().getResource("/schema/address.json").toURI();

SchemaStore schemaStore = new SchemaStore();
Schema addressSchema = schemaStore.create(addressSchemaUri);
Schema enumSchema = schemaStore.create(addressSchema, "enum.json");
Schema addressSchema = schemaStore.create(addressSchemaUri, "#/.");
Schema enumSchema = schemaStore.create(addressSchema, "enum.json", "#/.");

String expectedUri = removeEnd(addressSchemaUri.toString(), "address.json") + "enum.json";

Expand All @@ -68,8 +68,8 @@ public void createWithSelfRef() throws URISyntaxException {
URI schemaUri = getClass().getResource("/schema/address.json").toURI();

SchemaStore schemaStore = new SchemaStore();
Schema addressSchema = schemaStore.create(schemaUri);
Schema selfRefSchema = schemaStore.create(addressSchema, "#");
Schema addressSchema = schemaStore.create(schemaUri, "#/.");
Schema selfRefSchema = schemaStore.create(addressSchema, "#", "#/.");

assertThat(addressSchema, is(sameInstance(selfRefSchema)));

Expand All @@ -81,9 +81,9 @@ public void createWithEmbeddedSelfRef() throws URISyntaxException {
URI schemaUri = getClass().getResource("/schema/embeddedRef.json").toURI();

SchemaStore schemaStore = new SchemaStore();
Schema topSchema = schemaStore.create(schemaUri);
Schema embeddedSchema = schemaStore.create(topSchema, "#/definitions/embedded");
Schema selfRefSchema = schemaStore.create(embeddedSchema, "#");
Schema topSchema = schemaStore.create(schemaUri, "#/.");
Schema embeddedSchema = schemaStore.create(topSchema, "#/definitions/embedded", "#/.");
Schema selfRefSchema = schemaStore.create(embeddedSchema, "#", "#/.");

assertThat(topSchema, is(sameInstance(selfRefSchema)));

Expand All @@ -95,8 +95,8 @@ public void createWithFragmentResolution() throws URISyntaxException {
URI addressSchemaUri = getClass().getResource("/schema/address.json").toURI();

SchemaStore schemaStore = new SchemaStore();
Schema addressSchema = schemaStore.create(addressSchemaUri);
Schema innerSchema = schemaStore.create(addressSchema, "#/properties/post-office-box");
Schema addressSchema = schemaStore.create(addressSchemaUri, "#/.");
Schema innerSchema = schemaStore.create(addressSchema, "#/properties/post-office-box", "#/.");

String expectedUri = addressSchemaUri.toString() + "#/properties/post-office-box";

Expand All @@ -114,9 +114,9 @@ public void schemaAlreadyReadIsReused() throws URISyntaxException {

SchemaStore schemaStore = new SchemaStore();

Schema schema1 = schemaStore.create(schemaUri);
Schema schema1 = schemaStore.create(schemaUri, "#/.");

Schema schema2 = schemaStore.create(schemaUri);
Schema schema2 = schemaStore.create(schemaUri, "#/.");

assertThat(schema1, is(sameInstance(schema2)));

Expand All @@ -130,7 +130,7 @@ public void setIfEmptyOnlySetsIfEmpty() throws URISyntaxException {

URI schemaUri = getClass().getResource("/schema/address.json").toURI();

Schema schema = new SchemaStore().create(schemaUri);
Schema schema = new SchemaStore().create(schemaUri, "#/.");

schema.setJavaTypeIfEmpty(firstClass);
assertThat(schema.getJavaType(), is(equalTo(firstClass)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.net.URI;
import java.net.URISyntaxException;

import org.jsonschema2pojo.GenerationConfig;
import org.jsonschema2pojo.Schema;
import org.jsonschema2pojo.SchemaStore;
import org.junit.Test;
Expand Down Expand Up @@ -55,9 +56,13 @@ public void refsToOtherSchemasAreLoaded() throws URISyntaxException, JClassAlrea

JDefinedClass jclass = new JCodeModel()._class(TARGET_CLASS_NAME);

final GenerationConfig mockGenerationConfig = mock(GenerationConfig.class);
when(mockGenerationConfig.getRefFragmentPathDelimiters()).thenReturn("#/.");

TypeRule mockTypeRule = mock(TypeRule.class);
when(mockRuleFactory.getTypeRule()).thenReturn(mockTypeRule);
when(mockRuleFactory.getSchemaStore()).thenReturn(new SchemaStore());
when(mockRuleFactory.getGenerationConfig()).thenReturn(mockGenerationConfig);

ArgumentCaptor<JsonNode> captureJsonNode = ArgumentCaptor.forClass(JsonNode.class);
ArgumentCaptor<Schema> captureSchema = ArgumentCaptor.forClass(Schema.class);
Expand Down Expand Up @@ -106,10 +111,14 @@ public void existingTypeIsUsedWhenTypeIsAlreadyGenerated() throws URISyntaxExcep
URI schemaUri = getClass().getResource("/schema/address.json").toURI();

SchemaStore schemaStore = new SchemaStore();
Schema schema = schemaStore.create(schemaUri);
Schema schema = schemaStore.create(schemaUri, "#/.");
schema.setJavaType(previouslyGeneratedType);

final GenerationConfig mockGenerationConfig = mock(GenerationConfig.class);
when(mockGenerationConfig.getRefFragmentPathDelimiters()).thenReturn("#/.");

when(mockRuleFactory.getSchemaStore()).thenReturn(schemaStore);
when(mockRuleFactory.getGenerationConfig()).thenReturn(mockGenerationConfig);

ObjectNode schemaNode = new ObjectMapper().createObjectNode();
schemaNode.put("$ref", schemaUri.toString());
Expand Down
Loading

0 comments on commit 2736b8c

Please sign in to comment.