
org.javersion.json.JsonSerializer Maven / Gradle / Ivy
/*
* Copyright 2014 Samppa Saarela
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.javersion.json;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.util.LinkedHashMap;
import java.util.Map;
import org.javersion.core.Persistent;
import org.javersion.path.PropertyPath;
import org.javersion.path.NodeId;
import org.javersion.path.PropertyTree;
import org.javersion.path.Schema;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
/**
* Properties starting with _ are considered metadata.
* {
* "_type": "Type",
* "_revs": ["qwer1234-qwer1234", "asdf5678-asdf5678"],
* "_id": "12345677889"
* }
*/
public class JsonSerializer {
public static class JsonPaths {
public Map meta = new LinkedHashMap<>();
public Map properties = new LinkedHashMap<>();
}
public static class Config {
public boolean serializeNulls = true;
public boolean lenient = false;
public String indent = "";
public Config() {}
public Config(boolean serializeNulls, boolean lenient, String indent) {
this.serializeNulls = serializeNulls;
this.lenient = lenient;
this.indent = indent;
}
}
public static final String TYPE_FIELD = "_type";
public static final String META_PREFIX = "_";
private final Schema> schemaRoot;
private final Config config;
public JsonSerializer() {
this(null);
}
public JsonSerializer(Schema schemaRoot) {
this(new Config(), schemaRoot);
}
public JsonSerializer(Config config, Schema> schemaRoot) {
this.config = config;
this.schemaRoot = schemaRoot;
}
public JsonPaths parse(String json) {
JsonPaths paths = new JsonPaths();
try (JsonReader jsonReader = newJsonReader(json)) {
toMap(PropertyPath.ROOT, jsonReader, paths.meta, paths.properties);
} catch (IOException e) {
throw new RuntimeException(e);
}
return paths;
}
private JsonReader newJsonReader(String json) {
JsonReader reader = new JsonReader(new StringReader(json));
reader.setLenient(config.lenient);
return reader;
}
public String serialize(Map map) {
PropertyTree tree = PropertyTree.build(map.keySet());
StringWriter stringWriter = new StringWriter();
try (JsonWriter jsonWriter = newJsonWriter(stringWriter)) {
toJson(tree, map, jsonWriter);
} catch (IOException e) {
throw new RuntimeException(e);
}
return stringWriter.toString();
}
private JsonWriter newJsonWriter(StringWriter stringWriter) {
JsonWriter writer = new JsonWriter(stringWriter);
writer.setIndent(config.indent);
writer.setLenient(config.lenient);
writer.setSerializeNulls(config.serializeNulls);
return writer;
}
private void toJson(PropertyTree tree, Map map, JsonWriter writer) throws IOException {
Object value = map.get(tree.path);
switch (JsonType.getType(value)) {
case NULL:
writer.nullValue();
break;
case STRING:
writer.value((String) value);
break;
case BOOLEAN:
writer.value((Boolean) value);
break;
case NUMBER:
writer.value((Number) value);
break;
case ARRAY:
writer.beginArray();
Map childrenMap = tree.getChildrenMap();
int nonNullElements = 0;
for (int i=0; nonNullElements < childrenMap.size(); i++) {
PropertyTree child = childrenMap.get(NodeId.index(i));
if (child != null) {
nonNullElements++;
toJson(child, map, writer);
} else {
writer.nullValue();
}
}
writer.endArray();
break;
case OBJECT:
writer.beginObject();
String typeAlias = ((Persistent.Object) value).type;
if (!Persistent.GENERIC_TYPE.equals(typeAlias)) {
writer.name(TYPE_FIELD).value(typeAlias);
}
for (PropertyTree child : tree.getChildren()) {
NodeId nodeId = child.getNodeId();
writer.name(nodeId.toString());
toJson(child, map, writer);
}
writer.endObject();
break;
}
}
private void toMap(PropertyPath path, JsonReader reader, Map meta, Map properties) throws IOException {
switch (reader.peek()) {
case BEGIN_OBJECT:
reader.beginObject();
boolean map = isMap(path);
while (reader.hasNext()) {
String property = reader.nextName();
PropertyPath propertyPath;
if (map) {
propertyPath = path.key(property);
} else {
propertyPath = path.propertyOrKey(property);
}
if (property.startsWith(META_PREFIX)) {
toMap(propertyPath, reader, meta, meta);
} else {
toMap(propertyPath, reader, meta, properties);
}
}
String type = getType(path, meta);
properties.put(path, Persistent.object(type));
reader.endObject();
break;
case BEGIN_ARRAY:
properties.put(path, Persistent.array());
reader.beginArray();
int i=0;
while (reader.hasNext()) {
toMap(path.index(i++), reader, meta, properties);
}
reader.endArray();
break;
case STRING:
properties.put(path, reader.nextString());
break;
case NUMBER:
properties.put(path, new BigDecimal(reader.nextString()));
break;
case BOOLEAN:
properties.put(path, reader.nextBoolean());
break;
case NULL:
reader.nextNull();
properties.put(path, null);
break;
default: // others ignored
}
}
// TODO: Use Schema for real!
private boolean isMap(PropertyPath path) {
if (schemaRoot != null) {
Schema schema = this.schemaRoot.find(path);
return schema != null && (schema.hasChild(NodeId.ANY_KEY) || schema.hasChild(NodeId.ANY));
}
return false;
}
private String getType(PropertyPath path, Map meta) {
Object typeObject = meta.get(path.property(TYPE_FIELD));
if (typeObject != null) {
return typeObject.toString();
} else {
return Persistent.GENERIC_TYPE;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy