mirror of
https://github.com/lightbend/config.git
synced 2025-02-23 17:50:30 +08:00
add withOnlyKey, withOnlyPath, withoutKey, withoutPath methods
These allow easily pruning a Config or ConfigObject. Previously would have required some sort of tedious recursive copying of the nodes in the tree.
This commit is contained in:
parent
71d209070a
commit
3051608186
@ -418,7 +418,7 @@ public interface Config extends ConfigMergeable {
|
||||
* units suffixes like "10m" or "5ns" as documented in the <a
|
||||
* href="https://github.com/typesafehub/config/blob/master/HOCON.md">the
|
||||
* spec</a>.
|
||||
*
|
||||
*
|
||||
* @param path
|
||||
* path expression
|
||||
* @return the duration value at the requested path, in milliseconds
|
||||
@ -487,4 +487,23 @@ public interface Config extends ConfigMergeable {
|
||||
List<Long> getMillisecondsList(String path);
|
||||
|
||||
List<Long> getNanosecondsList(String path);
|
||||
|
||||
/**
|
||||
* Clone the config with only the given path (and its children) retained;
|
||||
* all sibling paths are removed.
|
||||
*
|
||||
* @param path
|
||||
* path to keep
|
||||
* @return a copy of the config minus all paths except the one specified
|
||||
*/
|
||||
Config withOnlyPath(String path);
|
||||
|
||||
/**
|
||||
* Clone the config with the given path removed.
|
||||
*
|
||||
* @param path
|
||||
* path to remove
|
||||
* @return a copy of the config minus the specified path
|
||||
*/
|
||||
Config withoutPath(String path);
|
||||
}
|
||||
|
@ -91,4 +91,23 @@ public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> {
|
||||
*/
|
||||
@Override
|
||||
ConfigValue get(Object key);
|
||||
|
||||
/**
|
||||
* Clone the object with only the given key (and its children) retained; all
|
||||
* sibling keys are removed.
|
||||
*
|
||||
* @param key
|
||||
* key to keep
|
||||
* @return a copy of the object minus all keys except the one specified
|
||||
*/
|
||||
ConfigObject withOnlyKey(String key);
|
||||
|
||||
/**
|
||||
* Clone the object with the given key removed.
|
||||
*
|
||||
* @param key
|
||||
* key to remove
|
||||
* @return a copy of the object minus the specified key
|
||||
*/
|
||||
ConfigObject withoutKey(String key);
|
||||
}
|
||||
|
@ -43,6 +43,18 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
abstract public AbstractConfigObject withOnlyKey(String key);
|
||||
|
||||
@Override
|
||||
abstract public AbstractConfigObject withoutKey(String key);
|
||||
|
||||
abstract protected AbstractConfigObject withOnlyPathOrNull(Path path);
|
||||
|
||||
abstract AbstractConfigObject withOnlyPath(Path path);
|
||||
|
||||
abstract AbstractConfigObject withoutPath(Path path);
|
||||
|
||||
/**
|
||||
* This looks up the key with no transformation or type conversion of any
|
||||
* kind, and returns null if the key is not present.
|
||||
|
@ -113,6 +113,31 @@ final class ConfigDelayedMergeObject extends AbstractConfigObject implements
|
||||
return (ConfigDelayedMergeObject) super.withFallback(mergeable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigDelayedMergeObject withOnlyKey(String key) {
|
||||
throw notResolved();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigDelayedMergeObject withoutKey(String key) {
|
||||
throw notResolved();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractConfigObject withOnlyPathOrNull(Path path) {
|
||||
throw notResolved();
|
||||
}
|
||||
|
||||
@Override
|
||||
AbstractConfigObject withOnlyPath(Path path) {
|
||||
throw notResolved();
|
||||
}
|
||||
|
||||
@Override
|
||||
AbstractConfigObject withoutPath(Path path) {
|
||||
throw notResolved();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<AbstractConfigValue> unmergedValues() {
|
||||
return stack;
|
||||
|
@ -826,4 +826,16 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
throw new ConfigException.ValidationFailed(problems);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleConfig withOnlyPath(String pathExpression) {
|
||||
Path path = Path.newPath(pathExpression);
|
||||
return new SimpleConfig(root().withOnlyPath(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleConfig withoutPath(String pathExpression) {
|
||||
Path path = Path.newPath(pathExpression);
|
||||
return new SimpleConfig(root().withoutPath(path));
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,83 @@ final class SimpleConfigObject extends AbstractConfigObject {
|
||||
this(origin, value, ResolveStatus.fromValues(value.values()), false /* ignoresFallbacks */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleConfigObject withOnlyKey(String key) {
|
||||
return withOnlyPath(Path.newKey(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleConfigObject withoutKey(String key) {
|
||||
return withoutPath(Path.newKey(key));
|
||||
}
|
||||
|
||||
// gets the object with only the path if the path
|
||||
// exists, otherwise null if it doesn't. this ensures
|
||||
// that if we have { a : { b : 42 } } and do
|
||||
// withOnlyPath("a.b.c") that we don't keep an empty
|
||||
// "a" object.
|
||||
@Override
|
||||
protected SimpleConfigObject withOnlyPathOrNull(Path path) {
|
||||
String key = path.first();
|
||||
Path next = path.remainder();
|
||||
AbstractConfigValue v = value.get(key);
|
||||
|
||||
if (next != null) {
|
||||
if (v != null && (v instanceof AbstractConfigObject)) {
|
||||
v = ((AbstractConfigObject) v).withOnlyPathOrNull(next);
|
||||
} else {
|
||||
// if the path has more elements but we don't have an object,
|
||||
// then the rest of the path does not exist.
|
||||
v = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (v == null) {
|
||||
return null;
|
||||
} else {
|
||||
return new SimpleConfigObject(origin(), Collections.singletonMap(key, v),
|
||||
resolveStatus(), ignoresFallbacks);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
SimpleConfigObject withOnlyPath(Path path) {
|
||||
SimpleConfigObject o = withOnlyPathOrNull(path);
|
||||
if (o == null) {
|
||||
return new SimpleConfigObject(origin(),
|
||||
Collections.<String, AbstractConfigValue> emptyMap(), resolveStatus(),
|
||||
ignoresFallbacks);
|
||||
} else {
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
SimpleConfigObject withoutPath(Path path) {
|
||||
String key = path.first();
|
||||
Path next = path.remainder();
|
||||
AbstractConfigValue v = value.get(key);
|
||||
|
||||
if (v != null && next != null && v instanceof AbstractConfigObject) {
|
||||
v = ((AbstractConfigObject) v).withoutPath(next);
|
||||
Map<String, AbstractConfigValue> updated = new HashMap<String, AbstractConfigValue>(
|
||||
value);
|
||||
updated.put(key, v);
|
||||
return new SimpleConfigObject(origin(), updated, resolveStatus(), ignoresFallbacks);
|
||||
} else if (next != null || v == null) {
|
||||
// can't descend, nothing to remove
|
||||
return this;
|
||||
} else {
|
||||
Map<String, AbstractConfigValue> smaller = new HashMap<String, AbstractConfigValue>(
|
||||
value.size() - 1);
|
||||
for (Map.Entry<String, AbstractConfigValue> old : value.entrySet()) {
|
||||
if (!old.getKey().equals(key))
|
||||
smaller.put(old.getKey(), old.getValue());
|
||||
}
|
||||
return new SimpleConfigObject(origin(), smaller, resolveStatus(), ignoresFallbacks);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractConfigValue peek(String key) {
|
||||
return value.get(key);
|
||||
|
@ -708,4 +708,29 @@ class ConfigValueTest extends TestUtils {
|
||||
assertEquals("/foo", urlOrigin.filename)
|
||||
assertEquals("file:/foo", urlOrigin.url.toExternalForm)
|
||||
}
|
||||
|
||||
@Test
|
||||
def withOnly() {
|
||||
val obj = parseObject("{ a=1, b=2, c.d.y=3, e.f.g=4, c.d.z=5 }")
|
||||
assertEquals("keep only a", parseObject("{ a=1 }"), obj.withOnlyKey("a"))
|
||||
assertEquals("keep only e", parseObject("{ e.f.g=4 }"), obj.withOnlyKey("e"))
|
||||
assertEquals("keep only c.d", parseObject("{ c.d.y=3, c.d.z=5 }"), obj.toConfig.withOnlyPath("c.d").root)
|
||||
assertEquals("keep only c.d.z", parseObject("{ c.d.z=5 }"), obj.toConfig.withOnlyPath("c.d.z").root)
|
||||
assertEquals("keep nonexistent key", parseObject("{ }"), obj.withOnlyKey("nope"))
|
||||
assertEquals("keep nonexistent path", parseObject("{ }"), obj.toConfig.withOnlyPath("q.w.e.r.t.y").root)
|
||||
assertEquals("keep only nonexistent underneath non-object", parseObject("{ }"), obj.toConfig.withOnlyPath("a.nonexistent").root)
|
||||
assertEquals("keep only nonexistent underneath nested non-object", parseObject("{ }"), obj.toConfig.withOnlyPath("c.d.z.nonexistent").root)
|
||||
}
|
||||
|
||||
@Test
|
||||
def without() {
|
||||
val obj = parseObject("{ a=1, b=2, c.d.y=3, e.f.g=4, c.d.z=5 }")
|
||||
assertEquals("without a", parseObject("{ b=2, c.d.y=3, e.f.g=4, c.d.z=5 }"), obj.withoutKey("a"))
|
||||
assertEquals("without c", parseObject("{ a=1, b=2, e.f.g=4 }"), obj.withoutKey("c"))
|
||||
assertEquals("without c.d", parseObject("{ a=1, b=2, e.f.g=4, c={} }"), obj.toConfig.withoutPath("c.d").root)
|
||||
assertEquals("without c.d.z", parseObject("{ a=1, b=2, c.d.y=3, e.f.g=4 }"), obj.toConfig.withoutPath("c.d.z").root)
|
||||
assertEquals("without nonexistent key", parseObject("{ a=1, b=2, c.d.y=3, e.f.g=4, c.d.z=5 }"), obj.withoutKey("nonexistent"))
|
||||
assertEquals("without nonexistent path", parseObject("{ a=1, b=2, c.d.y=3, e.f.g=4, c.d.z=5 }"), obj.toConfig.withoutPath("q.w.e.r.t.y").root)
|
||||
assertEquals("without nonexistent path with existing prefix", parseObject("{ a=1, b=2, c.d.y=3, e.f.g=4, c.d.z=5 }"), obj.toConfig.withoutPath("a.foo").root)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user