feat: add new JSON builtin function (`%json_merge`)

This commit is contained in:
The-Lum 2024-05-05 09:21:07 +00:00
parent dcfe17b73c
commit 3dba008497
3 changed files with 187 additions and 0 deletions

View File

@ -112,6 +112,7 @@ 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.JsonMerge;
import net.sourceforge.plantuml.tim.stdlib.JsonRemove;
import net.sourceforge.plantuml.tim.stdlib.JsonSet;
import net.sourceforge.plantuml.tim.stdlib.Lighten;
@ -224,6 +225,7 @@ public class TContext {
functionsSet.addFunction(new JsonAdd());
functionsSet.addFunction(new JsonRemove());
functionsSet.addFunction(new JsonSet());
functionsSet.addFunction(new JsonMerge());
// %standard_exists_function
// %str_replace
// !exit

View File

@ -0,0 +1,92 @@
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2024, 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 JsonMerge extends SimpleReturnFunction {
public TFunctionSignature getSignature() {
return new TFunctionSignature("%json_merge", 2);
}
@Override
public boolean canCover(int nbArg, Set<String> namedArgument) {
return nbArg == 2;
}
@Override
public TValue executeReturnFunction(TContext context, TMemory memory, StringLocated location, List<TValue> values,
Map<String, TValue> named) throws EaterException {
final TValue data0 = values.get(0);
if (!data0.isJson())
throw new EaterException("Not JSON data", location);
final TValue data1 = values.get(1);
if (!data1.isJson())
throw new EaterException("Not JSON data", location);
final JsonValue json0 = data0.toJson();
final JsonValue json1 = data1.toJson();
if ((!json0.isArray() && !json0.isObject() && !json1.isArray() && !json1.isObject())
|| ((json0.isArray() && json1.isObject()) || (json0.isObject() && json1.isArray())))
return data0;
if (json0.isArray() && json1.isArray()) {
for (JsonValue j1 : json1.asArray()) {
json0.asArray().add(j1);
}
return TValue.fromJson(json0);
}
if (json0.isObject() && json1.isObject()) {
json0.asObject().merge(json1.asObject());
return TValue.fromJson(json0);
}
throw new EaterException("Bad JSON type", location);
}
}

View File

@ -0,0 +1,93 @@
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 JsonMergeTest {
TFunction cut = new JsonMerge();
final String cutName = "json_merge";
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\"}}'",
" {\"z\":0}, '{\"z\":1}', '{\"z\":1}'",
" {\"z\":0}, '{\"z\":[1,2,3]}', '{\"z\":[1,2,3]}'",
" {\"z\":0}, '{\"z\":{\"b\":123}}', '{\"z\":{\"b\":123}}'",
" {\"z\":0}, '{\"z\":{\"b\":\"abc\"}}', '{\"z\":{\"b\":\"abc\"}}'",
" '{\"a\": 1, \"b\": \"two\"}', {\"b\":3}, '{\"a\":1,\"b\":3}'",
})
void Test_with_Object_Json(@ConvertWith(StringJsonConverter.class) JsonValue input1, @ConvertWith(StringJsonConverter.class) JsonValue input2, String expected) throws EaterException {
assertTimExpectedOutputFromInput(cut, input1, input2, expected);
}
@Nested
class Not_Nominal_Test {
@ParameterizedTest(name = paramTestName)
@CsvSource(value = {
" [], {\"a\":1}, []",
" [0], {\"a\":1}, '[0]' ",
" [0], {\"a\":1}, '[0]' ",
" [0], {\"a\":1}, '[0]' ",
" '[{\"a\":[1, 2]}]', {\"a\":1}, '[{\"a\":[1,2]}]' ",
})
void Test_with_Array_Object_Json(@ConvertWith(StringJsonConverter.class) JsonValue input1, @ConvertWith(StringJsonConverter.class) JsonValue input2, String expected) throws EaterException {
assertTimExpectedOutputFromInput(cut, input1, input2, expected);
}
@ParameterizedTest(name = paramTestName)
@CsvSource(value = {
" {}, [], {} ",
" {\"a\":1}, [], {\"a\":1} ",
" '{\"a\":[1,2,3]}', [0], '{\"a\":[1,2,3]}' ",
" '{\"z\":0,\"a\":1}', [0], '{\"z\":0,\"a\":1}' ",
" '{\"z\":0,\"a\":1}', '[{\"a\":[1, 2]}]', '{\"z\":0,\"a\":1}' ",
})
void Test_with_Object_Array_Json(@ConvertWith(StringJsonConverter.class) JsonValue input1, @ConvertWith(StringJsonConverter.class) JsonValue input2, String expected) throws EaterException {
assertTimExpectedOutputFromInput(cut, input1, input2, expected);
}
}
}