![JAR search and dependency download from the Maven repository](/logo.png)
com.github.jlangch.venice.impl.functions.JsonFunctions Maven / Gradle / Ivy
/* __ __ _
* \ \ / /__ _ __ (_) ___ ___
* \ \/ / _ \ '_ \| |/ __/ _ \
* \ / __/ | | | | (_| __/
* \/ \___|_| |_|_|\___\___|
*
*
* Copyright 2017-2024 Venice
*
* 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 com.github.jlangch.venice.impl.functions;
import static com.github.jlangch.venice.impl.types.Constants.Nil;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.Path;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import com.github.jlangch.venice.VncException;
import com.github.jlangch.venice.impl.types.Constants;
import com.github.jlangch.venice.impl.types.VncBoolean;
import com.github.jlangch.venice.impl.types.VncFunction;
import com.github.jlangch.venice.impl.types.VncKeyword;
import com.github.jlangch.venice.impl.types.VncString;
import com.github.jlangch.venice.impl.types.VncVal;
import com.github.jlangch.venice.impl.types.collections.VncHashMap;
import com.github.jlangch.venice.impl.types.collections.VncList;
import com.github.jlangch.venice.impl.types.util.Coerce;
import com.github.jlangch.venice.impl.types.util.Types;
import com.github.jlangch.venice.impl.util.ArityExceptions;
import com.github.jlangch.venice.impl.util.SymbolMapBuilder;
import com.github.jlangch.venice.impl.util.json.VncJsonReader;
import com.github.jlangch.venice.impl.util.json.VncJsonWriter;
import com.github.jlangch.venice.nanojson.JsonAppendableWriter;
import com.github.jlangch.venice.nanojson.JsonParser;
import com.github.jlangch.venice.nanojson.JsonReader;
import com.github.jlangch.venice.nanojson.JsonWriter;
public class JsonFunctions {
///////////////////////////////////////////////////////////////////////////
// JSON
///////////////////////////////////////////////////////////////////////////
public static VncFunction write_str =
new VncFunction(
"json/write-str",
VncFunction
.meta()
.arglists(
"(json/write-str val & options)")
.doc(
"Writes the val to a JSON string.\n\n" +
"Options: \n\n" +
"| :pretty b | Enables/disables pretty printing. " +
" Defaults to false. |\n" +
"| :decimal-as-double b | If true emit a decimal as double else as string. " +
" Defaults to false. |")
.examples(
"(json/write-str {:a 100 :b 100})",
"(json/write-str {:a 100 :b 100} :pretty true)")
.seeAlso(
"json/read-str", "json/spit", "json/slurp", "json/pretty-print")
.build()
) {
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertMinArity(this, args, 1);
final VncVal val = args.first();
if (val == Nil) {
return Nil;
}
else {
final VncHashMap options = VncHashMap.ofAll(args.slice(1));
final boolean prettyPrint = isTrueOption(options, "pretty");
final boolean decimalAsDouble = isTrueOption(options, "decimal-as-double");
final StringBuilder sb = new StringBuilder();
final JsonAppendableWriter writer = prettyPrint
? JsonWriter.indent(INDENT).on(sb)
: JsonWriter.on(sb);
new VncJsonWriter(writer, decimalAsDouble).write(val).done();
return new VncString(sb.toString());
}
}
private static final long serialVersionUID = -1848883965231344442L;
};
public static VncFunction spit =
new VncFunction(
"json/spit",
VncFunction
.meta()
.arglists(
"(json/spit out val & options)")
.doc(
"Spits the JSON converted val to the output. \n\n" +
"The out may be a: \n\n" +
" * `java.io.File`, e.g: `(io/file \"/temp/foo.json\")` \n" +
" * `java.nio.Path` \n" +
" * `java.io.OutputStream` \n" +
" * `java.io.Writer` \n\n" +
"Options: \n\n" +
"| :pretty b | Enables/disables pretty printing. " +
" Defaults to false. |\n" +
"| :decimal-as-double b | If true emit a decimal as double else as string. " +
" Defaults to false. |\n" +
"| :encoding e | e.g :encoding :utf-8, defaults to :utf-8 |")
.examples(
"(try-with [out (io/bytebuf-out-stream)] \n" +
" (json/spit out {:a 100 :b 100 :c [10 20 30]}) \n" +
" (flush out) \n" +
" (bytebuf-to-string @out :utf-8)) ")
.seeAlso(
"json/write-str", "json/read-str", "json/slurp", "json/pretty-print")
.build()
) {
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertMinArity(this, args, 2);
sandboxFunctionCallValidation();
final Object out = Coerce.toVncJavaObject(args.first()).getDelegate();
final VncVal val = args.second();
if (val == Nil) {
return Nil;
}
else {
final VncHashMap options = VncHashMap.ofAll(args.slice(2));
final boolean prettyPrint = isTrueOption(options, "pretty");
final boolean decimalAsDouble = isTrueOption(options, "decimal-as-double");
final String encoding = encoding(options.get(new VncKeyword("encoding")));
if (out instanceof File || out instanceof Path) {
// Delegate to 'io/file-out-stream' for sandbox validation
final OutputStream fileOS = Coerce.toVncJavaObject(
IOFunctionsStreams.io_file_out_stream.applyOf(args.first()),
OutputStream.class);
try (BufferedWriter wr = new BufferedWriter(new OutputStreamWriter(fileOS, encoding))) {
final JsonAppendableWriter writer = prettyPrint
? JsonWriter.indent(INDENT).on(wr)
: JsonWriter.on(wr);
new VncJsonWriter(writer, decimalAsDouble).write(val).done();
}
catch(Exception ex) {
throw new VncException("Function 'json/spit'. Failed to spit JSON to File", ex);
}
}
else if (out instanceof PrintStream) {
final JsonAppendableWriter writer = prettyPrint
? JsonWriter.indent(INDENT).on((PrintStream)out)
: JsonWriter.on((PrintStream)out);
new VncJsonWriter(writer, decimalAsDouble).write(val).done();
}
else if (out instanceof OutputStream) {
try (BufferedWriter wr = new BufferedWriter(new OutputStreamWriter((OutputStream)out, encoding))) {
final JsonAppendableWriter writer = prettyPrint
? JsonWriter.indent(INDENT).on(wr)
: JsonWriter.on(wr);
new VncJsonWriter(writer, decimalAsDouble).write(val).done();
}
catch(Exception ex) {
throw new VncException("Function 'json/spit'. Failed to spit JSON to File", ex);
}
}
else if (out instanceof Writer) {
final JsonAppendableWriter writer = prettyPrint
? JsonWriter.indent(INDENT).on((Writer)out)
: JsonWriter.on((Writer)out);
new VncJsonWriter(writer, decimalAsDouble).write(val).done();
}
else {
throw new VncException(String.format(
"Function 'json/spit' does not allow %s as out",
Types.getType(args.first())));
}
return Nil;
}
}
private static final long serialVersionUID = -1848883965231344442L;
};
public static VncFunction read_str =
new VncFunction(
"json/read-str",
VncFunction
.meta()
.arglists("(json/read-str s & options)")
.doc(
"Reads a JSON string and returns it as a Venice datatype.\n\n" +
"Options: \n\n" +
"| :key-fn fn | Single argument function called on JSON property names; " +
" return value will replace the property names in the output. " +
" Default is 'identity', use 'keyword' to get keyword " +
" properties. |\n" +
"| :value-fn fn | Function to transform values in JSON objects in " +
" the output. For each JSON property, value-fn is called with " +
" two arguments: the property name (transformed by key-fn) and " +
" the value. The return value of value-fn will replace the value " +
" in the output. The default value-fn returns the value unchanged. |\n" +
"| :decimal b | If true use BigDecimal for decimal numbers instead of Double. " +
" Default is false. |")
.examples(
"(json/read-str (json/write-str {:a 100 :b 100}))",
"(json/read-str (json/write-str {:a 100 :b 100}) :key-fn keyword)",
"(json/read-str (json/write-str {:a 100 :b 100}) \n" +
" :value-fn (fn [k v] (if (== \"a\" k) (inc v) v)))")
.seeAlso(
"json/write-str", "json/spit", "json/slurp", "json/pretty-print")
.build()
) {
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertMinArity(this, args, 1);
final VncVal val = args.first();
if (val == Nil) {
return Nil;
}
else {
try {
final VncString s = Coerce.toVncString(val);
final VncHashMap options = VncHashMap.ofAll(args.slice(1));
final VncFunction key_fn = getFunctionOption(options, "key-fn");
final VncFunction value_fn = getFunctionOption(options, "value-fn");
final boolean toDecimal = isTrueOption(options, "decimal");
final Function keyFN =
key_fn == null ? null : (key) -> key_fn.apply(VncList.of(key));
final BiFunction valueFN =
value_fn == null ? null : (k, v) -> value_fn.apply(VncList.of(k, v));
return new VncJsonReader(
JsonReader.from(s.getValue()), keyFN, valueFN, toDecimal).read();
}
catch(Exception ex) {
throw new VncException("Function 'json/read-str'. Failed to read JSON string", ex);
}
}
}
private static final long serialVersionUID = -1848883965231344442L;
};
public static VncFunction slurp =
new VncFunction(
"json/slurp",
VncFunction
.meta()
.arglists("(json/slurp source & options)")
.doc(
"Slurps a JSON data from a source and returns it as a Venice \n" +
"data. \n\n" +
"The source may be a: \n\n" +
" * `java.io.File`, e.g: `(io/file \"/temp/foo.json\")` \n" +
" * `java.nio.Path` \n" +
" * `java.io.InputStream` \n" +
" * `java.io.Reader` \n\n" +
"Options: \n\n" +
"| :key-fn fn | Single-argument function called on JSON property " +
" names; return value will replace the property names " +
" in the output. Default is 'identity', use 'keyword' " +
" to get keyword properties. |\n" +
"| :value-fn fn | Function to transform values in JSON objects in " +
" the output. For each JSON property, value-fn is " +
" called with two arguments: the property name " +
" (transformed by key-fn) and the value. The return " +
" value of value-fn will replace the value in the output. " +
" The default value-fn returns the value unchanged. |\n" +
"| :decimal b | If true use BigDecimal for decimal numbers instead " +
" of Double. Default is false. |\n" +
"| :encoding e | e.g :encoding :utf-8, defaults to :utf-8 |")
.examples(
"(let [json (json/write-str {:a 100 :b 100 :c 1.233})] \n" +
" (try-with [in (io/string-reader json)] \n" +
" (pr-str (json/slurp in)))) ",
"(let [json (json/write-str {:a 100 :b 100 :c 1.233})] \n" +
" (try-with [in (io/string-reader json)] \n" +
" (pr-str (json/slurp in :decimal true :key-fn keyword)))) ")
.seeAlso(
"json/write-str", "json/read-str", "json/spit", "json/pretty-print")
.build()
) {
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertMinArity(this, args, 1);
sandboxFunctionCallValidation();
final VncVal val = args.first();
if (val == Nil) {
return Nil;
}
else {
try {
final Object in = Coerce.toVncJavaObject(args.first()).getDelegate();
final VncHashMap options = VncHashMap.ofAll(args.rest());
final VncFunction key_fn = getFunctionOption(options, "key-fn");
final VncFunction value_fn = getFunctionOption(options, "value-fn");
final boolean toDecimal = isTrueOption(options, "decimal");
final String encoding = encoding(options.get(new VncKeyword("encoding")));
final Function keyFN =
key_fn == null ? null : (key) -> key_fn.apply(VncList.of(key));
final BiFunction valueFN =
value_fn == null ? null : (k, v) -> value_fn.apply(VncList.of(k, v));
if (in instanceof File || in instanceof Path) {
// Delegate to 'io/file-in-stream' for sandbox validation
final InputStream fileIS = Coerce.toVncJavaObject(
IOFunctionsStreams.io_file_in_stream.applyOf(args.first()),
InputStream.class);
try (BufferedReader br = new BufferedReader(new InputStreamReader(fileIS, encoding))) {
return new VncJsonReader(JsonReader.from(br), keyFN, valueFN, toDecimal).read();
}
}
else if (in instanceof InputStream) {
try (BufferedReader br = new BufferedReader(new InputStreamReader((InputStream)in, encoding))) {
return new VncJsonReader(JsonReader.from(br), keyFN, valueFN, toDecimal).read();
}
}
else if (in instanceof Reader) {
try (Reader rd = (Reader)in) {
return new VncJsonReader(JsonReader.from(rd), keyFN, valueFN, toDecimal).read();
}
}
else {
throw new VncException(String.format(
"Function 'json/slurp' does not allow %s as source!",
Types.getType(args.first())));
}
}
catch(Exception ex) {
throw new VncException("Function 'json/slurp'. Failed to parse JSON", ex);
}
}
}
private static final long serialVersionUID = -1848883965231344442L;
};
public static VncFunction pretty_print =
new VncFunction(
"json/pretty-print",
VncFunction
.meta()
.arglists(
"(json/pretty-print s & options)")
.doc(
"Pretty prints a JSON string\n\n" +
"Options: \n\n" +
"| :indent s | The indent for indented output. Must contain spaces or " +
" tabs only. Defaults to two spaces. |")
.examples(
"(-> (json/write-str {:a 100 :b 100 :c [1 2 3]}) \n" +
" (json/pretty-print) \n" +
" (println)) ",
"(-> (json/write-str {:a 100 :b 100 :c [1 2 {:x 7 :y 8}] :d {:z 9}}) \n" +
" (json/pretty-print :indent \" \") \n" +
" (println)) ")
.seeAlso(
"json/write-str", "json/read-str", "json/spit",
"json/slurp")
.build()
) {
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertMinArity(this, args, 1);
final VncVal val = args.first();
if (val == Nil) {
return Nil;
}
else {
try {
final VncString s = Coerce.toVncString(val);
final VncHashMap options = VncHashMap.ofAll(args.rest());
final String indent = Coerce.toVncString(options.get(
new VncKeyword("indent"),
new VncString(" ")))
.getValue();
final Object o = JsonParser.any().from(s.getValue());
return new VncString(
JsonWriter.indent(indent)
.string()
.value(o)
.done());
}
catch(Exception ex) {
throw new VncException("Function 'json/pretty-print'. Failed to pretty print JSON", ex);
}
}
}
private static final long serialVersionUID = -1848883965231344442L;
};
private static boolean isTrueOption(final VncHashMap options, final String optionName) {
return VncBoolean.isTrue(options.get(new VncKeyword(optionName),VncBoolean.False));
}
private static VncFunction getFunctionOption(final VncHashMap options, final String optionName) {
final VncVal val = options.get(new VncKeyword(optionName));
if (val == Constants.Nil) {
return null;
}
else {
final VncFunction fn = Coerce.toVncFunction(val);
fn.sandboxFunctionCallValidation();
return fn;
}
}
private static String encoding(final VncVal enc) {
return enc == Nil
? "UTF-8"
: Types.isVncKeyword(enc)
? Coerce.toVncKeyword(enc).getValue()
: Coerce.toVncString(enc).getValue();
}
private static final String INDENT = " ";
///////////////////////////////////////////////////////////////////////////
// types_ns is namespace of type functions
///////////////////////////////////////////////////////////////////////////
public static final Map ns =
new SymbolMapBuilder()
.add(write_str)
.add(spit)
.add(read_str)
.add(slurp)
.add(pretty_print)
.toMap();
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy