All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.javersion.json.JsonSerializer Maven / Gradle / Ivy

There is a newer version: 0.15.3
Show newest version
/*
 * 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