From 24239ff0bd94ab4ac04f237904b0017a5858de0e Mon Sep 17 00:00:00 2001 From: The-Lum <86879521+The-Lum@users.noreply.github.com> Date: Mon, 15 Apr 2024 05:33:02 +0000 Subject: [PATCH] feat: add new JSON builtin functions (`%str2json`, `%json_add` and `%json_remove`) From those requests: - https://forum.plantuml.net/15436/allow-update-json-object-or-array-value - https://forum.plantuml.net/17474/modify-json-variables-in-preprocessor - https://forum.plantuml.net/18418/request-%25set_json_keys-function-json-writing-capabilities - #328 - #1733 --- .../sourceforge/plantuml/tim/TContext.java | 6 + .../plantuml/tim/stdlib/JsonAdd.java | 89 +++++++++++++ .../plantuml/tim/stdlib/JsonRemove.java | 91 ++++++++++++++ .../plantuml/tim/stdlib/Str2Json.java | 75 +++++++++++ .../plantuml/tim/TimTestUtils.java | 49 ++++++-- .../plantuml/tim/stdlib/JsonAddTest.java | 117 ++++++++++++++++++ .../plantuml/tim/stdlib/JsonRemoveTest.java | 86 +++++++++++++ .../plantuml/tim/stdlib/Str2JsonTest.java | 105 ++++++++++++++++ 8 files changed, 611 insertions(+), 7 deletions(-) create mode 100644 src/net/sourceforge/plantuml/tim/stdlib/JsonAdd.java create mode 100644 src/net/sourceforge/plantuml/tim/stdlib/JsonRemove.java create mode 100644 src/net/sourceforge/plantuml/tim/stdlib/Str2Json.java create mode 100644 test/net/sourceforge/plantuml/tim/stdlib/JsonAddTest.java create mode 100644 test/net/sourceforge/plantuml/tim/stdlib/JsonRemoveTest.java create mode 100644 test/net/sourceforge/plantuml/tim/stdlib/Str2JsonTest.java diff --git a/src/net/sourceforge/plantuml/tim/TContext.java b/src/net/sourceforge/plantuml/tim/TContext.java index 3319522ca..f9fb996c7 100644 --- a/src/net/sourceforge/plantuml/tim/TContext.java +++ b/src/net/sourceforge/plantuml/tim/TContext.java @@ -110,7 +110,9 @@ import net.sourceforge.plantuml.tim.stdlib.IntVal; import net.sourceforge.plantuml.tim.stdlib.InvokeProcedure; import net.sourceforge.plantuml.tim.stdlib.IsDark; import net.sourceforge.plantuml.tim.stdlib.IsLight; +import net.sourceforge.plantuml.tim.stdlib.JsonAdd; import net.sourceforge.plantuml.tim.stdlib.JsonKeyExists; +import net.sourceforge.plantuml.tim.stdlib.JsonRemove; import net.sourceforge.plantuml.tim.stdlib.Lighten; import net.sourceforge.plantuml.tim.stdlib.LoadJson; import net.sourceforge.plantuml.tim.stdlib.LogicalAnd; @@ -132,6 +134,7 @@ import net.sourceforge.plantuml.tim.stdlib.SetVariableValue; import net.sourceforge.plantuml.tim.stdlib.Size; import net.sourceforge.plantuml.tim.stdlib.SplitStr; import net.sourceforge.plantuml.tim.stdlib.SplitStrRegex; +import net.sourceforge.plantuml.tim.stdlib.Str2Json; import net.sourceforge.plantuml.tim.stdlib.StringFunction; import net.sourceforge.plantuml.tim.stdlib.Strlen; import net.sourceforge.plantuml.tim.stdlib.Strpos; @@ -216,6 +219,9 @@ public class TContext { functionsSet.addFunction(new GetAllTheme()); functionsSet.addFunction(new GetAllStdlib()); functionsSet.addFunction(new SplitStrRegex()); + functionsSet.addFunction(new Str2Json()); + functionsSet.addFunction(new JsonAdd()); + functionsSet.addFunction(new JsonRemove()); // %standard_exists_function // %str_replace // !exit diff --git a/src/net/sourceforge/plantuml/tim/stdlib/JsonAdd.java b/src/net/sourceforge/plantuml/tim/stdlib/JsonAdd.java new file mode 100644 index 000000000..21d9d77fe --- /dev/null +++ b/src/net/sourceforge/plantuml/tim/stdlib/JsonAdd.java @@ -0,0 +1,89 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009-2021, Arnaud Roques + * + * Project Info: https://plantuml.com + * + * If you like this project or if you find it useful, you can support us at: + * + * https://plantuml.com/patreon (only 1$ per month!) + * https://plantuml.com/paypal + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * + * Original Author: Arnaud Roques + * Contribution: The-Lum + * + */ +package net.sourceforge.plantuml.tim.stdlib; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.sourceforge.plantuml.json.JsonArray; +import net.sourceforge.plantuml.json.JsonObject; +import net.sourceforge.plantuml.json.JsonValue; +import net.sourceforge.plantuml.text.StringLocated; +import net.sourceforge.plantuml.tim.EaterException; +import net.sourceforge.plantuml.tim.TContext; +import net.sourceforge.plantuml.tim.TFunctionSignature; +import net.sourceforge.plantuml.tim.TMemory; +import net.sourceforge.plantuml.tim.expression.TValue; + +public class JsonAdd extends SimpleReturnFunction { + + public TFunctionSignature getSignature() { + return new TFunctionSignature("%json_add", 3); + } + + @Override + public boolean canCover(int nbArg, Set namedArgument) { + return nbArg == 2 || nbArg == 3; + } + + @Override + public TValue executeReturnFunction(TContext context, TMemory memory, StringLocated location, List values, + Map named) throws EaterException { + final TValue data = values.get(0); + if (data.isJson() == false) + throw new EaterException("Not JSON data", location); + + final JsonValue json = data.toJson(); + + if (!json.isArray() && !json.isObject()) + return data; + if (json.isArray()) { + final JsonValue value = values.get(1).toJson(); + final JsonArray array = (JsonArray) json; + array.add(value); + return TValue.fromJson(array); + } + if (json.isObject()) { + final String name = values.get(1).toString(); + final JsonValue value = values.get(2).toJson(); + final JsonObject object = (JsonObject) json; + object.add(name, value); + return TValue.fromJson(object); + } + throw new EaterException("Bad JSON type", location); + } +} diff --git a/src/net/sourceforge/plantuml/tim/stdlib/JsonRemove.java b/src/net/sourceforge/plantuml/tim/stdlib/JsonRemove.java new file mode 100644 index 000000000..f34bcf8c1 --- /dev/null +++ b/src/net/sourceforge/plantuml/tim/stdlib/JsonRemove.java @@ -0,0 +1,91 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009-2021, Arnaud Roques + * + * Project Info: https://plantuml.com + * + * If you like this project or if you find it useful, you can support us at: + * + * https://plantuml.com/patreon (only 1$ per month!) + * https://plantuml.com/paypal + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * + * Original Author: Arnaud Roques + * Contribution: The-Lum + * + */ +package net.sourceforge.plantuml.tim.stdlib; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.sourceforge.plantuml.json.JsonArray; +import net.sourceforge.plantuml.json.JsonObject; +import net.sourceforge.plantuml.json.JsonValue; +import net.sourceforge.plantuml.text.StringLocated; +import net.sourceforge.plantuml.tim.EaterException; +import net.sourceforge.plantuml.tim.TContext; +import net.sourceforge.plantuml.tim.TFunctionSignature; +import net.sourceforge.plantuml.tim.TMemory; +import net.sourceforge.plantuml.tim.expression.TValue; + +public class JsonRemove extends SimpleReturnFunction { + + public TFunctionSignature getSignature() { + return new TFunctionSignature("%json_remove", 2); + } + + @Override + public boolean canCover(int nbArg, Set namedArgument) { + return nbArg == 2; + } + + @Override + public TValue executeReturnFunction(TContext context, TMemory memory, StringLocated location, List values, + Map named) throws EaterException { + final TValue data = values.get(0); + if (data.isJson() == false) + throw new EaterException("Not JSON data", location); + + final JsonValue json = data.toJson(); + + if (!json.isArray() && !json.isObject()) + return data; + if (json.isArray()) { + final JsonArray array = (JsonArray) json; + if (values.get(1).isNumber()) { + final Integer index = values.get(1).toInt(); + if (0 <= index && index < array.size()) + array.remove(index); + } + return TValue.fromJson(array); + } + if (json.isObject()) { + final JsonObject object = (JsonObject) json; + final String name = values.get(1).toString(); + object.remove(name); + return TValue.fromJson(object); + } + throw new EaterException("Bad JSON type", location); + } +} diff --git a/src/net/sourceforge/plantuml/tim/stdlib/Str2Json.java b/src/net/sourceforge/plantuml/tim/stdlib/Str2Json.java new file mode 100644 index 000000000..5e7890499 --- /dev/null +++ b/src/net/sourceforge/plantuml/tim/stdlib/Str2Json.java @@ -0,0 +1,75 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009-2021, Arnaud Roques + * + * Project Info: https://plantuml.com + * + * If you like this project or if you find it useful, you can support us at: + * + * https://plantuml.com/patreon (only 1$ per month!) + * https://plantuml.com/paypal + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * + * Original Author: Arnaud Roques + * Contribution: The-Lum + * + */ +package net.sourceforge.plantuml.tim.stdlib; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.sourceforge.plantuml.json.Json; +import net.sourceforge.plantuml.json.JsonArray; +import net.sourceforge.plantuml.json.JsonObject; +import net.sourceforge.plantuml.json.JsonValue; +import net.sourceforge.plantuml.text.StringLocated; +import net.sourceforge.plantuml.tim.EaterException; +import net.sourceforge.plantuml.tim.TContext; +import net.sourceforge.plantuml.tim.TFunctionSignature; +import net.sourceforge.plantuml.tim.TMemory; +import net.sourceforge.plantuml.tim.expression.TValue; + +public class Str2Json extends SimpleReturnFunction { + + public TFunctionSignature getSignature() { + return new TFunctionSignature("%str2json", 1); + } + + @Override + public boolean canCover(int nbArg, Set namedArgument) { + return nbArg == 1; + } + + @Override + public TValue executeReturnFunction(TContext context, TMemory memory, StringLocated location, List values, + Map named) throws EaterException { + try { + final String value = values.get(0).toString(); + final JsonValue json = Json.parse(value); + return TValue.fromJson(json); + } catch (Throwable t) { + return TValue.fromString(""); + } + } +} diff --git a/test/net/sourceforge/plantuml/tim/TimTestUtils.java b/test/net/sourceforge/plantuml/tim/TimTestUtils.java index 36b1c50a6..0083f63fe 100644 --- a/test/net/sourceforge/plantuml/tim/TimTestUtils.java +++ b/test/net/sourceforge/plantuml/tim/TimTestUtils.java @@ -16,21 +16,28 @@ public class TimTestUtils { // Tfunc: () -> (String) public static void assertTimExpectedOutput(TFunction func, String expected) throws EaterException { - TValue tValue = func.executeReturnFunction(null, null, null, null, null); + final TValue tValue = func.executeReturnFunction(null, null, null, Collections.emptyList(), null); assertEquals(expected, tValue.toString()); } // Tfunc: (Integer) -> (String) public static void assertTimExpectedOutputFromInput(TFunction func, Integer input, String expected) throws EaterException { - List values = Collections.singletonList(TValue.fromInt(input)); - TValue tValue = func.executeReturnFunction(null, null, null, values, null); + final List values = Collections.singletonList(TValue.fromInt(input)); + final TValue tValue = func.executeReturnFunction(null, null, null, values, null); + assertEquals(expected, tValue.toString()); + } + + // Tfunc: (Integer, Integer) -> (String) + public static void assertTimExpectedOutputFromInput(TFunction func, Integer input1, Integer input2, String expected) throws EaterException { + final List values = Arrays.asList(TValue.fromInt(input1), TValue.fromInt(input2)); + final TValue tValue = func.executeReturnFunction(null, null, null, values, null); assertEquals(expected, tValue.toString()); } // Tfunc: (String) -> (String) public static void assertTimExpectedOutputFromInput(TFunction func, String input, String expected) throws EaterException { - List values = Collections.singletonList(TValue.fromString(input)); - TValue tValue = func.executeReturnFunction(null, null, null, values, null); + final List values = Collections.singletonList(TValue.fromString(input)); + final TValue tValue = func.executeReturnFunction(null, null, null, values, null); assertEquals(expected, tValue.toString()); } @@ -43,8 +50,36 @@ public class TimTestUtils { // Tfunc: (JsonValue) -> (String) public static void assertTimExpectedOutputFromInput(TFunction func, JsonValue input, String expected) throws EaterException { - List values = Collections.singletonList(TValue.fromJson(input)); - TValue tValue = func.executeReturnFunction(null, null, null, values, null); + final List values = Collections.singletonList(TValue.fromJson(input)); + final TValue tValue = func.executeReturnFunction(null, null, null, values, null); + assertEquals(expected, tValue.toString()); + } + + // Tfunc: (JsonValue, JsonValue) -> (String) + public static void assertTimExpectedOutputFromInput(TFunction func, JsonValue input1, JsonValue input2, String expected) throws EaterException { + final List values = Arrays.asList(TValue.fromJson(input1), TValue.fromJson(input2)); + final TValue tValue = func.executeReturnFunction(null, null, null, values, null); + assertEquals(expected, tValue.toString()); + } + + // Tfunc: (JsonValue, Int) -> (String) + public static void assertTimExpectedOutputFromInput(TFunction func, JsonValue input1, Integer input2, String expected) throws EaterException { + final List values = Arrays.asList(TValue.fromJson(input1), TValue.fromInt(input2)); + final TValue tValue = func.executeReturnFunction(null, null, null, values, null); + assertEquals(expected, tValue.toString()); + } + + // Tfunc: (JsonValue, String) -> (String) + public static void assertTimExpectedOutputFromInput(TFunction func, JsonValue input1, String input2, String expected) throws EaterException { + final List values = Arrays.asList(TValue.fromJson(input1), TValue.fromString(input2)); + final TValue tValue = func.executeReturnFunction(null, null, null, values, null); + assertEquals(expected, tValue.toString()); + } + + // Tfunc: (JsonValue, String, JsonValue) -> (String) + public static void assertTimExpectedOutputFromInput(TFunction func, JsonValue input1, String input2, JsonValue input3, String expected) throws EaterException { + final List values = Arrays.asList(TValue.fromJson(input1), TValue.fromString(input2), TValue.fromJson(input3)); + final TValue tValue = func.executeReturnFunction(null, null, null, values, null); assertEquals(expected, tValue.toString()); } diff --git a/test/net/sourceforge/plantuml/tim/stdlib/JsonAddTest.java b/test/net/sourceforge/plantuml/tim/stdlib/JsonAddTest.java new file mode 100644 index 000000000..141f09b4b --- /dev/null +++ b/test/net/sourceforge/plantuml/tim/stdlib/JsonAddTest.java @@ -0,0 +1,117 @@ +package net.sourceforge.plantuml.tim.stdlib; + +import static net.sourceforge.plantuml.tim.TimTestUtils.assertTimExpectedOutputFromInput; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.IndicativeSentencesGeneration; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.converter.ConvertWith; +import org.junit.jupiter.params.provider.CsvSource; + +import net.sourceforge.plantuml.json.JsonValue; +import net.sourceforge.plantuml.tim.EaterException; +import net.sourceforge.plantuml.tim.TFunction; +import test.utils.JunitUtils.StringJsonConverter; + +/** + * Tests the builtin function. + */ +@IndicativeSentencesGeneration(separator = ": ", generator = ReplaceUnderscores.class) + +class JsonAddTest { + TFunction cut = new JsonAdd(); + final String cutName = "json_add"; + final String paramTestName = "[{index}] " + cutName + "({0}, {1}) = {2}"; + + @ParameterizedTest(name = paramTestName) + @CsvSource(value = { + " [], 1, [1]", + " [0], \"a\", '[0,\"a\"]' ", + " [0], {\"a\": 123}, '[0,{\"a\":123}]' ", + " [0], [1], '[0,[1]]' ", + " '[{\"a\":[1, 2]}]', 1, '[{\"a\":[1,2]},1]' ", + + }) + void Test_with_Array_Json(@ConvertWith(StringJsonConverter.class) JsonValue input1, @ConvertWith(StringJsonConverter.class) JsonValue input2, String expected) throws EaterException { + assertTimExpectedOutputFromInput(cut, input1, input2, expected); + } + + @ParameterizedTest(name = "[{index}] " + cutName + "({0}, {1}, {2}) = {3}") + @CsvSource(value = { + " {}, a, 1, {\"a\":1}", + " {}, a, '[1,2,3]', '{\"a\":[1,2,3]}'", + " {}, a, '{\"b\":123}', '{\"a\":{\"b\":123}}'", + " {}, a, '{\"b\":\"abc\"}', '{\"a\":{\"b\":\"abc\"}}'", + " {\"z\":0}, a, 1, '{\"z\":0,\"a\":1}'", + " {\"z\":0}, a, '[1,2,3]', '{\"z\":0,\"a\":[1,2,3]}'", + " {\"z\":0}, a, '{\"b\":123}', '{\"z\":0,\"a\":{\"b\":123}}'", + " {\"z\":0}, a, '{\"b\":\"abc\"}', '{\"z\":0,\"a\":{\"b\":\"abc\"}}'", + " '{\"a\": 1, \"b\": \"two\"}', c, 3, '{\"a\":1,\"b\":\"two\",\"c\":3}'", + " '{\"a\": 1, \"b\": \"two\"}', d, '{\"da\": 1, \"db\": \"two\"}', '{\"a\":1,\"b\":\"two\",\"d\":{\"da\":1,\"db\":\"two\"}}'", + + }) + void Test_with_Object_Json(@ConvertWith(StringJsonConverter.class) JsonValue input1, String input2, @ConvertWith(StringJsonConverter.class) JsonValue input3, String expected) throws EaterException { + assertTimExpectedOutputFromInput(cut, input1, input2, input3, expected); + } + + @Nested + class Not_Nominal_Test { + @ParameterizedTest(name = paramTestName) + @CsvSource(value = { + " 0, 1, 0", + " 1, 1, 1", + " true, 1, true", + " false, 1, false", + " \"a\", 1, a", + " null, 1, null", + " 0, \"b\", 0", + " 1, \"b\", 1", + " true, \"b\", true", + " false, \"b\", false", + " \"a\", \"b\", a", + " null, \"b\", null", + " 0, true, 0", + " 1, true, 1", + " true, true, true", + " false, true, false", + " \"a\", true, a", + " null, true, null", + }) + void Test_with_Simple(@ConvertWith(StringJsonConverter.class) JsonValue input1, @ConvertWith(StringJsonConverter.class) JsonValue input2, String expected) throws EaterException { + assertTimExpectedOutputFromInput(cut, input1, input2, expected); + } + @ParameterizedTest(name = paramTestName) + @CsvSource(value = { + " 0, 1, 0", + " 1, 1, 1", + " true, 1, true", + " false, 1, false", + " \"a\", 1, a", + " null, 1, null", + }) + void Test_with_Simple(@ConvertWith(StringJsonConverter.class) JsonValue input1, Integer input2, String expected) throws EaterException { + assertTimExpectedOutputFromInput(cut, input1, input2, expected); + } + @ParameterizedTest(name = paramTestName) + @CsvSource(value = { + " 0, 1, 0", + " 1, 1, 1", + " true, 1, true", + " false, 1, false", + " \"a\", 1, a", + " null, 1, null", + " 0, b, 0", + " 1, b, 1", + " true, b, true", + " false, b, false", + " \"a\", b, a", + " null, b, null", + }) + void Test_with_Simple(@ConvertWith(StringJsonConverter.class) JsonValue input1, String input2, String expected) throws EaterException { + assertTimExpectedOutputFromInput(cut, input1, input2, expected); + } + } +} diff --git a/test/net/sourceforge/plantuml/tim/stdlib/JsonRemoveTest.java b/test/net/sourceforge/plantuml/tim/stdlib/JsonRemoveTest.java new file mode 100644 index 000000000..983255fec --- /dev/null +++ b/test/net/sourceforge/plantuml/tim/stdlib/JsonRemoveTest.java @@ -0,0 +1,86 @@ +package net.sourceforge.plantuml.tim.stdlib; + +import static net.sourceforge.plantuml.tim.TimTestUtils.assertTimExpectedOutputFromInput; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.IndicativeSentencesGeneration; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.converter.ConvertWith; +import org.junit.jupiter.params.provider.CsvSource; + +import net.sourceforge.plantuml.json.JsonValue; +import net.sourceforge.plantuml.tim.EaterException; +import net.sourceforge.plantuml.tim.TFunction; +import test.utils.JunitUtils.StringJsonConverter; + +/** + * Tests the builtin function. + */ +@IndicativeSentencesGeneration(separator = ": ", generator = ReplaceUnderscores.class) + +class JsonRemoveTest { + TFunction cut = new JsonRemove(); + final String cutName = "json_remove"; + final String paramTestName = "[{index}] " + cutName + "({0}, {1}) = {2}"; + + @ParameterizedTest(name = paramTestName) + @CsvSource(value = { + " [], -1, []", + " [], 1, []", + " [0], 0, [] ", + " [0], 1, [0] ", + " '[1,2,3]', -1, '[1,2,3]' ", // To debate... + " '[1,2,3]', 0, '[2,3]' ", + " '[1,2,3]', 1, '[1,3]' ", + " '[1,2,3]', 2, '[1,2]' ", + " '[1,2,3]', 3, '[1,2,3]' ", + " '[{\"a\":[1, 2]}, 1, \"a\"]', 2, '[{\"a\":[1,2]},1]' ", + }) + void Test_with_Array_Json(@ConvertWith(StringJsonConverter.class) JsonValue input1, Integer input2, String expected) throws EaterException { + assertTimExpectedOutputFromInput(cut, input1, input2, expected); + } + + @ParameterizedTest(name = paramTestName) + @CsvSource(value = { + " '{\"z\":0,\"a\":1}', a, '{\"z\":0}'", + " '{\"z\":0,\"a\":1}', b, '{\"z\":0,\"a\":1}'", + " '{\"a\": 1, \"b\": \"two\",\"c\":3}', c, '{\"a\":1,\"b\":\"two\"}'", + " '{\"a\":1,\"b\":\"two\",\"d\":{\"da\":1,\"db\":\"two\"}}', d, '{\"a\":1,\"b\":\"two\"}'", + + }) + void Test_with_Object_Json(@ConvertWith(StringJsonConverter.class) JsonValue input1, String input2, String expected) throws EaterException { + assertTimExpectedOutputFromInput(cut, input1, input2, expected); + } + + @Nested + class Not_Nominal_Test { + @ParameterizedTest(name = paramTestName) + @CsvSource(value = { + " [], a, []", + " '[1,2,3]', abc, '[1,2,3]' ", + " '[1,2,3]', 1, '[1,2,3]' ", + " 123, 1, 123", + " true, a, true", + " true, 1, true", + " \"abc\", a, abc", + " \"a b c\", a, a b c", + " \"a b c\", 1, a b c", + }) + void Test_with_not_good_type(@ConvertWith(StringJsonConverter.class) JsonValue input1, String input2, String expected) throws EaterException { + assertTimExpectedOutputFromInput(cut, input1, input2, expected); + } + + @ParameterizedTest(name = paramTestName) + @CsvSource(value = { + " '{\"z\":0,\"a\":1}', 5, '{\"z\":0,\"a\":1}'", + " '{\"z\":0,\"5\":1}', 5, '{\"z\":0}'", // Allow cast int -> string + " '{\"a\": 1, \"b\": \"two\",\"c\":3}', 10, '{\"a\":1,\"b\":\"two\",\"c\":3}'", + }) + void Test_with_not_good_type(@ConvertWith(StringJsonConverter.class) JsonValue input1, Integer input2, String expected) throws EaterException { + assertTimExpectedOutputFromInput(cut, input1, input2, expected); + } + } +} diff --git a/test/net/sourceforge/plantuml/tim/stdlib/Str2JsonTest.java b/test/net/sourceforge/plantuml/tim/stdlib/Str2JsonTest.java new file mode 100644 index 000000000..e8dc340b1 --- /dev/null +++ b/test/net/sourceforge/plantuml/tim/stdlib/Str2JsonTest.java @@ -0,0 +1,105 @@ +package net.sourceforge.plantuml.tim.stdlib; + +import static net.sourceforge.plantuml.tim.TimTestUtils.assertTimExpectedOutput; +import static net.sourceforge.plantuml.tim.TimTestUtils.assertTimExpectedOutputFromInput; + +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.IndicativeSentencesGeneration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.converter.ConvertWith; +import org.junit.jupiter.params.provider.CsvSource; + +import net.sourceforge.plantuml.json.JsonValue; +import net.sourceforge.plantuml.tim.EaterException; +import net.sourceforge.plantuml.tim.TFunction; + +import test.utils.JunitUtils.StringJsonConverter; + +/** + * Tests the builtin function. + */ +@IndicativeSentencesGeneration(separator = ": ", generator = ReplaceUnderscores.class) + +class Str2JsonTest { + TFunction cut = new Str2Json(); + final String cutName = "str2json"; + final String paramTestName = "[{index}] " + cutName + "({0}) = {1}"; + + @Test + void Test_without_Param() throws EaterException { + assertTimExpectedOutput(cut, ""); + } + + @ParameterizedTest(name = paramTestName) + @CsvSource(nullValues = "null", value = { + " 0, 0", + " -1, -1", + " 12, 12", + }) + void Test_with_Integer(Integer input, String expected) throws EaterException { + assertTimExpectedOutputFromInput(cut, input, expected); + } + + @ParameterizedTest(name = paramTestName) + @CsvSource(value = { + " 0, 0 ", + " 1, 1 ", + " 12, 12 ", + " -1, -1 ", + " true, true ", + " [true], [true] ", + " null, null ", + " '', '' ", + " \"\", '' ", + " a, '' ", + " \"a\", a ", + " '\"a\"', a ", + " \"a b\", a b ", + " 'a b', '' ", + " [], [] ", + " '[1, 2]', '[1,2]' ", + " '[\"a\"]', '[\"a\"]' ", + " '{\"a\":[1, 2]}', '{\"a\":[1,2]}' ", + " '[{\"a\":[1, 2]}]' , '[{\"a\":[1,2]}]' ", + " '{\"a\":\"abc\"}', {\"a\":\"abc\"} ", + " '[{\"a\":[1, 2]}, {\"b\":[3, 4]}]', '[{\"a\":[1,2]},{\"b\":[3,4]}]'", + " '{\"a\":[1, 2], \"b\":\"abc\", \"b\":true}', '{\"a\":[1,2],\"b\":\"abc\",\"b\":true}'", + " '[3, \"different\", { \"types\" : \"of values\" }]', '[3,\"different\",{\"types\":\"of values\"}]'", + " '{\"a\": 1, \"b\": \"two\"}', '{\"a\":1,\"b\":\"two\"}' ", + }) + void Test_with_String(String input, String expected) throws EaterException { + assertTimExpectedOutputFromInput(cut, input, expected); + } + + @ParameterizedTest(name = paramTestName) + @CsvSource(value = { + " 0, 0 ", + " 1, 1 ", + " 12, 12 ", + " -1, -1 ", + " true, true ", + " [true], [true] ", + " null, null ", + //" '', '' ", + //" \"\", '' ", + //" a, '' ", + //" \"a\", a ", + //" '\"a\"', a ", + //" \"a b\", a b ", + //" 'a b', a b ", + " [], [] ", + " '[1, 2]', '[1,2]' ", + " '[\"a\"]', '[\"a\"]' ", + " '{\"a\":[1, 2]}', '{\"a\":[1,2]}' ", + " '[{\"a\":[1, 2]}]' , '[{\"a\":[1,2]}]' ", + " '{\"a\":\"abc\"}', {\"a\":\"abc\"} ", + " '[{\"a\":[1, 2]}, {\"b\":[3, 4]}]', '[{\"a\":[1,2]},{\"b\":[3,4]}]'", + " '{\"a\":[1, 2], \"b\":\"abc\", \"b\":true}', '{\"a\":[1,2],\"b\":\"abc\",\"b\":true}'", + " '[3, \"different\", { \"types\" : \"of values\" }]', '[3,\"different\",{\"types\":\"of values\"}]'", + " '{\"a\": 1, \"b\": \"two\"}', '{\"a\":1,\"b\":\"two\"}' ", + }) + void Test_with_Json(@ConvertWith(StringJsonConverter.class) JsonValue input, String expected) throws EaterException { + assertTimExpectedOutputFromInput(cut, input, expected); + } +}