fix JSON construction with maps/collections, more convenience for converting strings
This commit is contained in:
parent
b5491330a5
commit
9759d5c020
6 changed files with 264 additions and 61 deletions
|
@ -16,7 +16,7 @@ public interface Builder {
|
||||||
|
|
||||||
Builder buildMap(Map<String, Object> map) throws IOException;
|
Builder buildMap(Map<String, Object> map) throws IOException;
|
||||||
|
|
||||||
Builder buildCollection(Collection<Object> collection) throws IOException;
|
Builder buildCollection(Collection<?> collection) throws IOException;
|
||||||
|
|
||||||
Builder buildKey(CharSequence key) throws IOException;
|
Builder buildKey(CharSequence key) throws IOException;
|
||||||
|
|
||||||
|
@ -24,7 +24,13 @@ public interface Builder {
|
||||||
|
|
||||||
Builder buildNull() throws IOException;
|
Builder buildNull() throws IOException;
|
||||||
|
|
||||||
default Builder buildIfNotNull(CharSequence key, Object value) throws IOException {
|
default Builder field(CharSequence key, Object value) throws IOException {
|
||||||
|
buildKey(key);
|
||||||
|
buildValue(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
default Builder fieldIfNotNull(CharSequence key, Object value) throws IOException {
|
||||||
if (value != null){
|
if (value != null){
|
||||||
buildKey(key);
|
buildKey(key);
|
||||||
buildValue(value);
|
buildValue(value);
|
||||||
|
@ -32,5 +38,25 @@ public interface Builder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default Builder collection(CharSequence key, Collection<?> value) throws IOException {
|
||||||
|
buildKey(key);
|
||||||
|
buildValue(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
default Builder beginMap(CharSequence key) throws IOException {
|
||||||
|
buildKey(key);
|
||||||
|
beginMap();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
default Builder beginCollection(CharSequence key) throws IOException {
|
||||||
|
buildKey(key);
|
||||||
|
beginCollection();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder copy(Builder builder) throws IOException;
|
||||||
|
|
||||||
String build();
|
String build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,23 @@
|
||||||
package org.xbib.datastructures.json.tiny;
|
package org.xbib.datastructures.json.tiny;
|
||||||
|
|
||||||
import org.xbib.datastructures.api.*;
|
import org.xbib.datastructures.api.Builder;
|
||||||
|
import org.xbib.datastructures.api.ByteSizeValue;
|
||||||
|
import org.xbib.datastructures.api.DataStructure;
|
||||||
|
import org.xbib.datastructures.api.Generator;
|
||||||
|
import org.xbib.datastructures.api.Node;
|
||||||
|
import org.xbib.datastructures.api.Parser;
|
||||||
|
import org.xbib.datastructures.api.TimeValue;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class Json implements DataStructure {
|
public class Json implements DataStructure {
|
||||||
|
|
||||||
|
private static final Json INSTANCE = new Json();
|
||||||
|
|
||||||
private final char separator;
|
private final char separator;
|
||||||
|
|
||||||
private Node<?> root;
|
private Node<?> root;
|
||||||
|
@ -25,6 +35,15 @@ public class Json implements DataStructure {
|
||||||
this.separator = separator;
|
this.separator = separator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static Map<String, Object> toMap(String yaml) throws IOException {
|
||||||
|
return (Map<String, Object>) INSTANCE.createParser().parse(new StringReader(yaml)).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toString(Map<String, Object> map) throws IOException {
|
||||||
|
return INSTANCE.createBuilder().buildMap(map).build();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Parser createParser() {
|
public Parser createParser() {
|
||||||
return new StreamParser();
|
return new StreamParser();
|
||||||
|
|
|
@ -3,11 +3,8 @@ package org.xbib.datastructures.json.tiny;
|
||||||
import org.xbib.datastructures.api.Builder;
|
import org.xbib.datastructures.api.Builder;
|
||||||
import org.xbib.datastructures.api.ByteSizeValue;
|
import org.xbib.datastructures.api.ByteSizeValue;
|
||||||
import org.xbib.datastructures.api.TimeValue;
|
import org.xbib.datastructures.api.TimeValue;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.io.Writer;
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -15,31 +12,31 @@ import java.util.Objects;
|
||||||
|
|
||||||
public class JsonBuilder implements Builder {
|
public class JsonBuilder implements Builder {
|
||||||
|
|
||||||
private final Writer writer;
|
private final Appendable appendable;
|
||||||
|
|
||||||
private State state;
|
private State state;
|
||||||
|
|
||||||
public JsonBuilder() {
|
public JsonBuilder() {
|
||||||
this(new StringWriter());
|
this(new StringBuilder());
|
||||||
}
|
}
|
||||||
|
|
||||||
public JsonBuilder(Writer writer) {
|
public JsonBuilder(Appendable appendable) {
|
||||||
this.writer = writer;
|
this.appendable = appendable;
|
||||||
this.state = new State(null, 0, Structure.MAP, true);
|
this.state = new State(null, 0, Structure.DOCSTART, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JsonBuilder builder() {
|
public static JsonBuilder builder() {
|
||||||
return new JsonBuilder();
|
return new JsonBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JsonBuilder builder(Writer writer) {
|
public static JsonBuilder builder(Appendable appendable) {
|
||||||
return new JsonBuilder(writer);
|
return new JsonBuilder(appendable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Builder beginCollection() throws IOException {
|
public Builder beginCollection() throws IOException {
|
||||||
this.state = new State(state, state.level + 1, Structure.COLLECTION, true);
|
this.state = new State(state, state.level + 1, Structure.COLLECTION, true);
|
||||||
writer.write('[');
|
appendable.append('[');
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +45,7 @@ public class JsonBuilder implements Builder {
|
||||||
if (state.structure != Structure.COLLECTION) {
|
if (state.structure != Structure.COLLECTION) {
|
||||||
throw new JsonException("no array to close");
|
throw new JsonException("no array to close");
|
||||||
}
|
}
|
||||||
writer.write(']');
|
appendable.append(']');
|
||||||
this.state = state != null ? state.parent : null;
|
this.state = state != null ? state.parent : null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -56,25 +53,30 @@ public class JsonBuilder implements Builder {
|
||||||
@Override
|
@Override
|
||||||
public Builder beginMap() throws IOException {
|
public Builder beginMap() throws IOException {
|
||||||
this.state = new State(state, state.level + 1, Structure.MAP, true);
|
this.state = new State(state, state.level + 1, Structure.MAP, true);
|
||||||
writer.write('{');
|
appendable.append('{');
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Builder endMap() throws IOException {
|
public Builder endMap() throws IOException {
|
||||||
if (state.structure != Structure.MAP) {
|
if (state.structure != Structure.MAP && state.structure != Structure.KEY) {
|
||||||
throw new JsonException("no object to close");
|
throw new JsonException("no object to close");
|
||||||
}
|
}
|
||||||
writer.write('}');
|
appendable.append('}');
|
||||||
this.state = state != null ? state.parent : null;
|
this.state = state != null ? state.parent : null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Builder buildMap(Map<String, Object> map) throws IOException {
|
public Builder buildMap(Map<String, Object> map) throws IOException {
|
||||||
Objects.requireNonNull(map);
|
Objects.requireNonNull(map);
|
||||||
|
if (state.structure == Structure.COLLECTION) {
|
||||||
|
beginArrayValue(map);
|
||||||
|
}
|
||||||
|
boolean wrap = state.structure != Structure.MAP;
|
||||||
|
if (wrap) {
|
||||||
beginMap();
|
beginMap();
|
||||||
|
}
|
||||||
map.forEach((k, v) -> {
|
map.forEach((k, v) -> {
|
||||||
try {
|
try {
|
||||||
buildKey(k);
|
buildKey(k);
|
||||||
|
@ -83,12 +85,17 @@ public class JsonBuilder implements Builder {
|
||||||
throw new UncheckedIOException(e);
|
throw new UncheckedIOException(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (wrap) {
|
||||||
endMap();
|
endMap();
|
||||||
|
}
|
||||||
|
if (state.structure == Structure.COLLECTION) {
|
||||||
|
endArrayValue(map);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Builder buildCollection(Collection<Object> collection) throws IOException {
|
public Builder buildCollection(Collection<?> collection) throws IOException {
|
||||||
Objects.requireNonNull(collection);
|
Objects.requireNonNull(collection);
|
||||||
beginCollection();
|
beginCollection();
|
||||||
collection.forEach(v -> {
|
collection.forEach(v -> {
|
||||||
|
@ -105,7 +112,7 @@ public class JsonBuilder implements Builder {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public Builder buildValue(Object object) throws IOException {
|
public Builder buildValue(Object object) throws IOException {
|
||||||
if (state.structure == Structure.MAP) {
|
if (state.structure == Structure.MAP || state.structure == Structure.KEY) {
|
||||||
beginValue(object);
|
beginValue(object);
|
||||||
} else if (state.structure == Structure.COLLECTION) {
|
} else if (state.structure == Structure.COLLECTION) {
|
||||||
beginArrayValue(object);
|
beginArrayValue(object);
|
||||||
|
@ -142,7 +149,7 @@ public class JsonBuilder implements Builder {
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("unable to write object class " + object.getClass());
|
throw new IllegalArgumentException("unable to write object class " + object.getClass());
|
||||||
}
|
}
|
||||||
if (state.structure == Structure.MAP) {
|
if (state.structure == Structure.MAP || state.structure == Structure.KEY) {
|
||||||
endValue(object);
|
endValue(object);
|
||||||
} else if (state.structure == Structure.COLLECTION) {
|
} else if (state.structure == Structure.COLLECTION) {
|
||||||
endArrayValue(object);
|
endArrayValue(object);
|
||||||
|
@ -152,42 +159,68 @@ public class JsonBuilder implements Builder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Builder buildKey(CharSequence string) throws IOException {
|
public Builder buildKey(CharSequence string) throws IOException {
|
||||||
if (state.structure == Structure.MAP) {
|
if (state.structure == Structure.COLLECTION) {
|
||||||
|
beginArrayValue(string);
|
||||||
|
}
|
||||||
|
if (state.structure == Structure.MAP || state.structure == Structure.KEY) {
|
||||||
beginKey(string != null ? string.toString() : null);
|
beginKey(string != null ? string.toString() : null);
|
||||||
}
|
}
|
||||||
buildString(string, true);
|
buildString(string, true);
|
||||||
if (state.structure == Structure.MAP) {
|
if (state.structure == Structure.MAP || state.structure == Structure.KEY) {
|
||||||
endKey(string != null ? string.toString() : null);
|
endKey(string != null ? string.toString() : null);
|
||||||
}
|
}
|
||||||
|
state.structure = Structure.KEY;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Builder buildNull() throws IOException {
|
public Builder buildNull() throws IOException {
|
||||||
if (state.structure == Structure.MAP) {
|
if (state.structure == Structure.MAP || state.structure == Structure.KEY) {
|
||||||
beginValue(null);
|
beginValue(null);
|
||||||
} else if (state.structure == Structure.COLLECTION) {
|
} else if (state.structure == Structure.COLLECTION) {
|
||||||
beginArrayValue(null);
|
beginArrayValue(null);
|
||||||
}
|
}
|
||||||
buildString("null", false);
|
buildString("null", false);
|
||||||
|
if (state.structure == Structure.MAP || state.structure == Structure.KEY) {
|
||||||
|
endValue(null);
|
||||||
|
} else if (state.structure == Structure.COLLECTION) {
|
||||||
|
endArrayValue(null);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder copy(Builder builder) throws IOException {
|
||||||
|
if (state.structure == Structure.MAP || state.structure == Structure.KEY) {
|
||||||
|
beginValue(null);
|
||||||
|
} else if (state.structure == Structure.COLLECTION) {
|
||||||
|
beginArrayValue(null);
|
||||||
|
}
|
||||||
|
appendable.append(builder.build());
|
||||||
|
if (state.structure == Structure.MAP || state.structure == Structure.KEY) {
|
||||||
|
endValue(null);
|
||||||
|
}
|
||||||
|
if (state.structure == Structure.COLLECTION) {
|
||||||
|
endArrayValue(null);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String build() {
|
public String build() {
|
||||||
return writer.toString();
|
return appendable.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beginKey(String k) throws IOException {
|
private void beginKey(String k) throws IOException {
|
||||||
if (state.first) {
|
if (state.first) {
|
||||||
state.first = false;
|
state.first = false;
|
||||||
} else {
|
} else {
|
||||||
writer.write(",");
|
appendable.append(",");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void endKey(String k) throws IOException {
|
private void endKey(String k) throws IOException {
|
||||||
writer.write(":");
|
appendable.append(":");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beginValue(Object v) {
|
private void beginValue(Object v) {
|
||||||
|
@ -200,7 +233,7 @@ public class JsonBuilder implements Builder {
|
||||||
if (state.first) {
|
if (state.first) {
|
||||||
state.first = false;
|
state.first = false;
|
||||||
} else {
|
} else {
|
||||||
writer.write(",");
|
appendable.append(",");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,10 +253,10 @@ public class JsonBuilder implements Builder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildString(CharSequence string, boolean escape) throws IOException {
|
private void buildString(CharSequence string, boolean escape) throws IOException {
|
||||||
writer.write(escape ? escapeString(string) : string.toString());
|
appendable.append(escape ? escapeString(string) : string);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String escapeString(CharSequence string) {
|
private CharSequence escapeString(CharSequence string) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append('"');
|
sb.append('"');
|
||||||
int start = 0;
|
int start = 0;
|
||||||
|
@ -232,17 +265,17 @@ public class JsonBuilder implements Builder {
|
||||||
char c = string.charAt(i);
|
char c = string.charAt(i);
|
||||||
if (c == '"' || c < 32 || c >= 127 || c == '\\') {
|
if (c == '"' || c < 32 || c >= 127 || c == '\\') {
|
||||||
if (start < i) {
|
if (start < i) {
|
||||||
sb.append(string.toString(), start, i - start);
|
sb.append(string, start, i - start);
|
||||||
}
|
}
|
||||||
start = i;
|
start = i;
|
||||||
sb.append(escapeCharacter(c));
|
sb.append(escapeCharacter(c));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (start < l) {
|
if (start < l) {
|
||||||
sb.append(string.toString(), start, l - start);
|
sb.append(string, start, l - start);
|
||||||
}
|
}
|
||||||
sb.append('"');
|
sb.append('"');
|
||||||
return sb.toString();
|
return sb;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String escapeCharacter(char c) {
|
private static String escapeCharacter(char c) {
|
||||||
|
@ -264,7 +297,7 @@ public class JsonBuilder implements Builder {
|
||||||
return "\\u0000".substring(0, 6 - hex.length()) + hex;
|
return "\\u0000".substring(0, 6 - hex.length()) + hex;
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum Structure { MAP, COLLECTION };
|
private enum Structure { DOCSTART, MAP, KEY, COLLECTION };
|
||||||
|
|
||||||
private static class State {
|
private static class State {
|
||||||
State parent;
|
State parent;
|
||||||
|
|
|
@ -6,6 +6,7 @@ import org.xbib.datastructures.json.tiny.StreamParser;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -108,4 +109,108 @@ public class JsonBuilderTest {
|
||||||
jsonBuilder.buildMap(Map.of("a", "b"));
|
jsonBuilder.buildMap(Map.of("a", "b"));
|
||||||
assertEquals("{\"a\":\"b\"}", jsonBuilder.build());
|
assertEquals("{\"a\":\"b\"}", jsonBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testKeyValue() throws Exception {
|
||||||
|
JsonBuilder jsonBuilder = new JsonBuilder();
|
||||||
|
jsonBuilder.beginMap();
|
||||||
|
jsonBuilder.buildKey("a");
|
||||||
|
jsonBuilder.buildValue("b");
|
||||||
|
// test comma separation
|
||||||
|
jsonBuilder.buildKey("c");
|
||||||
|
jsonBuilder.buildValue("d");
|
||||||
|
jsonBuilder.endMap();
|
||||||
|
assertEquals("{\"a\":\"b\",\"c\":\"d\"}", jsonBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMapBuild() throws Exception {
|
||||||
|
JsonBuilder jsonBuilder = new JsonBuilder();
|
||||||
|
jsonBuilder.beginMap();
|
||||||
|
jsonBuilder.buildKey("map");
|
||||||
|
// buildMap is wrapped with '{' and '}'
|
||||||
|
jsonBuilder.buildMap(Map.of("a", "b"));
|
||||||
|
jsonBuilder.endMap();
|
||||||
|
assertEquals("{\"map\":{\"a\":\"b\"}}", jsonBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBeginMapBuild() throws Exception {
|
||||||
|
JsonBuilder jsonBuilder = new JsonBuilder();
|
||||||
|
jsonBuilder.beginMap();
|
||||||
|
jsonBuilder.beginMap("map");
|
||||||
|
// buildMap is not wrapped with '{' and '}'
|
||||||
|
jsonBuilder.buildMap(Map.of("a", "b"));
|
||||||
|
jsonBuilder.endMap();
|
||||||
|
jsonBuilder.endMap();
|
||||||
|
assertEquals("{\"map\":{\"a\":\"b\"}}", jsonBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMapOfCollections() throws Exception {
|
||||||
|
JsonBuilder jsonBuilder = new JsonBuilder();
|
||||||
|
jsonBuilder.beginMap();
|
||||||
|
jsonBuilder.beginMap("map");
|
||||||
|
jsonBuilder.collection("a", Arrays.asList("b", "c"));
|
||||||
|
// test comma separation
|
||||||
|
jsonBuilder.collection("d", Arrays.asList("e", "f"));
|
||||||
|
jsonBuilder.endMap();
|
||||||
|
jsonBuilder.endMap();
|
||||||
|
assertEquals("{\"map\":{\"a\":[\"b\",\"c\"],\"d\":[\"e\",\"f\"]}}", jsonBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMapOfEmptyCollections() throws Exception {
|
||||||
|
JsonBuilder jsonBuilder = new JsonBuilder();
|
||||||
|
jsonBuilder.beginMap();
|
||||||
|
jsonBuilder.beginMap("map");
|
||||||
|
jsonBuilder.collection("a", List.of());
|
||||||
|
// test comma separation
|
||||||
|
jsonBuilder.collection("b", List.of());
|
||||||
|
jsonBuilder.endMap();
|
||||||
|
jsonBuilder.endMap();
|
||||||
|
assertEquals("{\"map\":{\"a\":[],\"b\":[]}}", jsonBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCollectionOfMaps() throws Exception {
|
||||||
|
JsonBuilder jsonBuilder = new JsonBuilder();
|
||||||
|
jsonBuilder.beginMap();
|
||||||
|
jsonBuilder.beginCollection("collection");
|
||||||
|
jsonBuilder.buildMap(Map.of("a", "b"));
|
||||||
|
// test comma separation
|
||||||
|
jsonBuilder.buildMap(Map.of("c", "d"));
|
||||||
|
jsonBuilder.endCollection();
|
||||||
|
jsonBuilder.endMap();
|
||||||
|
assertEquals("{\"collection\":[{\"a\":\"b\"},{\"c\":\"d\"}]}", jsonBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCollectionOfEmptyMaps() throws Exception {
|
||||||
|
JsonBuilder jsonBuilder = new JsonBuilder();
|
||||||
|
jsonBuilder.beginMap();
|
||||||
|
jsonBuilder.beginCollection("collection");
|
||||||
|
jsonBuilder.buildMap(Map.of());
|
||||||
|
// test comma separation
|
||||||
|
jsonBuilder.buildMap(Map.of());
|
||||||
|
jsonBuilder.endCollection();
|
||||||
|
jsonBuilder.endMap();
|
||||||
|
assertEquals("{\"collection\":[{},{}]}", jsonBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCopy() throws Exception {
|
||||||
|
JsonBuilder jsonBuilder1 = new JsonBuilder();
|
||||||
|
jsonBuilder1.buildMap(Map.of("a", "b"));
|
||||||
|
JsonBuilder jsonBuilder2 = new JsonBuilder();
|
||||||
|
jsonBuilder2.buildMap(Map.of("c", "d"));
|
||||||
|
JsonBuilder jsonBuilder = new JsonBuilder();
|
||||||
|
jsonBuilder.beginCollection();
|
||||||
|
jsonBuilder.copy(jsonBuilder1);
|
||||||
|
// test comma separation
|
||||||
|
jsonBuilder.copy(jsonBuilder2);
|
||||||
|
jsonBuilder.endCollection();
|
||||||
|
assertEquals("[{\"a\":\"b\"},{\"c\":\"d\"}]", jsonBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,17 @@ package org.xbib.datastructures.yaml.tiny;
|
||||||
import org.xbib.datastructures.api.*;
|
import org.xbib.datastructures.api.*;
|
||||||
import org.xbib.datastructures.api.Builder;
|
import org.xbib.datastructures.api.Builder;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class Yaml implements DataStructure {
|
public class Yaml implements DataStructure {
|
||||||
|
|
||||||
|
private static final Yaml INSTANCE = new Yaml();
|
||||||
|
;
|
||||||
private final char separator;
|
private final char separator;
|
||||||
|
|
||||||
private Node<?> root;
|
private Node<?> root;
|
||||||
|
@ -26,6 +31,15 @@ public class Yaml implements DataStructure {
|
||||||
this.separator = separator;
|
this.separator = separator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static Map<String, Object> toMap(String yaml) throws IOException {
|
||||||
|
return (Map<String, Object>) INSTANCE.createParser().parse(new StringReader(yaml)).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toString(Map<String, Object> map) throws IOException {
|
||||||
|
return INSTANCE.createBuilder().buildMap(map).build();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Parser createParser() {
|
public Parser createParser() {
|
||||||
return new YamlParser();
|
return new YamlParser();
|
||||||
|
|
|
@ -4,9 +4,7 @@ import org.xbib.datastructures.api.Builder;
|
||||||
import org.xbib.datastructures.api.ByteSizeValue;
|
import org.xbib.datastructures.api.ByteSizeValue;
|
||||||
import org.xbib.datastructures.api.TimeValue;
|
import org.xbib.datastructures.api.TimeValue;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.io.Writer;
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -14,22 +12,22 @@ import java.util.Objects;
|
||||||
|
|
||||||
public class YamlBuilder implements Builder {
|
public class YamlBuilder implements Builder {
|
||||||
|
|
||||||
public final Writer writer;
|
public final Appendable appendable;
|
||||||
|
|
||||||
private final int indent;
|
private final int indent;
|
||||||
|
|
||||||
private State state;
|
private State state;
|
||||||
|
|
||||||
public YamlBuilder() {
|
public YamlBuilder() {
|
||||||
this(new StringWriter());
|
this(new StringBuilder());
|
||||||
}
|
}
|
||||||
|
|
||||||
public YamlBuilder(Writer writer) {
|
public YamlBuilder(Appendable appendable) {
|
||||||
this(writer, 2);
|
this(appendable, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public YamlBuilder(Writer writer, int indent) {
|
public YamlBuilder(Appendable appendable, int indent) {
|
||||||
this.writer = writer;
|
this.appendable = appendable;
|
||||||
this.indent = indent;
|
this.indent = indent;
|
||||||
this.state = new State(null, 0, Structure.MAP, false);
|
this.state = new State(null, 0, Structure.MAP, false);
|
||||||
}
|
}
|
||||||
|
@ -38,8 +36,8 @@ public class YamlBuilder implements Builder {
|
||||||
return new YamlBuilder();
|
return new YamlBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static YamlBuilder builder(Writer writer) {
|
public static YamlBuilder builder(Appendable appendable) {
|
||||||
return new YamlBuilder(writer);
|
return new YamlBuilder(appendable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -89,7 +87,7 @@ public class YamlBuilder implements Builder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Builder buildCollection(Collection<Object> collection) {
|
public Builder buildCollection(Collection<?> collection) {
|
||||||
Objects.requireNonNull(collection);
|
Objects.requireNonNull(collection);
|
||||||
beginCollection();
|
beginCollection();
|
||||||
collection.forEach(v -> {
|
collection.forEach(v -> {
|
||||||
|
@ -170,9 +168,16 @@ public class YamlBuilder implements Builder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder copy(Builder builder) throws IOException {
|
||||||
|
// TODO: no correct indent yet for copied yaml
|
||||||
|
buildValue(builder.build());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String build() {
|
public String build() {
|
||||||
return writer.toString();
|
return appendable.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildNumber(Number number) throws IOException {
|
private void buildNumber(Number number) throws IOException {
|
||||||
|
@ -188,7 +193,8 @@ public class YamlBuilder implements Builder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildString(CharSequence string, boolean escape) throws IOException {
|
private void buildString(CharSequence string, boolean escape) throws IOException {
|
||||||
String value = escape ? escapeString(string) : string.toString();
|
CharSequence charSequence = escape ? escapeString(string) : string;
|
||||||
|
String value = charSequence.toString();
|
||||||
if (!((value.startsWith("'") && value.endsWith("'")) || (value.startsWith("\"") && value.endsWith("\""))) &&
|
if (!((value.startsWith("'") && value.endsWith("'")) || (value.startsWith("\"") && value.endsWith("\""))) &&
|
||||||
value.matches(".*[?\\-#:>|$%&{}\\[\\]]+.*|[ ]+")) {
|
value.matches(".*[?\\-#:>|$%&{}\\[\\]]+.*|[ ]+")) {
|
||||||
if (value.contains("\"")) {
|
if (value.contains("\"")) {
|
||||||
|
@ -197,7 +203,7 @@ public class YamlBuilder implements Builder {
|
||||||
value = "\"" + value + "\"";
|
value = "\"" + value + "\"";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writer.write(value);
|
appendable.append(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beginKey(String k) throws IOException {
|
private void beginKey(String k) throws IOException {
|
||||||
|
@ -205,11 +211,11 @@ public class YamlBuilder implements Builder {
|
||||||
state.parent.item = false;
|
state.parent.item = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writer.write(" ".repeat((state.level - 1) * indent));
|
appendable.append(" ".repeat((state.level - 1) * indent));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void endKey(String k) throws IOException {
|
private void endKey(String k) throws IOException {
|
||||||
writer.write(": ");
|
appendable.append(": ");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beginValue(Object v) throws IOException {
|
private void beginValue(Object v) throws IOException {
|
||||||
|
@ -236,8 +242,8 @@ public class YamlBuilder implements Builder {
|
||||||
if (v instanceof Collection) {
|
if (v instanceof Collection) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writer.write(" ".repeat((state.level - 1) * indent));
|
appendable.append(" ".repeat((state.level - 1) * indent));
|
||||||
writer.write("- ");
|
appendable.append("- ");
|
||||||
state.item = true;
|
state.item = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,10 +258,10 @@ public class YamlBuilder implements Builder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeLn() throws IOException{
|
private void writeLn() throws IOException{
|
||||||
writer.write(System.lineSeparator());
|
appendable.append(System.lineSeparator());
|
||||||
}
|
}
|
||||||
|
|
||||||
private String escapeString(CharSequence string) {
|
private CharSequence escapeString(CharSequence string) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
int start = 0;
|
int start = 0;
|
||||||
int l = string.length();
|
int l = string.length();
|
||||||
|
@ -263,16 +269,16 @@ public class YamlBuilder implements Builder {
|
||||||
char c = string.charAt(i);
|
char c = string.charAt(i);
|
||||||
if (c == '"' || c < 32 || c >= 127 || c == '\\') {
|
if (c == '"' || c < 32 || c >= 127 || c == '\\') {
|
||||||
if (start < i) {
|
if (start < i) {
|
||||||
sb.append(string.toString(), start, i - start);
|
sb.append(string, start, i - start);
|
||||||
}
|
}
|
||||||
start = i;
|
start = i;
|
||||||
sb.append(escapeCharacter(c));
|
sb.append(escapeCharacter(c));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (start < l) {
|
if (start < l) {
|
||||||
sb.append(string.toString(), start, l - start);
|
sb.append(string, start, l - start);
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String escapeCharacter(char c) {
|
private static String escapeCharacter(char c) {
|
||||||
|
|
Loading…
Reference in a new issue