org.jgrapht.nio.json.JSONExporter Maven / Gradle / Ivy
/*
* (C) Copyright 2019-2023, Dimitrios Michail and Contributors.
*
* JGraphT : a free Java graph-theory library
*
* See the CONTRIBUTORS.md file distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the
* GNU Lesser General Public License v2.1 or later
* which is available at
* http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
*
* SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
*/
package org.jgrapht.nio.json;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import org.apache.commons.text.StringEscapeUtils;
import org.jgrapht.Graph;
import org.jgrapht.nio.Attribute;
import org.jgrapht.nio.AttributeType;
import org.jgrapht.nio.BaseExporter;
import org.jgrapht.nio.GraphExporter;
import org.jgrapht.nio.IntegerIdProvider;
/**
* Exports a graph using JSON.
*
*
* The output is one object which contains:
*
* - A member named
nodes
whose value is an array of nodes.
* - A member named
edges
whose value is an array of edges.
* - Two members named
creator
and version
for metadata.
*
*
*
* Each node contains an identifier and possibly other attributes. Similarly each edge contains the
* source and target vertices, a possible identifier and possible other attributes. All these can be
* adjusted using the setters. The default constructor constructs integer identifiers using an
* {@link IntegerIdProvider} for both vertices and edges and does not output any custom attributes.
*
* @param the graph vertex type
* @param the graph edge type
*
* @author Dimitrios Michail
*/
public class JSONExporter
extends BaseExporter
implements GraphExporter
{
/**
* Default name for the vertices collection
*/
public static final String DEFAULT_VERTICES_COLLECTION_NAME = "nodes";
/**
* Default name for the edges collection
*/
public static final String DEFAULT_EDGES_COLLECTION_NAME = "edges";
private static final String CREATOR = "JGraphT JSON Exporter";
private static final String VERSION = "1";
private String verticesCollectionName = DEFAULT_VERTICES_COLLECTION_NAME;
private String edgesCollectionName = DEFAULT_EDGES_COLLECTION_NAME;
/**
* Creates a new exporter with integers for the vertex identifiers.
*/
public JSONExporter()
{
this(new IntegerIdProvider<>());
}
/**
* Creates a new exporter.
*
* @param vertexIdProvider for generating vertex identifiers. Must not be null.
*/
public JSONExporter(Function vertexIdProvider)
{
super(vertexIdProvider);
}
/**
* Get the name used for the vertices collection in the file.
*
* @return the name used for the vertices collection in the file.
*/
public String getVerticesCollectionName()
{
return verticesCollectionName;
}
/**
* Set the name used for the vertices collection in the file.
*
* @param verticesCollectionName the name
*/
public void setVerticesCollectionName(String verticesCollectionName)
{
this.verticesCollectionName = Objects.requireNonNull(verticesCollectionName);
}
/**
* Get the name used for the edges collection in the file.
*
* @return the name used for the edges collection in the file.
*/
public String getEdgesCollectionName()
{
return edgesCollectionName;
}
/**
* Set the name used for the edges collection in the file.
*
* @param edgesCollectionName the name
*/
public void setEdgesCollectionName(String edgesCollectionName)
{
this.edgesCollectionName = Objects.requireNonNull(edgesCollectionName);
}
@Override
public void exportGraph(Graph g, Writer writer)
{
PrintWriter out = new PrintWriter(writer);
out.print('{');
/*
* Version
*/
out.print(quoted("creator"));
out.print(':');
out.print(quoted(CREATOR));
out.print(',');
out.print(quoted("version"));
out.print(':');
out.print(quoted(VERSION));
/*
* Vertices
*/
out.print(',');
out.print(quoted(verticesCollectionName));
out.print(':');
out.print('[');
boolean printComma = false;
for (V v : g.vertexSet()) {
if (!printComma) {
printComma = true;
} else {
out.print(',');
}
exportVertex(out, g, v);
}
out.print("]");
/*
* Edges
*/
out.print(',');
out.print(quoted(edgesCollectionName));
out.print(':');
out.print('[');
printComma = false;
for (E e : g.edgeSet()) {
if (!printComma) {
printComma = true;
} else {
out.print(',');
}
exportEdge(out, g, e);
}
out.print("]");
out.print('}');
out.flush();
}
private void exportVertex(PrintWriter out, Graph g, V v)
{
String vertexId = vertexIdProvider.apply(v);
out.print('{');
out.print(quoted("id"));
out.print(':');
out.print(quoted(vertexId));
exportVertexAttributes(out, g, v);
out.print('}');
}
private void exportEdge(PrintWriter out, Graph g, E e)
{
V source = g.getEdgeSource(e);
String sourceId = vertexIdProvider.apply(source);
V target = g.getEdgeTarget(e);
String targetId = vertexIdProvider.apply(target);
out.print('{');
edgeIdProvider.ifPresent(p -> {
String edgeId = p.apply(e);
if (edgeId != null) {
out.print(quoted("id"));
out.print(':');
out.print(quoted(edgeId));
out.print(',');
}
});
out.print(quoted("source"));
out.print(':');
out.print(quoted(sourceId));
out.print(',');
out.print(quoted("target"));
out.print(':');
out.print(quoted(targetId));
exportEdgeAttributes(out, g, e);
out.print('}');
}
private void exportVertexAttributes(PrintWriter out, Graph g, V v)
{
if (!vertexAttributeProvider.isPresent()) {
return;
}
vertexAttributeProvider
.get().apply(v).entrySet().stream().filter(e -> !e.getKey().equals("id"))
.forEach(entry -> {
out.print(",");
out.print(quoted(entry.getKey()));
out.print(":");
outputValue(out, entry.getValue());
});
}
private void exportEdgeAttributes(PrintWriter out, Graph g, E e)
{
if (!edgeAttributeProvider.isPresent()) {
return;
}
Set forbidden = Set.of("id", "source", "target");
edgeAttributeProvider
.get().apply(e).entrySet().stream().filter(entry -> !forbidden.contains(entry.getKey()))
.forEach(entry -> {
out.print(",");
out.print(quoted(entry.getKey()));
out.print(":");
outputValue(out, entry.getValue());
});
}
private void outputValue(PrintWriter out, Attribute value)
{
AttributeType type = value.getType();
if (type.equals(AttributeType.BOOLEAN)) {
boolean booleanValue = Boolean.parseBoolean(value.getValue());
out.print(booleanValue ? "true" : "false");
} else if (type.equals(AttributeType.INT)) {
out.print(Integer.parseInt(value.getValue()));
} else if (type.equals(AttributeType.LONG)) {
out.print(Long.parseLong(value.getValue()));
} else if (type.equals(AttributeType.FLOAT)) {
float floatValue = Float.parseFloat(value.getValue());
if (!Float.isFinite(floatValue)) {
throw new IllegalArgumentException("Infinity and NaN not allowed in JSON");
}
out.print(floatValue);
} else if (type.equals(AttributeType.DOUBLE)) {
double doubleValue = Double.parseDouble(value.getValue());
if (!Double.isFinite(doubleValue)) {
throw new IllegalArgumentException("Infinity and NaN not allowed in JSON");
}
out.print(doubleValue);
} else {
out.print(quoted(value.toString()));
}
}
private String quoted(final String s)
{
return "\"" + StringEscapeUtils.escapeJson(s) + "\"";
}
}