From c63fdddbdc82c3aca26590508b8b132e63d9bdeb Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 3 Dec 2024 20:56:54 +0300 Subject: [PATCH] refactor Liquid engine and add support for forLoop and capture --- .../r5/comparison/ComparisonRenderer.java | 4 +- .../hl7/fhir/r5/fhirpath/FHIRPathEngine.java | 7 + .../r5/{utils => liquid}/BaseCSVWrapper.java | 2 +- .../r5/{utils => liquid}/BaseJsonWrapper.java | 14 +- .../org/hl7/fhir/r5/liquid/GlobalObject.java | 108 ++++++++++++++ .../r5/{utils => liquid}/LiquidEngine.java | 138 +++++++++++++++++- .../main/java/org/hl7/fhir/r5/model/Base.java | 8 +- .../hl7/fhir/r5/renderers/LiquidRenderer.java | 6 +- .../hl7/fhir/r5/test/LiquidEngineTests.java | 6 +- .../org/hl7/fhir/r5/utils/LiquidJsonTest.java | 4 +- 10 files changed, 279 insertions(+), 18 deletions(-) rename org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/{utils => liquid}/BaseCSVWrapper.java (98%) rename org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/{utils => liquid}/BaseJsonWrapper.java (91%) create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/liquid/GlobalObject.java rename org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/{utils => liquid}/LiquidEngine.java (89%) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonRenderer.java index b9d29fae0..89408099d 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonRenderer.java @@ -30,13 +30,13 @@ import org.hl7.fhir.r5.fhirpath.ExpressionNode.CollectionStatus; import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IEvaluationContext; import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FunctionDetails; import org.hl7.fhir.r5.formats.IParser.OutputStyle; +import org.hl7.fhir.r5.liquid.LiquidEngine; +import org.hl7.fhir.r5.liquid.LiquidEngine.LiquidDocument; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.StringType; import org.hl7.fhir.r5.model.Tuple; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.utils.EOperationOutcome; -import org.hl7.fhir.r5.utils.LiquidEngine; -import org.hl7.fhir.r5.utils.LiquidEngine.LiquidDocument; import org.hl7.fhir.utilities.FhirPublication; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java index 838e418d9..86009cdd1 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java @@ -189,6 +189,13 @@ public class FHIRPathEngine { // the application can implement them by providing a constant resolver public interface IEvaluationContext { + public abstract class FunctionDefinition { + public abstract String name(); + public abstract FunctionDetails details(); + public abstract TypeDetails check(FHIRPathEngine engine, Object appContext, TypeDetails focus, List parameters); + public abstract List execute(FHIRPathEngine engine, Object appContext, List focus, List> parameters); + } + /** * A constant reference - e.g. a reference to a name that must be resolved in context. * The % will be removed from the constant name before this is invoked. diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/BaseCSVWrapper.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/liquid/BaseCSVWrapper.java similarity index 98% rename from org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/BaseCSVWrapper.java rename to org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/liquid/BaseCSVWrapper.java index 4462cb64a..31fd3751e 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/BaseCSVWrapper.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/liquid/BaseCSVWrapper.java @@ -1,4 +1,4 @@ -package org.hl7.fhir.r5.utils; +package org.hl7.fhir.r5.liquid; import java.util.List; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/BaseJsonWrapper.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/liquid/BaseJsonWrapper.java similarity index 91% rename from org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/BaseJsonWrapper.java rename to org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/liquid/BaseJsonWrapper.java index f4a719af3..ee25ce10d 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/BaseJsonWrapper.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/liquid/BaseJsonWrapper.java @@ -1,4 +1,4 @@ -package org.hl7.fhir.r5.utils; +package org.hl7.fhir.r5.liquid; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r5.model.Base; @@ -83,5 +83,15 @@ public class BaseJsonWrapper extends Base { } } - + + @Override + public boolean isPrimitive() { + return j.isJsonPrimitive(); + } + + + @Override + public String primitiveValue() { + return toString(); + } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/liquid/GlobalObject.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/liquid/GlobalObject.java new file mode 100644 index 000000000..10d3715ac --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/liquid/GlobalObject.java @@ -0,0 +1,108 @@ +package org.hl7.fhir.r5.liquid; + +import java.util.ArrayList; +import java.util.List; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r5.fhirpath.FHIRPathEngine; +import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IEvaluationContext.FunctionDefinition; +import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FunctionDetails; +import org.hl7.fhir.r5.fhirpath.TypeDetails; +import org.hl7.fhir.r5.fhirpath.ExpressionNode.CollectionStatus; +import org.hl7.fhir.r5.model.Base; +import org.hl7.fhir.r5.model.DateTimeType; +import org.hl7.fhir.r5.model.IntegerType; +import org.hl7.fhir.r5.model.StringType; +import org.hl7.fhir.utilities.FhirPublication; +import org.hl7.fhir.utilities.Utilities; + +import com.microsoft.schemas.office.visio.x2012.main.impl.FunctionDefTypeImpl; + +public class GlobalObject extends Base { + + private DateTimeType dt; + private StringType pathToSpec; + + public GlobalObject(DateTimeType td, StringType pathToSpec) { + super(); + this.dt = td; + this.pathToSpec = pathToSpec; + } + + @Override + public String fhirType() { + return "GlobalObject"; + } + + @Override + public String getIdBase() { + return null; + } + + @Override + public void setIdBase(String value) { + throw new Error("Read only"); + } + + @Override + public Base copy() { + return this; + } + + @Override + public FhirPublication getFHIRPublicationVersion() { + return FhirPublication.R5; + } + + public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException { + if ("dateTime".equals(name)) { + return wrap(dt); + } else if ("path".equals(name)) { + return wrap(pathToSpec); + } else { + return super.getProperty(hash, name, checkValid); + } + } + + private Base[] wrap(Base b) { + Base[] l = new Base[1]; + l[0] = b; + return l; + } + + @Override + public List executeFunction(FHIRPathEngine engine, Object appContext, List focus, String functionName, List> parameters) { + return null; + } + + public static class GlobalObjectRandomFunction extends FunctionDefinition { + + @Override + public String name() { + return "random"; + } + + @Override + public FunctionDetails details() { + return new FunctionDetails("Generate a Random Number", 1, 1); + } + + @Override + public TypeDetails check(FHIRPathEngine engine, Object appContext, TypeDetails focus, List parameters) { + if (focus.hasType("GlobalObject")) { + return new TypeDetails(CollectionStatus.SINGLETON, "integer"); + } else { + return null; + } + } + + @Override + public List execute(FHIRPathEngine engine, Object appContext, List focus, List> parameters) { + List list = new ArrayList<>(); + int scale = Utilities.parseInt(parameters.get(0).get(0).primitiveValue(), 100)+ 1; + list.add(new IntegerType((int)(Math.random() * scale))); + return list; + } + + } +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/LiquidEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/liquid/LiquidEngine.java similarity index 89% rename from org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/LiquidEngine.java rename to org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/liquid/LiquidEngine.java index 48229cb96..dc968c74d 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/LiquidEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/liquid/LiquidEngine.java @@ -1,4 +1,4 @@ -package org.hl7.fhir.r5.utils; +package org.hl7.fhir.r5.liquid; import java.util.ArrayList; import java.util.Arrays; @@ -42,22 +42,114 @@ import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.fhirpath.ExpressionNode; import org.hl7.fhir.r5.fhirpath.FHIRLexer; import org.hl7.fhir.r5.fhirpath.FHIRPathEngine; -import org.hl7.fhir.r5.fhirpath.TypeDetails; import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.ExpressionNodeWithOffset; import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IEvaluationContext; import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FunctionDetails; +import org.hl7.fhir.r5.fhirpath.TypeDetails; import org.hl7.fhir.r5.model.Base; +import org.hl7.fhir.r5.model.BooleanType; +import org.hl7.fhir.r5.model.IntegerType; +import org.hl7.fhir.r5.model.StringType; import org.hl7.fhir.r5.model.Tuple; import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.utilities.FhirPublication; import org.hl7.fhir.utilities.MarkDownProcessor; -import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.MarkDownProcessor.Dialect; +import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.i18n.I18nConstants; import org.hl7.fhir.utilities.xhtml.NodeType; import org.hl7.fhir.utilities.xhtml.XhtmlNode; public class LiquidEngine implements IEvaluationContext { + public static class LiquidForLoopObject extends Base { + + private static final long serialVersionUID = 6951452522873320076L; + private boolean first; + private int index; + private int index0; + private int rindex; + private int rindex0; + private boolean last; + private int length; + private LiquidForLoopObject parentLoop; + + + public LiquidForLoopObject(int size, int i, int offset, int limit, LiquidForLoopObject parentLoop) { + super(); + this.parentLoop = parentLoop; + if (offset == -1) { + offset = 0; + } + if (limit == -1) { + limit = size; + } + + first = i == offset; + index = i+1-offset; + index0 = i-offset; + rindex = (limit-offset) - 1 - i; + rindex0 = (limit-offset) - i; + length = limit-offset; + last = i == (limit-offset)-1; + } + + + @Override + public String getIdBase() { + return null; + } + + @Override + public void setIdBase(String value) { + throw new Error("forLoop is read only"); + } + + @Override + public Base copy() { + throw new Error("forLoop is read only"); + } + + @Override + public FhirPublication getFHIRPublicationVersion() { + return FhirPublication.R5; + } + + public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException { + switch (name) { + case "parentLoop" : return wrap(parentLoop); + case "first" : return wrap(new BooleanType(first)); + case "last" : return wrap(new BooleanType(last)); + case "index" : return wrap(new IntegerType(index)); + case "index0" : return wrap(new IntegerType(index0)); + case "rindex" : return wrap(new IntegerType(rindex)); + case "rindex0" : return wrap(new IntegerType(rindex0)); + case "length" : return wrap(new IntegerType(length)); + } + + return super.getProperty(hash, name, checkValid); + } + + private Base[] wrap(Base b) { + Base[] l = new Base[1]; + l[0] = b; + return l; + } + + + @Override + public String toString() { + return "forLoop"; + } + + + @Override + public String fhirType() { + return "ForLoop"; + } + + } + public interface ILiquidRenderingSupport { String renderForLiquid(Object appContext, Base i) throws FHIRException; } @@ -71,16 +163,18 @@ public class LiquidEngine implements IEvaluationContext { private ILiquidEngineIncludeResolver includeResolver; private ILiquidRenderingSupport renderingSupport; private MarkDownProcessor processor = new MarkDownProcessor(Dialect.COMMON_MARK); + private Map vars = new HashMap<>(); private class LiquidEngineContext { private Object externalContext; private Map loopVars = new HashMap<>(); private Map globalVars = new HashMap<>(); - public LiquidEngineContext(Object externalContext) { + public LiquidEngineContext(Object externalContext, Map vars) { super(); this.externalContext = externalContext; globalVars = new HashMap<>(); + globalVars.putAll(vars); } public LiquidEngineContext(Object externalContext, LiquidEngineContext existing) { @@ -122,13 +216,17 @@ public class LiquidEngine implements IEvaluationContext { this.renderingSupport = renderingSupport; } + public Map getVars() { + return vars; + } + public LiquidDocument parse(String source, String sourceName) throws FHIRException { return new LiquidParser(source).parse(sourceName); } public String evaluate(LiquidDocument document, Base resource, Object appContext) throws FHIRException { StringBuilder b = new StringBuilder(); - LiquidEngineContext ctxt = new LiquidEngineContext(appContext); + LiquidEngineContext ctxt = new LiquidEngineContext(appContext, vars ); for (LiquidNode n : document.body) { n.evaluate(b, resource, ctxt); } @@ -403,6 +501,7 @@ public class LiquidEngine implements IEvaluationContext { Collections.reverse(list); } int i = 0; + LiquidForLoopObject parentLoop = (LiquidForLoopObject) lctxt.globalVars.get("forLoop"); for (Base o : list) { if (offset >= 0 && i < offset) { i++; @@ -411,6 +510,8 @@ public class LiquidEngine implements IEvaluationContext { if (limit >= 0 && i == limit) { break; } + LiquidForLoopObject forloop = new LiquidForLoopObject(list.size(), i, offset, limit, parentLoop); + lctxt.globalVars.put("forLoop", forloop); if (lctxt.globalVars.containsKey(varName)) { throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_VARIABLE_ALREADY_ASSIGNED, varName)); } @@ -431,6 +532,7 @@ public class LiquidEngine implements IEvaluationContext { } i++; } + lctxt.globalVars.put("forLoop", parentLoop); } } @@ -477,6 +579,20 @@ public class LiquidEngine implements IEvaluationContext { } } + private class LiquidCapture extends LiquidNode { + private String varName; + private List body = new ArrayList<>(); + + @Override + public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException { + StringBuilder bc = new StringBuilder(); + for (LiquidNode n : body) { + n.evaluate(bc, resource, ctxt); + } + ctxt.globalVars.put(varName, new StringType(bc.toString())); + } + } + private class LiquidInclude extends LiquidNode { private String page; private Map params = new HashMap<>(); @@ -600,6 +716,8 @@ public class LiquidEngine implements IEvaluationContext { list.add(parseInclude(cnt.substring(7).trim())); else if (cnt.startsWith("assign ")) list.add(parseAssign(cnt.substring(6).trim())); + else if (cnt.startsWith("capture ")) + list.add(parseCapture(cnt.substring(7).trim())); else throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_UNKNOWN_FLOW_STMT,name, cnt)); } else { // next2() == '{' @@ -728,6 +846,16 @@ public class LiquidEngine implements IEvaluationContext { return res; } + private LiquidNode parseCapture(String cnt) throws FHIRException { + int i = 0; + while (i < cnt.length() && !Character.isWhitespace(cnt.charAt(i))) + i++; + LiquidCapture res = new LiquidCapture(); + res.varName = cnt.substring(0, i); + parseList(res.body, true, new String[] { "endcapture" }); + return res; + } + private LiquidNode parseAssign(String cnt) throws FHIRException { int i = 0; while (!Character.isWhitespace(cnt.charAt(i))) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java index 6a4b08ece..ac2a9b578 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java @@ -36,7 +36,7 @@ import java.util.Map; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IBase; -import org.hl7.fhir.r5.model.Enumerations.FHIRVersion; +import org.hl7.fhir.r5.fhirpath.FHIRPathEngine; import org.hl7.fhir.utilities.FhirPublication; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.validation.ValidationMessage; @@ -629,4 +629,10 @@ public abstract class Base implements Serializable, IBase, IElement { } public abstract FhirPublication getFHIRPublicationVersion(); + + public List executeFunction(FHIRPathEngine engine, Object appContext, List focus, String functionName, List> parameters) { + return new ArrayList<>(); + } + + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/LiquidRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/LiquidRenderer.java index a3573d6eb..29d95fc95 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/LiquidRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/LiquidRenderer.java @@ -7,14 +7,14 @@ import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.elementmodel.Element; +import org.hl7.fhir.r5.liquid.LiquidEngine; +import org.hl7.fhir.r5.liquid.LiquidEngine.ILiquidRenderingSupport; +import org.hl7.fhir.r5.liquid.LiquidEngine.LiquidDocument; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.DataType; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.ResourceWrapper; import org.hl7.fhir.r5.utils.EOperationOutcome; -import org.hl7.fhir.r5.utils.LiquidEngine; -import org.hl7.fhir.r5.utils.LiquidEngine.ILiquidRenderingSupport; -import org.hl7.fhir.r5.utils.LiquidEngine.LiquidDocument; import org.hl7.fhir.utilities.xhtml.NodeType; import org.hl7.fhir.utilities.xhtml.XhtmlComposer; import org.hl7.fhir.utilities.xhtml.XhtmlNode; diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/LiquidEngineTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/LiquidEngineTests.java index d1ee8f0f4..76c080cc6 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/LiquidEngineTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/LiquidEngineTests.java @@ -11,11 +11,11 @@ import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.collections4.map.HashedMap; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.formats.XmlParser; +import org.hl7.fhir.r5.liquid.LiquidEngine; +import org.hl7.fhir.r5.liquid.LiquidEngine.ILiquidEngineIncludeResolver; +import org.hl7.fhir.r5.liquid.LiquidEngine.LiquidDocument; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.test.utils.TestingUtilities; -import org.hl7.fhir.r5.utils.LiquidEngine; -import org.hl7.fhir.r5.utils.LiquidEngine.ILiquidEngineIncludeResolver; -import org.hl7.fhir.r5.utils.LiquidEngine.LiquidDocument; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/LiquidJsonTest.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/LiquidJsonTest.java index 3607c06d1..79a320d86 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/LiquidJsonTest.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/LiquidJsonTest.java @@ -12,11 +12,13 @@ import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.fhirpath.FHIRPathEngine; import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IEvaluationContext; import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FunctionDetails; +import org.hl7.fhir.r5.liquid.BaseJsonWrapper; +import org.hl7.fhir.r5.liquid.LiquidEngine; +import org.hl7.fhir.r5.liquid.LiquidEngine.LiquidDocument; import org.hl7.fhir.r5.fhirpath.TypeDetails; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.test.utils.TestingUtilities; -import org.hl7.fhir.r5.utils.LiquidEngine.LiquidDocument; import org.hl7.fhir.utilities.json.JsonException; import org.hl7.fhir.utilities.json.model.JsonObject; import org.hl7.fhir.utilities.json.parser.JsonParser;