From 5a659c3fa9a90885b2ce97ae8357478722919a23 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Wed, 10 Jan 2024 11:23:58 +0530 Subject: [PATCH 01/27] Implement base for the jsondata module --- .gitattributes | 9 + .gitignore | 24 ++ ballerina/Ballerina.toml | 19 ++ ballerina/Dependencies.toml | 28 ++ ballerina/Package.md | 2 + ballerina/build.gradle | 118 +++++++++ ballerina/init.bal | 0 ballerina/json_api.bal | 34 +++ build-config/checkstyle/build.gradle | 48 ++++ build-config/resources/Ballerina.toml | 19 ++ build.gradle | 92 +++++++ gradle.properties | 15 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 63375 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 248 ++++++++++++++++++ gradlew.bat | 92 +++++++ native/build.gradle | 77 ++++++ .../stdlib/data/jsondata/json/Native.java | 41 +++ native/src/main/java/module-info.java | 25 ++ .../data/xmldata-native/resource-config.json | 6 + settings.gradle | 37 +++ 21 files changed, 941 insertions(+) create mode 100644 .gitattributes create mode 100644 ballerina/Ballerina.toml create mode 100644 ballerina/Dependencies.toml create mode 100644 ballerina/Package.md create mode 100644 ballerina/build.gradle create mode 100644 ballerina/init.bal create mode 100644 ballerina/json_api.bal create mode 100644 build-config/checkstyle/build.gradle create mode 100644 build-config/resources/Ballerina.toml create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 native/build.gradle create mode 100644 native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java create mode 100644 native/src/main/java/module-info.java create mode 100644 native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/data/xmldata-native/resource-config.json create mode 100644 settings.gradle diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..097f9f9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/.gitignore b/.gitignore index 524f096..8262b3f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ # Package Files # *.jar +!gradle/wrapper/gradle-wrapper.jar *.war *.nar *.ear @@ -22,3 +23,26 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* replay_pid* + +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build + +.gradle/ +target +bin/ + +# IDEA Files +.idea/ +*.iml +*.ipr +*.iws + +# MacOS +*.DS_Store + +# Ballerina +velocity.log* +*Ballerina.lock diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml new file mode 100644 index 0000000..997dc5d --- /dev/null +++ b/ballerina/Ballerina.toml @@ -0,0 +1,19 @@ +[package] +org = "ballerina" +name = "jsondata" +version = "0.1.0" +authors = ["Ballerina"] +keywords = ["json"] +repository = "https://github.com/ballerina-platform/module-ballerina.jsondata" +#icon = "icon.png" +license = ["Apache-2.0"] +distribution = "2201.8.1" + +[platform.java17] +graalvmCompatible = true + +[[platform.java17.dependency]] +groupId = "io.ballerina.stdlib" +artifactId = "jsondata-native" +version = "0.1.0" +path = "../native/build/libs/data.jsondata-native-0.1.0-SNAPSHOT.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml new file mode 100644 index 0000000..71c078e --- /dev/null +++ b/ballerina/Dependencies.toml @@ -0,0 +1,28 @@ +# AUTO-GENERATED FILE. DO NOT MODIFY. + +# This file is auto-generated by Ballerina for managing dependency versions. +# It should not be modified by hand. + +[ballerina] +dependencies-toml-version = "2" +distribution-version = "2201.8.1" + +[[package]] +org = "ballerina" +name = "jballerina.java" +version = "0.0.0" +modules = [ + {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "jsondata" +version = "0.1.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "jsondata", moduleName = "jsondata"} +] + diff --git a/ballerina/Package.md b/ballerina/Package.md new file mode 100644 index 0000000..f2bc4a0 --- /dev/null +++ b/ballerina/Package.md @@ -0,0 +1,2 @@ +# module-ballerina-data.jsondata +The Ballerina JSON Data Library is a comprehensive toolkit designed to facilitate the handling and manipulation of JSON data within Ballerina applications. It streamlines the process of converting JSON data to native Ballerina data types, enabling developers to work with JSON content seamlessly and efficiently. diff --git a/ballerina/build.gradle b/ballerina/build.gradle new file mode 100644 index 0000000..5027152 --- /dev/null +++ b/ballerina/build.gradle @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * 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. + * + */ + +import org.apache.tools.ant.taskdefs.condition.Os + +buildscript { + repositories { + maven { + url = 'https://maven.pkg.github.com/ballerina-platform/plugin-gradle' + credentials { + username System.getenv("packageUser") + password System.getenv("packagePAT") + } + } + } + dependencies { + classpath "io.ballerina:plugin-gradle:${project.ballerinaGradlePluginVersion}" + } +} + +description = 'Ballerina - Data JSON Module' + +def packageName = "data.jsondata" +def packageOrg = "ballerina" + +def tomlVersion = stripBallerinaExtensionVersion("${project.version}") +def ballerinaTomlFilePlaceHolder = new File("${project.rootDir}/build-config/resources/Ballerina.toml") +def ballerinaTomlFile = new File("$project.projectDir/Ballerina.toml") + +def stripBallerinaExtensionVersion(String extVersion) { + if (extVersion.matches(project.ext.timestampedVersionRegex)) { + def splitVersion = extVersion.split('-'); + if (splitVersion.length > 3) { + def strippedValues = splitVersion[0..-4] + return strippedValues.join('-') + } else { + return extVersion + } + } else { + return extVersion.replace("${project.ext.snapshotVersion}", "") + } +} + +apply plugin: 'io.ballerina.plugin' + +ballerina { + packageOrganization = packageOrg + module = packageName + langVersion = ballerinaLangVersion +} + +task updateTomlFiles { + doLast { + def newConfig = ballerinaTomlFilePlaceHolder.text.replace("@project.version@", project.version) + newConfig = newConfig.replace("@toml.version@", tomlVersion) + ballerinaTomlFile.text = newConfig + } +} + +task commitTomlFiles { + doLast { + project.exec { + ignoreExitValue true + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + commandLine 'cmd', '/c', "git commit -m \"[Automated] Update the native jar versions\" Ballerina.toml Dependencies.toml" + } else { + commandLine 'sh', '-c', "git commit -m '[Automated] Update the native jar versions' Ballerina.toml Dependencies.toml" + } + } + } +} + +publishing { + publications { + maven(MavenPublication) { + artifact source: createArtifactZip, extension: 'zip' + } + } + + repositories { + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/ballerina-platform/module-${packageOrg}-${packageName}") + credentials { + username = System.getenv("publishUser") + password = System.getenv("publishPAT") + } + } + } +} + +task deleteDependencyTomlFiles { + if (project.hasProperty("deleteDependencies")) { + delete "${project.projectDir}/Dependencies.toml" + } +} + +updateTomlFiles.dependsOn copyStdlibs + +build.dependsOn "generatePomFileForMavenPublication" +build.dependsOn ":${packageName}-native:build" +build.dependsOn deleteDependencyTomlFiles + +test.dependsOn ":${packageName}-native:build" diff --git a/ballerina/init.bal b/ballerina/init.bal new file mode 100644 index 0000000..e69de29 diff --git a/ballerina/json_api.bal b/ballerina/json_api.bal new file mode 100644 index 0000000..075879b --- /dev/null +++ b/ballerina/json_api.bal @@ -0,0 +1,34 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you 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. + +import ballerina/jballerina.java; + +public isolated function fromJsonWithType(json v, Options options = {}, typedesc t = <>) + returns t|Error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; + +public isolated function fromJsonStringWithType(string|byte[]|stream s, Options options = {}, typedesc t = <>) + returns t|Error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; + +# Represent the options that can be used for filtering in the projection. +# +# + numericPreference - field description +public type Options record { + typedesc numericPreference = decimal; +}; + +# Represents the error type of the ballerina/data.jsondata module. This error type represents any error that can occur +# during the execution of jsondata APIs. +public type Error distinct error; diff --git a/build-config/checkstyle/build.gradle b/build-config/checkstyle/build.gradle new file mode 100644 index 0000000..83901a7 --- /dev/null +++ b/build-config/checkstyle/build.gradle @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * 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. + * + */ + +plugins { + id "de.undercouch.download" +} + +apply plugin: 'java' + +task downloadCheckstyleRuleFiles(type: Download) { + src([ + 'https://raw.githubusercontent.com/wso2/code-quality-tools/v1.4/checkstyle/jdk-17/checkstyle.xml', + 'https://raw.githubusercontent.com/wso2/code-quality-tools/v1.4/checkstyle/jdk-17/suppressions.xml' + ]) + overwrite false + onlyIfNewer true + dest buildDir +} + +jar { + enabled = false +} + +clean { + enabled = false +} + +artifacts.add('default', file("$project.buildDir/checkstyle.xml")) { + builtBy('downloadCheckstyleRuleFiles') +} + +artifacts.add('default', file("$project.buildDir/suppressions.xml")) { + builtBy('downloadCheckstyleRuleFiles') +} diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml new file mode 100644 index 0000000..83d4194 --- /dev/null +++ b/build-config/resources/Ballerina.toml @@ -0,0 +1,19 @@ +[package] +org = "ballerina" +name = "jsondata" +version = "@toml.version@" +authors = ["Ballerina"] +keywords = ["json"] +repository = "https://github.com/ballerina-platform/module-ballerina.jsondata" +#icon = "icon.png" +license = ["Apache-2.0"] +distribution = "2201.8.1" + +[platform.java17] +graalvmCompatible = true + +[[platform.java17.dependency]] +groupId = "io.ballerina.stdlib" +artifactId = "jsondata-native" +version = "@toml.version@" +path = "../native/build/libs/data.jsondata-native-@project.version@.jar" diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..2551eb1 --- /dev/null +++ b/build.gradle @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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. + */ + +plugins { + id "com.github.spotbugs" version "${githubSpotbugsVersion}" + id "com.github.johnrengelman.shadow" version "${githubJohnrengelmanShadowVersion}" + id "de.undercouch.download" version "${underCouchDownloadVersion}" + id "net.researchgate.release" version "${researchgateReleaseVersion}" +} + +allprojects { + group = project.group + version = project.version + + apply plugin: 'jacoco' + apply plugin: 'maven-publish' + + repositories { + mavenLocal() + maven { + url = 'https://maven.wso2.org/nexus/content/repositories/releases/' + } + + maven { + url = 'https://maven.wso2.org/nexus/content/groups/wso2-public/' + } + + maven { + url = 'https://repo.maven.apache.org/maven2' + } + + maven { + url = 'https://maven.pkg.github.com/ballerina-platform/ballerina-lang' + credentials { + username System.getenv("packageUser") + password System.getenv("packagePAT") + } + } + } + + ext { + snapshotVersion= '-SNAPSHOT' + timestampedVersionRegex = '.*-\\d{8}-\\d{6}-\\w.*\$' + } +} + +subprojects { + + configurations { + ballerinaStdLibs + } + + dependencies { + /* Standard libraries */ + ballerinaStdLibs "io.ballerina.stdlib:io-ballerina:${stdlibIoVersion}" + } +} + +def moduleVersion = project.version.replace("-SNAPSHOT", "") + +release { + failOnPublishNeeded = false + + buildTasks = ["build"] + failOnSnapshotDependencies = true + versionPropertyFile = 'gradle.properties' + tagTemplate = 'v$version' + + git { + requireBranch = "release-${moduleVersion}" + pushToRemote = 'origin' + } +} + +task build { + dependsOn('data.jsondata-ballerina:build') +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..538c62a --- /dev/null +++ b/gradle.properties @@ -0,0 +1,15 @@ +org.gradle.caching=true +group=io.ballerina.stdlib +version=0.1.0-SNAPSHOT +ballerinaLangVersion=2201.8.1 + +checkstyleToolVersion=10.12.0 +puppycrawlCheckstyleVersion=10.12.0 +testngVersion=7.6.1 +slf4jVersion=2.0.7 +githubSpotbugsVersion=5.0.14 +githubJohnrengelmanShadowVersion=8.1.1 +underCouchDownloadVersion=4.0.4 +researchgateReleaseVersion=2.8.0 +ballerinaGradlePluginVersion=2.0.1 +stdlibIoVersion=1.6.0 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..033e24c4cdf41af1ab109bc7f253b2b887023340 GIT binary patch literal 63375 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≫L+crb^Bz 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/native/build.gradle b/native/build.gradle new file mode 100644 index 0000000..9a7ca44 --- /dev/null +++ b/native/build.gradle @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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. + */ + +plugins { + id 'java' + id 'checkstyle' + id 'com.github.spotbugs' +} + +description = 'Ballerina - Data.Json Java Utils' + +dependencies { + implementation 'junit:junit:4.13.1' + checkstyle project(':checkstyle') + checkstyle "com.puppycrawl.tools:checkstyle:${puppycrawlCheckstyleVersion}" + implementation 'org.apache.commons:commons-lang3:3.6' + + implementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}" + implementation group: 'org.ballerinalang', name: 'ballerina-runtime', version: "${ballerinaLangVersion}" + implementation group: 'org.ballerinalang', name: 'value', version: "${ballerinaLangVersion}" +} + +checkstyle { + toolVersion "${checkstyleToolVersion}" + configFile rootProject.file("build-config/checkstyle/build/checkstyle.xml") + configProperties = ["suppressionFile" : file("${rootDir}/build-config/checkstyle/build/suppressions.xml")] +} + +checkstyleMain.dependsOn(":checkstyle:downloadCheckstyleRuleFiles") + +def excludePattern = '**/module-info.java' +tasks.withType(Checkstyle) { + exclude excludePattern +} + +spotbugsMain { + enabled=false + effort "max" + reportLevel "low" + reportsDir = file("$project.buildDir/reports/spotbugs") + reports { + html.enabled true + text.enabled = true + } + def excludeFile = file("${rootDir}/spotbugs-exclude.xml") + if(excludeFile.exists()) { + excludeFilter = excludeFile + } +} + +spotbugsTest { + enabled = false +} + +compileJava { + doFirst { + options.compilerArgs = [ + '--module-path', classpath.asPath, + ] + classpath = files() + } +} diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java new file mode 100644 index 0000000..f23bdbf --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.data.jsondata.json; + +import io.ballerina.runtime.api.Environment; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.api.values.BTypedesc; + +/** + * This class is used to convert json inform of string, byte[], byte-stream to record or json type. + * + * @since 0.1.0 + */ +public class Native { + + public static Object fromJsonWithType(Object json, BMap options, BTypedesc typed) { + return null; + } + + public static Object fromJsonStringWithType(Environment env, Object json, BMap options, + BTypedesc typed) { + return null; + } +} diff --git a/native/src/main/java/module-info.java b/native/src/main/java/module-info.java new file mode 100644 index 0000000..0f6421a --- /dev/null +++ b/native/src/main/java/module-info.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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. + */ + +module io.ballerina.stdlib.data { + requires io.ballerina.runtime; + requires io.ballerina.lang.value; + requires junit; + requires org.apache.commons.lang3; + exports io.ballerina.stdlib.data.jsondata.json; +} diff --git a/native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/data/xmldata-native/resource-config.json b/native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/data/xmldata-native/resource-config.json new file mode 100644 index 0000000..befe09e --- /dev/null +++ b/native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/data/xmldata-native/resource-config.json @@ -0,0 +1,6 @@ +{ + "bundles":[{ + "name":"error", + "locales":[""] + }] +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..52fea65 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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. + */ + +plugins { + id "com.gradle.enterprise" version "3.2" +} + +rootProject.name = 'data.jsondata' +include(':checkstyle') +include(':data.jsondata-native') +include(':data.jsondata-ballerina') + +project(':checkstyle').projectDir = file("build-config${File.separator}checkstyle") +project(':data.jsondata-native').projectDir = file('native') +project(':data.jsondata-ballerina').projectDir = file('ballerina') + +gradleEnterprise { + buildScan { + termsOfServiceUrl = 'https://gradle.com/terms-of-service' + termsOfServiceAgree = 'yes' + } +} From 6ee0f98380590e163123e0009d7076845c09ba93 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Fri, 12 Jan 2024 11:18:42 +0530 Subject: [PATCH 02/27] Add fromJsonWithType function --- ballerina/Ballerina.toml | 2 +- ballerina/Dependencies.toml | 18 +- ballerina/json_api.bal | 4 +- build-config/resources/Ballerina.toml | 2 +- .../stdlib/data/jsondata/FromString.java | 163 +++++++++ .../data/jsondata/json/JsonTraverse.java | 311 ++++++++++++++++++ .../stdlib/data/jsondata/json/Native.java | 6 +- .../resource-config.json | 0 8 files changed, 492 insertions(+), 14 deletions(-) create mode 100644 native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java create mode 100644 native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java rename native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/data/{xmldata-native => jsondata-native}/resource-config.json (100%) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 997dc5d..1784a11 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,6 +1,6 @@ [package] org = "ballerina" -name = "jsondata" +name = "data.jsondata" version = "0.1.0" authors = ["Ballerina"] keywords = ["json"] diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 71c078e..e52d88c 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -9,20 +9,20 @@ distribution-version = "2201.8.1" [[package]] org = "ballerina" -name = "jballerina.java" -version = "0.0.0" +name = "data.jsondata" +version = "0.1.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] modules = [ - {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} + {org = "ballerina", packageName = "data.jsondata", moduleName = "data.jsondata"} ] [[package]] org = "ballerina" -name = "jsondata" -version = "0.1.0" -dependencies = [ - {org = "ballerina", name = "jballerina.java"} -] +name = "jballerina.java" +version = "0.0.0" modules = [ - {org = "ballerina", packageName = "jsondata", moduleName = "jsondata"} + {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} ] diff --git a/ballerina/json_api.bal b/ballerina/json_api.bal index 075879b..9722271 100644 --- a/ballerina/json_api.bal +++ b/ballerina/json_api.bal @@ -16,10 +16,10 @@ import ballerina/jballerina.java; -public isolated function fromJsonWithType(json v, Options options = {}, typedesc t = <>) +public isolated function fromJsonWithType(json v, Options options = {}, typedesc t = <>) returns t|Error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; -public isolated function fromJsonStringWithType(string|byte[]|stream s, Options options = {}, typedesc t = <>) +public isolated function fromJsonStringWithType(string|byte[]|stream s, Options options = {}, typedesc t = <>) returns t|Error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; # Represent the options that can be used for filtering in the projection. diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index 83d4194..bff9a3f 100644 --- a/build-config/resources/Ballerina.toml +++ b/build-config/resources/Ballerina.toml @@ -1,6 +1,6 @@ [package] org = "ballerina" -name = "jsondata" +name = "data.jsondata" version = "@toml.version@" authors = ["Ballerina"] keywords = ["json"] diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java new file mode 100644 index 0000000..c15558a --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.data.jsondata; + +import io.ballerina.runtime.api.TypeTags; +import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.types.ReferenceType; +import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.UnionType; +import io.ballerina.runtime.api.values.BDecimal; +import io.ballerina.runtime.api.values.BError; +import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.api.values.BTypedesc; + +import java.util.Comparator; +import java.util.List; + +/** + * Native implementation of data:fromStringWithType(string). + * + * @since 0.1.0 + */ +public class FromString { + + public static Object fromStringWithType(BString string, BTypedesc typed) { + Type expType = typed.getDescribingType(); + + try { + return fromStringWithType(string, expType); + } catch (NumberFormatException e) { + return returnError(string.getValue(), expType.toString()); + } + } + + public static Object fromStringWithTypeInternal(BString string, Type expType) { + return fromStringWithType(string, expType); + } + + private static Object fromStringWithType(BString string, Type expType) { + String value = string.getValue(); + try { + switch (expType.getTag()) { + case TypeTags.INT_TAG: + return stringToInt(value); + case TypeTags.FLOAT_TAG: + return stringToFloat(value); + case TypeTags.DECIMAL_TAG: + return stringToDecimal(value); + case TypeTags.STRING_TAG: + return string; + case TypeTags.BOOLEAN_TAG: + return stringToBoolean(value); + case TypeTags.NULL_TAG: + return stringToNull(value); + case TypeTags.UNION_TAG: + return stringToUnion(string, (UnionType) expType); + case TypeTags.TYPE_REFERENCED_TYPE_TAG: + return fromStringWithType(string, ((ReferenceType) expType).getReferredType()); + default: + return returnError(value, expType.toString()); + } + } catch (NumberFormatException e) { + return returnError(value, expType.toString()); + } + } + + private static Long stringToInt(String value) throws NumberFormatException { + return Long.parseLong(value); + } + + private static Double stringToFloat(String value) throws NumberFormatException { + if (hasFloatOrDecimalLiteralSuffix(value)) { + throw new NumberFormatException(); + } + return Double.parseDouble(value); + } + + private static BDecimal stringToDecimal(String value) throws NumberFormatException { + return ValueCreator.createDecimalValue(value); + } + + private static Object stringToBoolean(String value) throws NumberFormatException { + if ("true".equalsIgnoreCase(value) || "1".equalsIgnoreCase(value)) { + return true; + } + + if ("false".equalsIgnoreCase(value) || "0".equalsIgnoreCase(value)) { + return false; + } + return returnError(value, "boolean"); + } + + private static Object stringToNull(String value) throws NumberFormatException { + if ("null".equalsIgnoreCase(value) || "()".equalsIgnoreCase(value)) { + return null; + } + return returnError(value, "()"); + } + + private static Object stringToUnion(BString string, UnionType expType) throws NumberFormatException { + List memberTypes = expType.getMemberTypes(); + memberTypes.sort(Comparator.comparingInt(t -> t.getTag())); + boolean isStringExpType = false; + for (Type memberType : memberTypes) { + try { + Object result = fromStringWithType(string, memberType); + if (result instanceof BString) { + isStringExpType = true; + continue; + } else if (result instanceof BError) { + continue; + } + return result; + } catch (Exception e) { + // Skip + } + } + + if (isStringExpType) { + return string; + } + return returnError(string.getValue(), expType.toString()); + } + + private static boolean hasFloatOrDecimalLiteralSuffix(String value) { + int length = value.length(); + if (length == 0) { + return false; + } + + switch (value.charAt(length - 1)) { + case 'F': + case 'f': + case 'D': + case 'd': + return true; + default: + return false; + } + } + + private static BError returnError(String string, String expType) { +// return DiagnosticLog.error(DiagnosticErrorCode.CANNOT_CONVERT_TO_EXPECTED_TYPE, +// PredefinedTypes.TYPE_STRING.getName(), string, expType); + return null; + } +} diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java new file mode 100644 index 0000000..9e4038e --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.data.jsondata.json; + +import io.ballerina.runtime.api.TypeTags; +import io.ballerina.runtime.api.creators.ErrorCreator; +import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.flags.SymbolFlags; +import io.ballerina.runtime.api.types.ArrayType; +import io.ballerina.runtime.api.types.Field; +import io.ballerina.runtime.api.types.MapType; +import io.ballerina.runtime.api.types.RecordType; +import io.ballerina.runtime.api.types.TupleType; +import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.UnionType; +import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.utils.TypeUtils; +import io.ballerina.runtime.api.values.BArray; +import io.ballerina.runtime.api.values.BError; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BString; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Stack; + +/** + * Traverse json tree. + * + * @since 0.1.0 + */ +public class JsonTraverse { + + private static final ThreadLocal tlJsonTree = ThreadLocal.withInitial(JsonTree::new); + + public static Object traverse(Object json, Type type) { + JsonTree jsonTree = tlJsonTree.get(); + try { + return jsonTree.traverseJson(json, type); + } catch (BError e) { + return e; + } finally { + jsonTree.reset(); + } + } + + static class JsonTree { + + Object currentJsonNode; + Field currentField; + Stack> fieldHierarchy = new Stack<>(); + Stack restType = new Stack<>(); + Deque nodesStack = new ArrayDeque<>(); + Deque fieldNames = new ArrayDeque<>(); + Type rootArray; + + void reset() { + currentJsonNode = null; + currentField = null; + fieldHierarchy.clear(); + restType.clear(); + nodesStack.clear(); + fieldNames.clear(); + rootArray = null; + } + + public Object traverseJson(Object json, Type type) { + Type referredType = TypeUtils.getReferredType(type); + switch (referredType.getTag()) { + case TypeTags.RECORD_TYPE_TAG: + RecordType recordType = (RecordType) referredType; + this.fieldHierarchy.push(new HashMap<>(recordType.getFields())); + this.restType.push(recordType.getRestFieldType()); + currentJsonNode = ValueCreator.createRecordValue(recordType); + nodesStack.push(currentJsonNode); + traverseMapJsonOrArrayJson(json, referredType); + break; + case TypeTags.ARRAY_TAG: + currentJsonNode = ValueCreator.createArrayValue((ArrayType) referredType); + nodesStack.push(currentJsonNode); + traverseMapJsonOrArrayJson(json, referredType); + break; + case TypeTags.TUPLE_TAG: + rootArray = referredType; + currentJsonNode = ValueCreator.createTupleValue((TupleType) referredType); + nodesStack.push(currentJsonNode); + traverseMapJsonOrArrayJson(json, referredType); + break; + case TypeTags.NULL_TAG: + case TypeTags.BOOLEAN_TAG: + case TypeTags.INT_TAG: + case TypeTags.FLOAT_TAG: + case TypeTags.DECIMAL_TAG: + case TypeTags.STRING_TAG: + return convertToBasicType(json, referredType); + case TypeTags.UNION_TAG: + for (Type memberType : ((UnionType) referredType).getMemberTypes()) { + try { + return traverseJson(json, memberType); + } catch (Exception e) { + // Ignore + } + } + return ErrorCreator.createError(StringUtils.fromString("incompatible type for json: " + type)); + case TypeTags.JSON_TAG: + case TypeTags.ANYDATA_TAG: + return json; + default: + return ErrorCreator.createError(StringUtils.fromString("incompatible type for json: " + type)); + } + return currentJsonNode; + } + + private void traverseMapJsonOrArrayJson(Object json, Type type) { + Object parentJsonNode = nodesStack.peek(); + if (json instanceof BMap map) { + traverseMapValue(map, parentJsonNode); + } else if (json instanceof BArray) { + traverseArrayValue(json, parentJsonNode); + } else { + // JSON value not compatible with map or array. + if (type.getTag() == TypeTags.RECORD_TYPE_TAG) { + this.fieldHierarchy.pop(); + this.restType.pop(); + } + throw ErrorCreator.createError(StringUtils.fromString("incompatible type for json: " + type)); + } + nodesStack.pop(); + } + + private void traverseMapValue(BMap map, Object parentJsonNode) { + for (BString key : map.getKeys()) { + currentField = fieldHierarchy.peek().remove(key.toString()); + if (currentField == null) { + // Add to the rest field + if (restType.peek() != null) { + Type restFieldType = TypeUtils.getReferredType(restType.peek()); + addRestField(restFieldType, key, map.get(key)); + } + continue; + } + + fieldNames.push(currentField.getFieldName()); + Type currentFieldType = TypeUtils.getReferredType(currentField.getFieldType()); + int currentFieldTypeTag = currentFieldType.getTag(); + Object mapValue = map.get(key); + + switch (currentFieldTypeTag) { + case TypeTags.MAP_TAG: + if (!checkTypeCompatibility(((MapType) currentFieldType).getConstrainedType(), mapValue)) { + throw ErrorCreator.createError(StringUtils.fromString("incompatible value '" + mapValue + + "' for type '" + currentFieldType + "' in field '" + getCurrentFieldPath() + "'")); + } + ((BMap) currentJsonNode).put(StringUtils.fromString(fieldNames.pop()), + mapValue); + break; + case TypeTags.NULL_TAG: + case TypeTags.BOOLEAN_TAG: + case TypeTags.INT_TAG: + case TypeTags.FLOAT_TAG: + case TypeTags.DECIMAL_TAG: + case TypeTags.STRING_TAG: + Object value = convertToBasicType(mapValue, currentFieldType); + ((BMap) currentJsonNode).put(StringUtils.fromString(fieldNames.pop()), value); + break; + default: + currentJsonNode = traverseJson(mapValue, currentFieldType); + ((BMap) parentJsonNode).put(key, currentJsonNode); + currentJsonNode = parentJsonNode; + } + } + Map currentField = fieldHierarchy.pop(); + checkOptionalFieldsAndLogError(currentField); + restType.pop(); + } + + private void traverseArrayValue(Object json, Object parentJsonNode) { + BArray array = (BArray) json; + switch (rootArray.getTag()) { + case TypeTags.ARRAY_TAG: + int expectedArraySize = ((ArrayType) rootArray).getSize(); + if (expectedArraySize > array.getLength()) { + throw ErrorCreator.createError(StringUtils.fromString( + "size mismatch between target and source")); + } + if (expectedArraySize == -1) { + traverseArrayMembers(array.getLength(), array, parentJsonNode); + } else { + traverseArrayMembers(expectedArraySize, array, parentJsonNode); + } + break; + case TypeTags.TUPLE_TAG: + Type restType = ((TupleType) rootArray).getRestType(); + int expectedTupleTypeCount = ((TupleType) rootArray).getTupleTypes().size(); + if (expectedTupleTypeCount > array.getLength()) { + throw ErrorCreator.createError(StringUtils.fromString( + "size mismatch between target and source")); + } + + for (int i = 0; i < array.getLength(); i++) { + Object jsonMember = array.get(i); + if (i < expectedTupleTypeCount) { + currentJsonNode = traverseJson(jsonMember, ((TupleType) rootArray).getTupleTypes().get(i)); + } else if (restType != null) { + currentJsonNode = traverseJson(jsonMember, restType); + } else { + continue; + } + ((BArray) parentJsonNode).append(currentJsonNode); + } + break; + } + currentJsonNode = parentJsonNode; + } + + private void traverseArrayMembers(long length, BArray array, Object parentJsonNode) { + for (int i = 0; i < length; i++) { + Object jsonMember = array.get(i); + currentJsonNode = traverseJson(jsonMember, ((ArrayType) rootArray).getElementType()); + ((BArray) parentJsonNode).append(currentJsonNode); + } + } + + private void addRestField(Type restFieldType, BString key, Object jsonMember) { + switch (restFieldType.getTag()) { + case TypeTags.ANYDATA_TAG: + case TypeTags.JSON_TAG: + ((BMap) currentJsonNode).put(key, jsonMember); + break; + case TypeTags.BOOLEAN_TAG: + case TypeTags.INT_TAG: + case TypeTags.FLOAT_TAG: + case TypeTags.DECIMAL_TAG: + case TypeTags.STRING_TAG: + if (checkTypeCompatibility(restFieldType, jsonMember)) { + ((BMap) currentJsonNode).put(key, jsonMember); + } + break; + default: + return; + } + } + + private boolean checkTypeCompatibility(Type constraintType, Object json) { + if (json instanceof BMap) { + BMap map = (BMap) json; + for (BString key : map.getKeys()) { + if (!checkTypeCompatibility(constraintType, map.get(key))) { + return false; + } + } + return true; + } else if ((json instanceof BString && constraintType.getTag() == TypeTags.STRING_TAG) + || (json instanceof Long && constraintType.getTag() == TypeTags.INT_TAG) + || (json instanceof Double && (constraintType.getTag() == TypeTags.FLOAT_TAG + || constraintType.getTag() == TypeTags.DECIMAL_TAG)) + || (Boolean.class.isInstance(json) && constraintType.getTag() == TypeTags.BOOLEAN_TAG) + || (json == null && constraintType.getTag() == TypeTags.NULL_TAG)) { + return true; + } else { + return false; + } + } + + private void checkOptionalFieldsAndLogError(Map currentField) { + currentField.values().forEach(field -> { + if (SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.REQUIRED)) { + throw ErrorCreator.createError(StringUtils.fromString("required field '" + field.getFieldName() + + "' not present in JSON")); + } + }); + } + + private Object convertToBasicType(Object json, Type targetType) { + Type jsonType = TypeUtils.getType(json); + if (TypeUtils.isSameType(jsonType, targetType)) { + return json; + } + return ErrorCreator.createError(StringUtils.fromString("incompatible type expected '" + targetType + + "', found '" + jsonType + "'")); + } + + private String getCurrentFieldPath() { + Iterator itr = fieldNames.descendingIterator(); + StringBuilder sb = new StringBuilder(itr.hasNext() ? itr.next() : ""); + while (itr.hasNext()) { + sb.append(".").append(itr.next()); + } + return sb.toString(); + } + } +} diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java index f23bdbf..9e76d7c 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java @@ -31,7 +31,11 @@ public class Native { public static Object fromJsonWithType(Object json, BMap options, BTypedesc typed) { - return null; + try { + return JsonTraverse.traverse(json, typed.getDescribingType()); + } catch (Exception e) { + return null; + } } public static Object fromJsonStringWithType(Environment env, Object json, BMap options, diff --git a/native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/data/xmldata-native/resource-config.json b/native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/data/jsondata-native/resource-config.json similarity index 100% rename from native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/data/xmldata-native/resource-config.json rename to native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/data/jsondata-native/resource-config.json From 4875a0234c0dbb5bc8244b3fd9942a8ba8742ba5 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Tue, 23 Jan 2024 18:20:42 +0530 Subject: [PATCH 03/27] Add fromJsonStringWithType function --- ballerina/init.bal | 25 + ballerina/json_api.bal | 4 +- .../stdlib/data/jsondata/FromString.java | 6 +- .../data/jsondata/json/JsonCreator.java | 298 ++++ .../stdlib/data/jsondata/json/JsonParser.java | 1199 +++++++++++++++++ .../stdlib/data/jsondata/json/Native.java | 11 + .../stdlib/data/jsondata/utils/Constants.java | 11 + .../data/jsondata/utils/ModuleUtils.java | 46 + 8 files changed, 1593 insertions(+), 7 deletions(-) create mode 100644 native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java create mode 100644 native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java create mode 100644 native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/Constants.java create mode 100644 native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/ModuleUtils.java diff --git a/ballerina/init.bal b/ballerina/init.bal index e69de29..36362f4 100644 --- a/ballerina/init.bal +++ b/ballerina/init.bal @@ -0,0 +1,25 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you 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. + +import ballerina/jballerina.java; + +isolated function init() { + setModule(); +} + +isolated function setModule() = @java:Method { + 'class: "io.ballerina.stdlib.data.jsondata.utils.ModuleUtils" +} external; diff --git a/ballerina/json_api.bal b/ballerina/json_api.bal index 9722271..56f01d6 100644 --- a/ballerina/json_api.bal +++ b/ballerina/json_api.bal @@ -17,10 +17,10 @@ import ballerina/jballerina.java; public isolated function fromJsonWithType(json v, Options options = {}, typedesc t = <>) - returns t|Error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; + returns t|error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; public isolated function fromJsonStringWithType(string|byte[]|stream s, Options options = {}, typedesc t = <>) - returns t|Error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; + returns t|error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; # Represent the options that can be used for filtering in the projection. # diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java index c15558a..af175d2 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java @@ -48,11 +48,7 @@ public static Object fromStringWithType(BString string, BTypedesc typed) { } } - public static Object fromStringWithTypeInternal(BString string, Type expType) { - return fromStringWithType(string, expType); - } - - private static Object fromStringWithType(BString string, Type expType) { + public static Object fromStringWithType(BString string, Type expType) { String value = string.getValue(); try { switch (expType.getTag()) { diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java new file mode 100644 index 0000000..db8fb35 --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.data.jsondata.json; + +import io.ballerina.runtime.api.TypeTags; +import io.ballerina.runtime.api.creators.ErrorCreator; +import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.types.*; +import io.ballerina.runtime.api.utils.JsonUtils; +import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.utils.TypeUtils; +import io.ballerina.runtime.api.values.*; +import io.ballerina.stdlib.data.jsondata.FromString; +import io.ballerina.stdlib.data.jsondata.utils.Constants; + +import java.util.HashMap; +import java.util.Iterator; + +/** + * Create objects for partially parsed json. + * + * @since 0.1.0 + */ +public class JsonCreator { + + // convert json[] to output array + static BArray finalizeArray(JsonParser.StateMachine sm, Type arrType, BArray currArr) + throws JsonParser.JsonParserException { + int arrTypeTag = arrType.getTag(); + BListInitialValueEntry[] initialValues = new BListInitialValueEntry[currArr.size()]; + for (int i = 0; i < currArr.size(); i++) { + Object curElement = currArr.get(i); + Type currElmType = TypeUtils.getType(curElement); + if (currElmType.getTag() == TypeTags.ARRAY_TAG) { + if (arrTypeTag == TypeTags.ARRAY_TAG) { + curElement = finalizeArray(sm, ((ArrayType) arrType).getElementType(), (BArray) curElement); + } else if (arrTypeTag == TypeTags.TUPLE_TAG) { + curElement = finalizeArray(sm, ((TupleType) arrType).getTupleTypes().get(i), (BArray) curElement); + } else { + throw new JsonParser.JsonParserException("invalid type in field " + + getCurrentFieldPath(sm)); + } + } else { + if (arrTypeTag == TypeTags.ARRAY_TAG) { + curElement = JsonCreator.convertJSON(sm, curElement, ((ArrayType) arrType).getElementType()); + } else if (arrTypeTag == TypeTags.TUPLE_TAG) { + curElement = JsonCreator.convertJSON(sm, curElement, ((TupleType) arrType).getTupleTypes().get(i)); + } else { + throw new JsonParser.JsonParserException("invalid type in field " + + getCurrentFieldPath(sm)); + } + } + + initialValues[i] = ValueCreator.createListInitialValueEntry(curElement); + } + + if (arrTypeTag == TypeTags.ARRAY_TAG) { + return ValueCreator.createArrayValue((ArrayType) arrType, initialValues); + } else if (arrTypeTag == TypeTags.TUPLE_TAG) { + return ValueCreator.createTupleValue((TupleType) arrType, initialValues); + } else { + throw new JsonParser.JsonParserException("invalid type in field " + + getCurrentFieldPath(sm)); + } + } + + static BArray finalizeArray(JsonTraverse.JsonTree jsonTree, Type arrType, BArray currArr) { + int arrTypeTag = arrType.getTag(); + BListInitialValueEntry[] initialValues = new BListInitialValueEntry[currArr.size()]; + for (int i = 0; i < currArr.size(); i++) { + Object curElement = currArr.get(i); + Type currElmType = TypeUtils.getType(curElement); + if (currElmType.getTag() == TypeTags.ARRAY_TAG) { + if (arrTypeTag == TypeTags.ARRAY_TAG) { + curElement = finalizeArray(jsonTree, ((ArrayType) arrType).getElementType(), (BArray) curElement); + } else if (arrTypeTag == TypeTags.TUPLE_TAG) { + curElement = finalizeArray(jsonTree, ((TupleType) arrType).getTupleTypes().get(i), + (BArray) curElement); + } else { + throw ErrorCreator.createError(StringUtils.fromString("invalid type in field ")); + } + } else { + if (arrTypeTag == TypeTags.ARRAY_TAG) { + curElement = JsonCreator.convertJSON(jsonTree, curElement, ((ArrayType) arrType).getElementType()); + } else if (arrTypeTag == TypeTags.TUPLE_TAG) { + TupleType tupleType = (TupleType) arrType; + if (tupleType.getTupleTypes().size() <= i) { + curElement = JsonCreator.convertJSON(jsonTree, curElement, + (tupleType.getRestType())); + } else { + curElement = JsonCreator.convertJSON(jsonTree, curElement, + (tupleType.getTupleTypes().get(i))); + } + } else { + throw ErrorCreator.createError(StringUtils.fromString("invalid type in field ")); + } + } + initialValues[i] = ValueCreator.createListInitialValueEntry(curElement); + } + + if (arrTypeTag == TypeTags.ARRAY_TAG) { + return ValueCreator.createArrayValue((ArrayType) arrType, initialValues); + } else if (arrTypeTag == TypeTags.TUPLE_TAG) { + return ValueCreator.createTupleValue((TupleType) arrType, initialValues); + } else { + throw ErrorCreator.createError(StringUtils.fromString("invalid type in field ")); + } + } + + static BMap initRecordValue(Type expectedType) + throws JsonParser.JsonParserException { + if (expectedType.getTag() != TypeTags.RECORD_TYPE_TAG) { + throw new JsonParser.JsonParserException("expected record type for input type"); + } + + return ValueCreator.createRecordValue((RecordType) expectedType); + } + + static BArray initArrayValue(Type expectedType) throws JsonParser.JsonParserException { + if (expectedType.getTag() == TypeTags.TUPLE_TAG) { + return ValueCreator.createTupleValue((TupleType) expectedType); + } else if (expectedType.getTag() == TypeTags.ARRAY_TAG) { + return ValueCreator.createArrayValue((ArrayType) expectedType); + } else { + throw new JsonParser.JsonParserException("expected array or tuple type for input type"); + } + } + + static BMap initNewMapValue(JsonParser.StateMachine sm) + throws JsonParser.JsonParserException { + Type currentType = TypeUtils.getReferredType(sm.expectedTypes.peek()); + +// if (currentType == null) { +// // TODO: Check optional and Update the CurrentJsonNode. +// return Optional.empty(); +// } + + if (sm.currentJsonNode != null) { + sm.nodesStack.push(sm.currentJsonNode); + } + + BMap nextMapValue; + switch (currentType.getTag()) { + case TypeTags.RECORD_TYPE_TAG: + RecordType recordType = (RecordType) currentType; + nextMapValue = ValueCreator.createRecordValue(recordType); + sm.fieldHierarchy.push(new HashMap<>(recordType.getFields())); + sm.restType.push(recordType.getRestFieldType()); + break; + case TypeTags.JSON_TAG: + nextMapValue = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); + sm.fieldHierarchy.push(new HashMap<>()); + sm.restType.push(sm.definedJsonType); + sm.jsonFieldDepth++; + break; + case TypeTags.ANYDATA_TAG: + nextMapValue = ValueCreator.createMapValue(Constants.ANYDATA_MAP_TYPE); + sm.fieldHierarchy.push(new HashMap<>()); + sm.restType.push(sm.definedJsonType); + sm.jsonFieldDepth++; + break; + default: + throw new JsonParser.JsonParserException("invalid type in field " + getCurrentFieldPath(sm)); + } + + Object currentJson = sm.currentJsonNode; + int valueTypeTag = TypeUtils.getType(currentJson).getTag(); + if (valueTypeTag == TypeTags.MAP_TAG || valueTypeTag == TypeTags.RECORD_TYPE_TAG) { + // TODO: Fix -> Using fieldName as the key is wrong all the time when json as exp type. + ((BMap) currentJson).put(StringUtils.fromString(sm.fieldNames.peek()), + nextMapValue); + } + return nextMapValue; + } + + static BArray initNewArrayValue(JsonParser.StateMachine sm) throws JsonParser.JsonParserException { + Object currentJsonNode = sm.currentJsonNode; + BArray nextArrValue = initArrayValue(sm.expectedTypes.peek()); + if (currentJsonNode == null) { + return nextArrValue; + } + + sm.nodesStack.push(sm.currentJsonNode); + return nextArrValue; + } + + static Object convertJSON(JsonParser.StateMachine sm, Object value, Type type) + throws JsonParser.JsonParserException { + // all types are currently allowed for readonly type fields + if (type.getTag() == TypeTags.READONLY_TAG) { + return value; + } + try { + return JsonUtils.convertJSON(value, type); + } catch (Exception e) { + throw new JsonParser.JsonParserException("incompatible value '" + value + "' for type '" + + type + "' in field '" + getCurrentFieldPath(sm) + "'"); + } + } + + static Object convertJSON(JsonTraverse.JsonTree jsonTree, Object value, Type type) { + // all types are currently allowed for readonly type fields + if (type.getTag() == TypeTags.READONLY_TAG) { + return value; + } + try { + return JsonUtils.convertJSON(value, type); + } catch (Exception e) { + if (jsonTree.fieldNames.isEmpty()) { + throw ErrorCreator.createError(StringUtils.fromString("incompatible type for json: " + type)); + } + throw ErrorCreator.createError(StringUtils.fromString("incompatible value '" + value + "' for type '" + + type + "' in field '" + getCurrentFieldPath(jsonTree))); + } + } + + private static String getCurrentFieldPath(JsonParser.StateMachine sm) { + Iterator itr = sm.fieldNames.descendingIterator(); + + StringBuilder result = new StringBuilder(itr.hasNext() ? itr.next() : ""); + while (itr.hasNext()) { + result.append(".").append(itr.next()); + } + return result.toString(); + } + + static String getCurrentFieldPath(JsonTraverse.JsonTree jsonTree) { + Iterator itr = jsonTree.fieldNames.descendingIterator(); + + StringBuilder result = new StringBuilder(itr.hasNext() ? itr.next() : ""); + while (itr.hasNext()) { + result.append(".").append(itr.next()); + } + return result.toString(); + } + + static Object convertAndUpdateCurrentJsonNode(JsonParser.StateMachine sm, BString value, Type type) + throws JsonParser.JsonParserException { + Object currentJson = sm.currentJsonNode; + Object convertedValue = FromString.fromStringWithType(value, type); + // TODO: Remove null case after properly returning error. + if (convertedValue == null || convertedValue instanceof BError) { + throw new JsonParser.JsonParserException("incompatible value '" + value + "' for type '" + + type + "' in field '" + getCurrentFieldPath(sm) + "'"); + } + + switch (TypeUtils.getType(currentJson).getTag()) { + case TypeTags.MAP_TAG: + case TypeTags.RECORD_TYPE_TAG: + ((BMap) currentJson).put(StringUtils.fromString(sm.fieldNames.pop()), + convertedValue); + return currentJson; + case TypeTags.ARRAY_TAG: + ((BArray) currentJson).append(convertedValue); + return currentJson; + case TypeTags.TUPLE_TAG: + ((BArray) currentJson).add(sm.arrayIndexes.peek(), convertedValue); + return currentJson; + default: + return convertedValue; + } + } + + static void updateRecordFieldValue(BString fieldName, Object parent, Object currentJson) { + switch (TypeUtils.getType(parent).getTag()) { + case TypeTags.MAP_TAG: + case TypeTags.RECORD_TYPE_TAG: + ((BMap) parent).put(fieldName, currentJson); + break; + } + } + + static Type getMemberType(Type expectedType, int index) { + if (expectedType.getTag() == TypeTags.ARRAY_TAG) { + return ((ArrayType) expectedType).getElementType(); + } else if (expectedType.getTag() == TypeTags.TUPLE_TAG) { + return ((TupleType) expectedType).getTupleTypes().get(index); + } else { + return expectedType; + } + } +} diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java new file mode 100644 index 0000000..40efac3 --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java @@ -0,0 +1,1199 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.data.jsondata.json; + +import io.ballerina.runtime.api.PredefinedTypes; +import io.ballerina.runtime.api.TypeTags; +import io.ballerina.runtime.api.creators.ErrorCreator; +import io.ballerina.runtime.api.flags.SymbolFlags; +import io.ballerina.runtime.api.types.Field; +import io.ballerina.runtime.api.types.RecordType; +import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.utils.TypeUtils; +import io.ballerina.runtime.api.values.BArray; +import io.ballerina.runtime.api.values.BError; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BString; +import org.apache.commons.lang3.StringEscapeUtils; + +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +/** + * This class converts string to Json with projection. + * + * @since 0.1.0 + */ +public class JsonParser { + + private static final ThreadLocal tlStateMachine = ThreadLocal.withInitial(StateMachine::new); + + private static Object changeForBString(Object jsonObj) { + if (jsonObj instanceof String) { + return StringUtils.fromString((String) jsonObj); + } + return jsonObj; + } + + /** + * Parses the contents in the given {@link Reader} and returns a json. + * + * @param reader reader which contains the JSON content + * @return JSON structure + * @throws BError for any parsing error + */ + public static Object parse(Reader reader, Type type) + throws BError, JsonParserException { + StateMachine sm = tlStateMachine.get(); + try { + return sm.execute(reader, TypeUtils.getReferredType(type)); + } finally { + // Need to reset the state machine before leaving. Otherwise, references to the created + // JSON values will be maintained and the java GC will not happen properly. + sm.reset(); + } + } + + /** + * Represents a JSON parser related exception. + */ + public static class JsonParserException extends Exception { + public JsonParserException(String msg) { + super(msg); + } + } + + /** + * Represents the state machine used for JSON parsing. + */ + static class StateMachine { + + private static final char CR = 0x000D; + private static final char NEWLINE = 0x000A; + private static final char HZ_TAB = 0x0009; + private static final char SPACE = 0x0020; + private static final char BACKSPACE = 0x0008; + private static final char FORMFEED = 0x000C; + private static final char QUOTES = '"'; + private static final char REV_SOL = '\\'; + private static final char SOL = '/'; + private static final char EOF = (char) -1; + private static final String NULL = "null"; + private static final String TRUE = "true"; + private static final String FALSE = "false"; + + private static final State DOC_START_STATE = new DocumentStartState(); + private static final State DOC_END_STATE = new DocumentEndState(); + static final State FIRST_FIELD_READY_STATE = new FirstFieldReadyState(); + private static final State NON_FIRST_FIELD_READY_STATE = new NonFirstFieldReadyState(); + private static final State FIELD_NAME_STATE = new FieldNameState(); + private static final State END_FIELD_NAME_STATE = new EndFieldNameState(); + private static final State FIELD_VALUE_READY_STATE = new FieldValueReadyState(); + private static final State STRING_FIELD_VALUE_STATE = new StringFieldValueState(); + private static final State NON_STRING_FIELD_VALUE_STATE = new NonStringFieldValueState(); + private static final State NON_STRING_VALUE_STATE = new NonStringValueState(); + private static final State STRING_VALUE_STATE = new StringValueState(); + private static final State FIELD_END_STATE = new FieldEndState(); + private static final State STRING_AE_ESC_CHAR_PROCESSING_STATE = new StringAEEscapedCharacterProcessingState(); + private static final State STRING_AE_PROCESSING_STATE = new StringAEProcessingState(); + private static final State FIELD_NAME_UNICODE_HEX_PROCESSING_STATE = new FieldNameUnicodeHexProcessingState(); + static final State FIRST_ARRAY_ELEMENT_READY_STATE = new FirstArrayElementReadyState(); + private static final State NON_FIRST_ARRAY_ELEMENT_READY_STATE = new NonFirstArrayElementReadyState(); + private static final State STRING_ARRAY_ELEMENT_STATE = new StringArrayElementState(); + private static final State NON_STRING_ARRAY_ELEMENT_STATE = new NonStringArrayElementState(); + private static final State ARRAY_ELEMENT_END_STATE = new ArrayElementEndState(); + private static final State STRING_FIELD_ESC_CHAR_PROCESSING_STATE = + new StringFieldEscapedCharacterProcessingState(); + private static final State STRING_VAL_ESC_CHAR_PROCESSING_STATE = + new StringValueEscapedCharacterProcessingState(); + private static final State FIELD_NAME_ESC_CHAR_PROCESSING_STATE = + new FieldNameEscapedCharacterProcessingState(); + private static final State STRING_FIELD_UNICODE_HEX_PROCESSING_STATE = + new StringFieldUnicodeHexProcessingState(); + private static final State STRING_VALUE_UNICODE_HEX_PROCESSING_STATE = + new StringValueUnicodeHexProcessingState(); + Type definedJsonType = PredefinedTypes.TYPE_JSON; + + Object currentJsonNode; + Deque nodesStack; + Deque fieldNames; + + private StringBuilder hexBuilder = new StringBuilder(4); + private char[] charBuff = new char[1024]; + private int charBuffIndex; + + private int index; + private int line; + private int column; + private char currentQuoteChar; + Field currentField; + Stack> fieldHierarchy = new Stack<>(); + Stack restType = new Stack<>(); + Stack expectedTypes = new Stack<>(); + int jsonFieldDepth = 0; + Stack arrayIndexes = new Stack<>(); + + StateMachine() { + reset(); + } + + public void reset() { + index = 0; + currentJsonNode = null; + line = 1; + column = 0; + nodesStack = new ArrayDeque<>(); + fieldNames = new ArrayDeque<>(); + fieldHierarchy.clear(); + currentField = null; + restType.clear(); + expectedTypes.clear(); + jsonFieldDepth = 0; + arrayIndexes.clear(); + } + + private static boolean isWhitespace(char ch) { + return ch == SPACE || ch == HZ_TAB || ch == NEWLINE || ch == CR; + } + + private static void throwExpected(String... chars) throws JsonParserException { + throw new JsonParserException("expected '" + String.join("' or '", chars) + "'"); + } + + private void processLocation(char ch) { + if (ch == '\n') { + this.line++; + this.column = 0; + } else { + this.column++; + } + } + + public Object execute(Reader reader, Type type) throws BError, JsonParserException { + switch (type.getTag()) { + // TODO: Handle all anydata type except record. + case TypeTags.RECORD_TYPE_TAG: + RecordType recordType = (RecordType) type; + expectedTypes.push(recordType); + fieldHierarchy.push(new HashMap<>(recordType.getFields())); + restType.push(recordType.getRestFieldType()); + break; + case TypeTags.ARRAY_TAG: + case TypeTags.TUPLE_TAG: + expectedTypes.push(type); + arrayIndexes.push(0); + break; + case TypeTags.NULL_TAG: + case TypeTags.BOOLEAN_TAG: + case TypeTags.INT_TAG: + case TypeTags.FLOAT_TAG: + case TypeTags.DECIMAL_TAG: + case TypeTags.STRING_TAG: + expectedTypes.push(type); + break; +// case TypeTags.JSON_TAG: +// case TypeTags.ANYDATA_TAG: +// case TypeTags.UNION_TAG: +// case TypeTags.MAP_TAG: +// break; + default: + throw ErrorCreator.createError(StringUtils.fromString("unsupported type: " + type)); + + } + + State currentState = DOC_START_STATE; + try { + char[] buff = new char[1024]; + int count; + while ((count = reader.read(buff)) > 0) { + this.index = 0; + while (this.index < count) { + currentState = currentState.transition(this, buff, this.index, count); + } + } + return currentJsonNode; + } catch (IOException e) { + throw ErrorCreator.createError(StringUtils.fromString("Error reading JSON: " + e.getMessage())); + } catch (JsonParserException e) { + throw ErrorCreator.createError(StringUtils.fromString(e.getMessage() + " at line: " + this.line + + " column: " + this.column)); + } + } + + private void append(char ch) { + try { + this.charBuff[this.charBuffIndex] = ch; + this.charBuffIndex++; + } catch (ArrayIndexOutOfBoundsException e) { + /* this approach is faster than checking for the size by ourself */ + this.growCharBuff(); + this.charBuff[this.charBuffIndex++] = ch; + } + } + + private void growCharBuff() { + char[] newBuff = new char[charBuff.length * 2]; + System.arraycopy(this.charBuff, 0, newBuff, 0, this.charBuff.length); + this.charBuff = newBuff; + } + + private State finalizeNonArrayObject() throws JsonParserException { + if (jsonFieldDepth > 0) { + jsonFieldDepth--; + } + Map remainingFields = fieldHierarchy.pop(); + restType.pop(); + for (Field field : remainingFields.values()) { + if (SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.REQUIRED)) { + throw new JsonParserException("required field '" + field.getFieldName() + "' not present in JSON"); + } + } + return finalizeObject(); + } + + private State finalizeObject() { + if (this.nodesStack.isEmpty()) { + return DOC_END_STATE; + } + + Object parentNode = this.nodesStack.pop(); + if (TypeUtils.getReferredType(TypeUtils.getType(parentNode)).getTag() == TypeTags.RECORD_TYPE_TAG || + TypeUtils.getReferredType(TypeUtils.getType(parentNode)).getTag() == TypeTags.MAP_TAG) { + currentJsonNode = parentNode; + return FIELD_END_STATE; + } + + switch (TypeUtils.getType(parentNode).getTag()) { + case TypeTags.ARRAY_TAG: + ((BArray) parentNode).append(currentJsonNode); + break; + case TypeTags.TUPLE_TAG: + ((BArray) parentNode).add(arrayIndexes.peek(), currentJsonNode); + break; + default: + break; + } + + currentJsonNode = parentNode; + return ARRAY_ELEMENT_END_STATE; + } + + + + /** + * A specific state in the JSON parsing state machine. + */ + interface State { + + /** + * Input given to the current state for a transition. + * + * @param sm the state machine + * @param buff the input characters for the current state + * @param i the location from the character should be read from + * @param count the number of characters to read from the buffer + * @return the new resulting state + */ + State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException; + + } + + /** + * Represents the JSON document start state. + */ + private static class DocumentStartState implements State { + + @Override + public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + char ch; + State state = null; + for (; i < count; i++) { + ch = buff[i]; + sm.processLocation(ch); + if (ch == '{') { + sm.currentJsonNode = JsonCreator.initRecordValue(sm.expectedTypes.peek()); + state = FIRST_FIELD_READY_STATE; + } else if (ch == '[') { + sm.currentJsonNode = JsonCreator.initArrayValue(sm.expectedTypes.peek()); + state = FIRST_ARRAY_ELEMENT_READY_STATE; + } else if (StateMachine.isWhitespace(ch)) { + state = this; + continue; + } else if (ch == QUOTES) { + sm.currentQuoteChar = ch; + state = STRING_VALUE_STATE; + } else if (ch == EOF) { + throw new JsonParserException("empty JSON document"); + } else { + state = NON_STRING_VALUE_STATE; + } + break; + } + if (state == NON_STRING_VALUE_STATE) { + sm.index = i; + } else { + sm.index = i + 1; + } + return state; + } + } + + /** + * Represents the JSON document end state. + */ + private static class DocumentEndState implements State { + + @Override + public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + char ch; + State state = null; + for (; i < count; i++) { + ch = buff[i]; + sm.processLocation(ch); + if (StateMachine.isWhitespace(ch) || ch == EOF) { + state = this; + continue; + } + throw new JsonParserException("JSON document has already ended"); + } + sm.index = i + 1; + return state; + } + } + + /** + * Represents the state just before the first object field is defined. + */ + private static class FirstFieldReadyState implements State { + + @Override + public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + char ch; + State state = null; + for (; i < count; i++) { + ch = buff[i]; + sm.processLocation(ch); + if (ch == QUOTES) { + state = FIELD_NAME_STATE; + sm.currentQuoteChar = ch; + } else if (StateMachine.isWhitespace(ch)) { + state = this; + continue; + } else if (ch == '}') { + state = sm.finalizeNonArrayObject(); + } else { + StateMachine.throwExpected("\"", "}"); + } + break; + } + sm.index = i + 1; + return state; + } + } + + /** + * Represents the state just before the first array element is defined. + */ + private static class FirstArrayElementReadyState implements State { + + @Override + public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + State state = null; + char ch; + for (; i < count; i++) { + ch = buff[i]; + sm.processLocation(ch); + if (StateMachine.isWhitespace(ch)) { + state = this; + continue; + } else if (ch == QUOTES) { + state = STRING_ARRAY_ELEMENT_STATE; + sm.currentQuoteChar = ch; + sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), + sm.arrayIndexes.peek())); + } else if (ch == '{') { + // Get member type of the array and set as expected type. + sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), + sm.arrayIndexes.peek())); + sm.currentJsonNode = JsonCreator.initNewMapValue(sm); + state = FIRST_FIELD_READY_STATE; + } else if (ch == '[') { + // Get member type of the array and set as expected type. + sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), + sm.arrayIndexes.peek())); + sm.arrayIndexes.push(0); + sm.currentJsonNode = JsonCreator.initNewArrayValue(sm); + state = FIRST_ARRAY_ELEMENT_READY_STATE; + } else if (ch == ']') { + state = sm.finalizeObject(); + } else { + state = NON_STRING_ARRAY_ELEMENT_STATE; + sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), + sm.arrayIndexes.peek())); + } + break; + } + if (state == NON_STRING_ARRAY_ELEMENT_STATE) { + sm.index = i; + } else { + sm.index = i + 1; + } + return state; + } + + } + + /** + * Represents the state just before a non-first object field is defined. + */ + private static class NonFirstFieldReadyState implements State { + + @Override + public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + State state = null; + char ch; + for (; i < count; i++) { + ch = buff[i]; + sm.processLocation(ch); + if (ch == QUOTES) { + sm.currentQuoteChar = ch; + state = FIELD_NAME_STATE; + } else if (StateMachine.isWhitespace(ch)) { + state = this; + continue; + } else { + StateMachine.throwExpected("\""); + } + break; + } + sm.index = i + 1; + return state; + } + + } + + /** + * Represents the state just before a non-first array element is defined. + */ + private static class NonFirstArrayElementReadyState implements State { + + @Override + public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + State state = null; + char ch; + for (; i < count; i++) { + ch = buff[i]; + sm.processLocation(ch); + if (StateMachine.isWhitespace(ch)) { + state = this; + continue; + } else if (ch == QUOTES) { + state = STRING_ARRAY_ELEMENT_STATE; + sm.currentQuoteChar = ch; + sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), + sm.arrayIndexes.peek())); + } else if (ch == '{') { + sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), + sm.arrayIndexes.peek())); + sm.arrayIndexes.push(0); + sm.currentJsonNode = JsonCreator.initNewMapValue(sm); + state = FIRST_FIELD_READY_STATE; + } else if (ch == '[') { + sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), + sm.arrayIndexes.peek())); + sm.arrayIndexes.push(0); + sm.currentJsonNode = JsonCreator.initNewArrayValue(sm); + state = FIRST_ARRAY_ELEMENT_READY_STATE; + } else { + sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), + sm.arrayIndexes.peek())); + state = NON_STRING_ARRAY_ELEMENT_STATE; + } + break; + } + if (state == NON_STRING_ARRAY_ELEMENT_STATE) { + sm.index = i; + } else { + sm.index = i + 1; + } + return state; + } + + } + + private String value() { + String result = new String(this.charBuff, 0, this.charBuffIndex); + this.charBuffIndex = 0; + return result; + } + + private String processFieldName() { + String value = this.value(); + this.fieldNames.push(value); + return value; + } + + /** + * Represents the state during a field name. + */ + private static class FieldNameState implements State { + + @Override + public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + char ch; + State state = null; + for (; i < count; i++) { + ch = buff[i]; + sm.processLocation(ch); + if (ch == sm.currentQuoteChar) { + String jsonFieldName = sm.processFieldName(); + if (sm.jsonFieldDepth == 0) { + sm.currentField = sm.fieldHierarchy.peek().remove(jsonFieldName); + Type fieldType; + if (sm.currentField == null) { + fieldType = sm.restType.peek(); + } else { + fieldType = sm.currentField.getFieldType(); + } + sm.expectedTypes.push(fieldType); + } + state = END_FIELD_NAME_STATE; + } else if (ch == REV_SOL) { + state = FIELD_NAME_ESC_CHAR_PROCESSING_STATE; + } else if (ch == EOF) { + throw new JsonParserException("unexpected end of JSON document"); + } else { + sm.append(ch); + state = this; + continue; + } + break; + } + sm.index = i + 1; + return state; + } + + } + + /** + * Represents the state where a field name definition has ended. + */ + private static class EndFieldNameState implements State { + + @Override + public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + State state = null; + char ch; + for (; i < count; i++) { + ch = buff[i]; + sm.processLocation(ch); + if (StateMachine.isWhitespace(ch)) { + state = this; + continue; + } else if (ch == ':') { + state = FIELD_VALUE_READY_STATE; + } else { + StateMachine.throwExpected(":"); + } + break; + } + sm.index = i + 1; + return state; + } + + } + + /** + * Represents the state where a field value is about to be defined. + */ + private static class FieldValueReadyState implements State { + + @Override + public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + State state = null; + char ch; + for (; i < count; i++) { + ch = buff[i]; + sm.processLocation(ch); + if (StateMachine.isWhitespace(ch)) { + state = this; + continue; + } else if (ch == QUOTES) { + state = STRING_FIELD_VALUE_STATE; + sm.currentQuoteChar = ch; + } else if (ch == '{') { + sm.currentJsonNode = JsonCreator.initNewMapValue(sm); + state = FIRST_FIELD_READY_STATE; + } else if (ch == '[') { + sm.arrayIndexes.push(0); + sm.currentJsonNode = JsonCreator.initNewArrayValue(sm); + JsonCreator.updateRecordFieldValue(StringUtils.fromString(sm.currentField.getFieldName()), + sm.nodesStack.peek(), sm.currentJsonNode); + state = FIRST_ARRAY_ELEMENT_READY_STATE; + } else { + state = NON_STRING_FIELD_VALUE_STATE; + } + break; + } + if (state == NON_STRING_FIELD_VALUE_STATE) { + sm.index = i; + } else { + sm.index = i + 1; + } + return state; + } + + } + + /** + * Represents the state during a string field value is defined. + */ + private static class StringFieldValueState implements State { + + @Override + public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + State state = null; + char ch; + for (; i < count; i++) { + ch = buff[i]; + sm.processLocation(ch); + if (ch == sm.currentQuoteChar) { + String s = sm.value(); + Type expType = sm.expectedTypes.pop(); + if (expType == null) { + state = FIELD_END_STATE; + break; + } + + if (sm.jsonFieldDepth > 0) { + sm.currentJsonNode = JsonCreator.convertAndUpdateCurrentJsonNode(sm, + StringUtils.fromString(s), expType); + } else if (sm.currentField != null) { + sm.currentJsonNode = JsonCreator.convertAndUpdateCurrentJsonNode(sm, + StringUtils.fromString(s), expType); + // TODO: Why ignore anydata type? + } else if (sm.restType.peek() != null && sm.restType.peek().getTag() != TypeTags.ANYDATA_TAG) { + try { + sm.currentJsonNode = JsonCreator.convertAndUpdateCurrentJsonNode(sm, + StringUtils.fromString(s), expType); + // this element will be ignored in projection + } catch (JsonParserException ignored) { } + } + state = FIELD_END_STATE; + } else if (ch == REV_SOL) { + state = STRING_FIELD_ESC_CHAR_PROCESSING_STATE; + } else if (ch == EOF) { + throw new JsonParserException("unexpected end of JSON document"); + } else { + sm.append(ch); + state = this; + continue; + } + break; + } + sm.index = i + 1; + return state; + } + + } + + /** + * Represents the state during a string array element is defined. + */ + private static class StringArrayElementState implements State { + + @Override + public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + State state = null; + char ch; + for (; i < count; i++) { + ch = buff[i]; + sm.processLocation(ch); + if (ch == sm.currentQuoteChar) { + sm.currentJsonNode = JsonCreator.convertAndUpdateCurrentJsonNode(sm, + StringUtils.fromString(sm.value()), sm.expectedTypes.pop()); + state = ARRAY_ELEMENT_END_STATE; + } else if (ch == REV_SOL) { + state = STRING_AE_ESC_CHAR_PROCESSING_STATE; + } else if (ch == EOF) { + throw new JsonParserException("unexpected end of JSON document"); + } else { + sm.append(ch); + state = this; + continue; + } + break; + } + sm.index = i + 1; + return state; + } + } + + /** + * Represents the state during a non-string field value is defined. + */ + private static class NonStringFieldValueState implements State { + + @Override + public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + State state = null; + char ch; + for (; i < count; i++) { + ch = buff[i]; + sm.processLocation(ch); + if (ch == '{') { + state = FIRST_FIELD_READY_STATE; + sm.currentJsonNode = JsonCreator.initNewMapValue(sm); + } else if (ch == '[') { + state = FIRST_ARRAY_ELEMENT_READY_STATE; + sm.currentJsonNode = JsonCreator.initNewArrayValue(sm); + } else if (ch == '}') { + sm.processNonStringValue(); + state = sm.finalizeNonArrayObject(); + } else if (ch == ']') { + sm.processNonStringValue(); + state = sm.finalizeObject(); + } else if (ch == ',') { + sm.processNonStringValue(); + state = NON_FIRST_FIELD_READY_STATE; + } else if (StateMachine.isWhitespace(ch)) { + sm.processNonStringValue(); + state = FIELD_END_STATE; + } else if (ch == EOF) { + throw new JsonParserException("unexpected end of JSON document"); + } else { + sm.append(ch); + state = this; + continue; + } + break; + } + sm.index = i + 1; + return state; + } + } + + /** + * Represents the state during a non-string array element is defined. + */ + private static class NonStringArrayElementState implements State { + + @Override + public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + State state = null; + char ch; + for (; i < count; i++) { + ch = buff[i]; + sm.processLocation(ch); + if (ch == '{') { + state = FIRST_FIELD_READY_STATE; + sm.currentJsonNode = JsonCreator.initNewMapValue(sm); + } else if (ch == '[') { + state = FIRST_ARRAY_ELEMENT_READY_STATE; + sm.currentJsonNode = JsonCreator.initNewArrayValue(sm); + } else if (ch == ']') { + sm.processNonStringValue(); + sm.expectedTypes.pop(); + sm.arrayIndexes.pop(); + state = sm.finalizeObject(); + } else if (ch == ',') { + sm.processNonStringValue(); + state = NON_FIRST_ARRAY_ELEMENT_READY_STATE; + + // Update index of the array element + int arrayIndex = sm.arrayIndexes.pop(); + sm.arrayIndexes.push(arrayIndex + 1); + } else if (StateMachine.isWhitespace(ch)) { + sm.processNonStringValue(); + state = ARRAY_ELEMENT_END_STATE; + } else if (ch == EOF) { + throw new JsonParserException("unexpected end of JSON document"); + } else { + sm.append(ch); + state = this; + continue; + } + break; + } + sm.index = i + 1; + return state; + } + + } + + /** + * Represents the state during a string value is defined. + */ + private static class StringValueState implements State { + + @Override + public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + State state = null; + char ch; + for (; i < count; i++) { + ch = buff[i]; + sm.processLocation(ch); + if (ch == sm.currentQuoteChar) { + sm.currentJsonNode = JsonCreator.convertAndUpdateCurrentJsonNode(sm, + StringUtils.fromString(sm.value()), sm.expectedTypes.peek()); + state = DOC_END_STATE; + } else if (ch == REV_SOL) { + state = STRING_VAL_ESC_CHAR_PROCESSING_STATE; + } else if (ch == EOF) { + throw new JsonParserException("unexpected end of JSON document"); + } else { + sm.append(ch); + state = this; + continue; + } + break; + } + sm.index = i + 1; + return state; + } + } + + private void processNonStringValue() throws JsonParserException { + Type expType = expectedTypes.pop(); + BString value = StringUtils.fromString(value()); + if (expType == null) { + return; + } + + currentJsonNode = JsonCreator.convertAndUpdateCurrentJsonNode(this, value, expType); + } + + /** + * Represents the state during a non-string value is defined. + */ + private static class NonStringValueState implements State { + + @Override + public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + State state = null; + char ch; + for (; i < count; i++) { + ch = buff[i]; + sm.processLocation(ch); + if (StateMachine.isWhitespace(ch) || ch == EOF) { + sm.currentJsonNode = null; + sm.processNonStringValue(); + state = DOC_END_STATE; + } else { + sm.append(ch); + state = this; + continue; + } + break; + } + sm.index = i + 1; + sm.currentJsonNode = JsonCreator.convertAndUpdateCurrentJsonNode(sm, + StringUtils.fromString(sm.value()), sm.expectedTypes.peek()); + return state; + } + + } + + /** + * Represents the state where an object field has ended. + */ + private static class FieldEndState implements State { + + @Override + public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + State state = null; + char ch; + for (; i < count; i++) { + ch = buff[i]; + sm.processLocation(ch); + if (StateMachine.isWhitespace(ch)) { + state = this; + continue; + } else if (ch == ',') { + state = NON_FIRST_FIELD_READY_STATE; + } else if (ch == '}') { + state = sm.finalizeNonArrayObject(); + } else { + StateMachine.throwExpected(",", "}"); + } + break; + } + sm.index = i + 1; + return state; + } + + } + + /** + * Represents the state where an array element has ended. + */ + private static class ArrayElementEndState implements State { + + @Override + public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + State state = null; + char ch; + for (; i < count; i++) { + ch = buff[i]; + sm.processLocation(ch); + if (StateMachine.isWhitespace(ch)) { + state = this; + continue; + } else if (ch == ',') { + state = NON_FIRST_ARRAY_ELEMENT_READY_STATE; + + // Update index of the array element + int arrayIndex = sm.arrayIndexes.pop(); + sm.arrayIndexes.push(arrayIndex + 1); + } else if (ch == ']') { + sm.expectedTypes.pop(); + sm.arrayIndexes.pop(); + state = sm.finalizeObject(); + } else { + StateMachine.throwExpected(",", "]"); + } + break; + } + sm.index = i + 1; + return state; + } + + } + + /** + * Represents the state where an escaped unicode character in hex format is processed + * from a object string field. + */ + private static class StringFieldUnicodeHexProcessingState extends UnicodeHexProcessingState { + + @Override + protected State getSourceState() { + return STRING_FIELD_VALUE_STATE; + } + + } + + /** + * Represents the state where an escaped unicode character in hex format is processed + * from an array string field. + */ + private static class StringAEProcessingState extends UnicodeHexProcessingState { + + @Override + protected State getSourceState() { + return STRING_ARRAY_ELEMENT_STATE; + } + + } + + /** + * Represents the state where an escaped unicode character in hex format is processed + * from a string value. + */ + private static class StringValueUnicodeHexProcessingState extends UnicodeHexProcessingState { + + @Override + protected State getSourceState() { + return STRING_VALUE_STATE; + } + + } + + /** + * Represents the state where an escaped unicode character in hex format is processed + * from a field name. + */ + private static class FieldNameUnicodeHexProcessingState extends UnicodeHexProcessingState { + + @Override + protected State getSourceState() { + return FIELD_NAME_STATE; + } + + } + + /** + * Represents the state where an escaped unicode character in hex format is processed. + */ + private abstract static class UnicodeHexProcessingState implements State { + + protected abstract State getSourceState(); + + @Override + public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + State state = null; + char ch; + for (; i < count; i++) { + ch = buff[i]; + sm.processLocation(ch); + if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f')) { + sm.hexBuilder.append(ch); + if (sm.hexBuilder.length() >= 4) { + sm.append(this.extractUnicodeChar(sm)); + this.reset(sm); + state = this.getSourceState(); + break; + } + state = this; + continue; + } + this.reset(sm); + StateMachine.throwExpected("hexadecimal value of an unicode character"); + break; + } + sm.index = i + 1; + return state; + } + + private void reset(StateMachine sm) { + sm.hexBuilder.setLength(0); + } + + private char extractUnicodeChar(StateMachine sm) { + return StringEscapeUtils.unescapeJava("\\u" + sm.hexBuilder.toString()).charAt(0); + } + + } + + /** + * Represents the state where an escaped character is processed in a object string field. + */ + private static class StringFieldEscapedCharacterProcessingState extends EscapedCharacterProcessingState { + + @Override + protected State getSourceState() { + return STRING_FIELD_VALUE_STATE; + } + + } + + /** + * Represents the state where an escaped character is processed in an array string field. + */ + private static class StringAEEscapedCharacterProcessingState extends EscapedCharacterProcessingState { + + @Override + protected State getSourceState() { + return STRING_ARRAY_ELEMENT_STATE; + } + + } + + /** + * Represents the state where an escaped character is processed in a string value. + */ + private static class StringValueEscapedCharacterProcessingState extends EscapedCharacterProcessingState { + + @Override + protected State getSourceState() { + return STRING_VALUE_STATE; + } + + } + + /** + * Represents the state where an escaped character is processed in a field name. + */ + private static class FieldNameEscapedCharacterProcessingState extends EscapedCharacterProcessingState { + + @Override + protected State getSourceState() { + return FIELD_NAME_STATE; + } + + } + + /** + * Represents the state where an escaped character is processed. + */ + private abstract static class EscapedCharacterProcessingState implements State { + + protected abstract State getSourceState(); + + @Override + public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + State state = null; + char ch; + if (i < count) { + ch = buff[i]; + sm.processLocation(ch); + switch (ch) { + case '"': + sm.append(QUOTES); + state = this.getSourceState(); + break; + case '\\': + sm.append(REV_SOL); + state = this.getSourceState(); + break; + case '/': + sm.append(SOL); + state = this.getSourceState(); + break; + case 'b': + sm.append(BACKSPACE); + state = this.getSourceState(); + break; + case 'f': + sm.append(FORMFEED); + state = this.getSourceState(); + break; + case 'n': + sm.append(NEWLINE); + state = this.getSourceState(); + break; + case 'r': + sm.append(CR); + state = this.getSourceState(); + break; + case 't': + sm.append(HZ_TAB); + state = this.getSourceState(); + break; + case 'u': + if (this.getSourceState() == STRING_FIELD_VALUE_STATE) { + state = STRING_FIELD_UNICODE_HEX_PROCESSING_STATE; + } else if (this.getSourceState() == STRING_VALUE_STATE) { + state = STRING_VALUE_UNICODE_HEX_PROCESSING_STATE; + } else if (this.getSourceState() == FIELD_NAME_STATE) { + state = FIELD_NAME_UNICODE_HEX_PROCESSING_STATE; + } else if (this.getSourceState() == STRING_ARRAY_ELEMENT_STATE) { + state = STRING_AE_PROCESSING_STATE; + } else { + throw new JsonParserException("unknown source '" + this.getSourceState() + + "' in escape char processing state"); + } + break; + default: + StateMachine.throwExpected("escaped characters"); + } + } + sm.index = i + 1; + return state; + } + + } + } +} diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java index 9e76d7c..09aaf9e 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java @@ -19,10 +19,13 @@ package io.ballerina.stdlib.data.jsondata.json; import io.ballerina.runtime.api.Environment; +import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.api.values.BTypedesc; +import java.io.StringReader; + /** * This class is used to convert json inform of string, byte[], byte-stream to record or json type. * @@ -40,6 +43,14 @@ public static Object fromJsonWithType(Object json, BMap options public static Object fromJsonStringWithType(Environment env, Object json, BMap options, BTypedesc typed) { + try { + Type expType = typed.getDescribingType(); + if (json instanceof BString) { + return JsonParser.parse(new StringReader(((BString) json).getValue()), expType); + } + } catch (Exception e) { + return null; + } return null; } } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/Constants.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/Constants.java new file mode 100644 index 0000000..fb7d116 --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/Constants.java @@ -0,0 +1,11 @@ +package io.ballerina.stdlib.data.jsondata.utils; + +import io.ballerina.runtime.api.PredefinedTypes; +import io.ballerina.runtime.api.creators.TypeCreator; +import io.ballerina.runtime.api.types.ArrayType; +import io.ballerina.runtime.api.types.MapType; + +public class Constants { + public static final MapType JSON_MAP_TYPE = TypeCreator.createMapType(PredefinedTypes.TYPE_JSON); + public static final MapType ANYDATA_MAP_TYPE = TypeCreator.createMapType(PredefinedTypes.TYPE_ANYDATA); +} diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/ModuleUtils.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/ModuleUtils.java new file mode 100644 index 0000000..b2735ae --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/ModuleUtils.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.data.jsondata.utils; + +import io.ballerina.runtime.api.Environment; +import io.ballerina.runtime.api.Module; + +/** + * This class will hold module related utility functions. + * + * @since 0.1.0 + */ +public class ModuleUtils { + + /** + * Time standard library package ID. + */ + private static Module module = null; + + private ModuleUtils() { + } + + public static void setModule(Environment env) { + module = env.getCurrentModule(); + } + + public static Module getModule() { + return module; + } +} From 7fae6e03b807852905db92c0e098514bd94b227b Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Fri, 26 Jan 2024 11:42:22 +0530 Subject: [PATCH 04/27] Add projection for record type --- .../data/jsondata/json/JsonCreator.java | 162 ++++-------------- .../stdlib/data/jsondata/json/JsonParser.java | 111 +++++++++--- .../stdlib/data/jsondata/utils/Constants.java | 19 +- 3 files changed, 133 insertions(+), 159 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java index db8fb35..5cfe9a1 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java @@ -19,18 +19,23 @@ package io.ballerina.stdlib.data.jsondata.json; import io.ballerina.runtime.api.TypeTags; -import io.ballerina.runtime.api.creators.ErrorCreator; import io.ballerina.runtime.api.creators.ValueCreator; -import io.ballerina.runtime.api.types.*; -import io.ballerina.runtime.api.utils.JsonUtils; +import io.ballerina.runtime.api.types.ArrayType; +import io.ballerina.runtime.api.types.RecordType; +import io.ballerina.runtime.api.types.TupleType; +import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; -import io.ballerina.runtime.api.values.*; +import io.ballerina.runtime.api.values.BArray; +import io.ballerina.runtime.api.values.BError; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BString; import io.ballerina.stdlib.data.jsondata.FromString; import io.ballerina.stdlib.data.jsondata.utils.Constants; import java.util.HashMap; import java.util.Iterator; +import java.util.Optional; /** * Create objects for partially parsed json. @@ -39,92 +44,7 @@ */ public class JsonCreator { - // convert json[] to output array - static BArray finalizeArray(JsonParser.StateMachine sm, Type arrType, BArray currArr) - throws JsonParser.JsonParserException { - int arrTypeTag = arrType.getTag(); - BListInitialValueEntry[] initialValues = new BListInitialValueEntry[currArr.size()]; - for (int i = 0; i < currArr.size(); i++) { - Object curElement = currArr.get(i); - Type currElmType = TypeUtils.getType(curElement); - if (currElmType.getTag() == TypeTags.ARRAY_TAG) { - if (arrTypeTag == TypeTags.ARRAY_TAG) { - curElement = finalizeArray(sm, ((ArrayType) arrType).getElementType(), (BArray) curElement); - } else if (arrTypeTag == TypeTags.TUPLE_TAG) { - curElement = finalizeArray(sm, ((TupleType) arrType).getTupleTypes().get(i), (BArray) curElement); - } else { - throw new JsonParser.JsonParserException("invalid type in field " + - getCurrentFieldPath(sm)); - } - } else { - if (arrTypeTag == TypeTags.ARRAY_TAG) { - curElement = JsonCreator.convertJSON(sm, curElement, ((ArrayType) arrType).getElementType()); - } else if (arrTypeTag == TypeTags.TUPLE_TAG) { - curElement = JsonCreator.convertJSON(sm, curElement, ((TupleType) arrType).getTupleTypes().get(i)); - } else { - throw new JsonParser.JsonParserException("invalid type in field " + - getCurrentFieldPath(sm)); - } - } - - initialValues[i] = ValueCreator.createListInitialValueEntry(curElement); - } - - if (arrTypeTag == TypeTags.ARRAY_TAG) { - return ValueCreator.createArrayValue((ArrayType) arrType, initialValues); - } else if (arrTypeTag == TypeTags.TUPLE_TAG) { - return ValueCreator.createTupleValue((TupleType) arrType, initialValues); - } else { - throw new JsonParser.JsonParserException("invalid type in field " + - getCurrentFieldPath(sm)); - } - } - - static BArray finalizeArray(JsonTraverse.JsonTree jsonTree, Type arrType, BArray currArr) { - int arrTypeTag = arrType.getTag(); - BListInitialValueEntry[] initialValues = new BListInitialValueEntry[currArr.size()]; - for (int i = 0; i < currArr.size(); i++) { - Object curElement = currArr.get(i); - Type currElmType = TypeUtils.getType(curElement); - if (currElmType.getTag() == TypeTags.ARRAY_TAG) { - if (arrTypeTag == TypeTags.ARRAY_TAG) { - curElement = finalizeArray(jsonTree, ((ArrayType) arrType).getElementType(), (BArray) curElement); - } else if (arrTypeTag == TypeTags.TUPLE_TAG) { - curElement = finalizeArray(jsonTree, ((TupleType) arrType).getTupleTypes().get(i), - (BArray) curElement); - } else { - throw ErrorCreator.createError(StringUtils.fromString("invalid type in field ")); - } - } else { - if (arrTypeTag == TypeTags.ARRAY_TAG) { - curElement = JsonCreator.convertJSON(jsonTree, curElement, ((ArrayType) arrType).getElementType()); - } else if (arrTypeTag == TypeTags.TUPLE_TAG) { - TupleType tupleType = (TupleType) arrType; - if (tupleType.getTupleTypes().size() <= i) { - curElement = JsonCreator.convertJSON(jsonTree, curElement, - (tupleType.getRestType())); - } else { - curElement = JsonCreator.convertJSON(jsonTree, curElement, - (tupleType.getTupleTypes().get(i))); - } - } else { - throw ErrorCreator.createError(StringUtils.fromString("invalid type in field ")); - } - } - initialValues[i] = ValueCreator.createListInitialValueEntry(curElement); - } - - if (arrTypeTag == TypeTags.ARRAY_TAG) { - return ValueCreator.createArrayValue((ArrayType) arrType, initialValues); - } else if (arrTypeTag == TypeTags.TUPLE_TAG) { - return ValueCreator.createTupleValue((TupleType) arrType, initialValues); - } else { - throw ErrorCreator.createError(StringUtils.fromString("invalid type in field ")); - } - } - - static BMap initRecordValue(Type expectedType) - throws JsonParser.JsonParserException { + static BMap initRecordValue(Type expectedType) throws JsonParser.JsonParserException { if (expectedType.getTag() != TypeTags.RECORD_TYPE_TAG) { throw new JsonParser.JsonParserException("expected record type for input type"); } @@ -142,14 +62,14 @@ static BArray initArrayValue(Type expectedType) throws JsonParser.JsonParserExce } } - static BMap initNewMapValue(JsonParser.StateMachine sm) + static Optional> initNewMapValue(JsonParser.StateMachine sm) throws JsonParser.JsonParserException { - Type currentType = TypeUtils.getReferredType(sm.expectedTypes.peek()); - -// if (currentType == null) { -// // TODO: Check optional and Update the CurrentJsonNode. -// return Optional.empty(); -// } + sm.parserContexts.push(JsonParser.StateMachine.ParserContext.MAP); + Type expType = sm.expectedTypes.peek(); + if (expType == null) { + return Optional.empty(); + } + Type currentType = TypeUtils.getReferredType(expType); if (sm.currentJsonNode != null) { sm.nodesStack.push(sm.currentJsonNode); @@ -186,48 +106,24 @@ static BMap initNewMapValue(JsonParser.StateMachine sm) ((BMap) currentJson).put(StringUtils.fromString(sm.fieldNames.peek()), nextMapValue); } - return nextMapValue; + return Optional.of(nextMapValue); } - static BArray initNewArrayValue(JsonParser.StateMachine sm) throws JsonParser.JsonParserException { + static Optional initNewArrayValue(JsonParser.StateMachine sm) throws JsonParser.JsonParserException { + sm.parserContexts.push(JsonParser.StateMachine.ParserContext.ARRAY); + Type expType = sm.expectedTypes.peek(); + if (expType == null) { + return Optional.empty(); + } + Object currentJsonNode = sm.currentJsonNode; BArray nextArrValue = initArrayValue(sm.expectedTypes.peek()); if (currentJsonNode == null) { - return nextArrValue; + return Optional.ofNullable(nextArrValue); } sm.nodesStack.push(sm.currentJsonNode); - return nextArrValue; - } - - static Object convertJSON(JsonParser.StateMachine sm, Object value, Type type) - throws JsonParser.JsonParserException { - // all types are currently allowed for readonly type fields - if (type.getTag() == TypeTags.READONLY_TAG) { - return value; - } - try { - return JsonUtils.convertJSON(value, type); - } catch (Exception e) { - throw new JsonParser.JsonParserException("incompatible value '" + value + "' for type '" + - type + "' in field '" + getCurrentFieldPath(sm) + "'"); - } - } - - static Object convertJSON(JsonTraverse.JsonTree jsonTree, Object value, Type type) { - // all types are currently allowed for readonly type fields - if (type.getTag() == TypeTags.READONLY_TAG) { - return value; - } - try { - return JsonUtils.convertJSON(value, type); - } catch (Exception e) { - if (jsonTree.fieldNames.isEmpty()) { - throw ErrorCreator.createError(StringUtils.fromString("incompatible type for json: " + type)); - } - throw ErrorCreator.createError(StringUtils.fromString("incompatible value '" + value + "' for type '" + - type + "' in field '" + getCurrentFieldPath(jsonTree))); - } + return Optional.ofNullable(nextArrValue); } private static String getCurrentFieldPath(JsonParser.StateMachine sm) { @@ -287,6 +183,10 @@ static void updateRecordFieldValue(BString fieldName, Object parent, Object curr } static Type getMemberType(Type expectedType, int index) { + if (expectedType == null) { + return null; + } + if (expectedType.getTag() == TypeTags.ARRAY_TAG) { return ((ArrayType) expectedType).getElementType(); } else if (expectedType.getTag() == TypeTags.TUPLE_TAG) { diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java index 40efac3..41d19fb 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java @@ -39,6 +39,7 @@ import java.util.Deque; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.Stack; /** @@ -50,13 +51,6 @@ public class JsonParser { private static final ThreadLocal tlStateMachine = ThreadLocal.withInitial(StateMachine::new); - private static Object changeForBString(Object jsonObj) { - if (jsonObj instanceof String) { - return StringUtils.fromString((String) jsonObj); - } - return jsonObj; - } - /** * Parses the contents in the given {@link Reader} and returns a json. * @@ -100,10 +94,6 @@ static class StateMachine { private static final char REV_SOL = '\\'; private static final char SOL = '/'; private static final char EOF = (char) -1; - private static final String NULL = "null"; - private static final String TRUE = "true"; - private static final String FALSE = "false"; - private static final State DOC_START_STATE = new DocumentStartState(); private static final State DOC_END_STATE = new DocumentEndState(); static final State FIRST_FIELD_READY_STATE = new FirstFieldReadyState(); @@ -154,6 +144,7 @@ static class StateMachine { Stack expectedTypes = new Stack<>(); int jsonFieldDepth = 0; Stack arrayIndexes = new Stack<>(); + Stack parserContexts = new Stack<>(); StateMachine() { reset(); @@ -263,6 +254,16 @@ private State finalizeNonArrayObject() throws JsonParserException { if (jsonFieldDepth > 0) { jsonFieldDepth--; } + + if (!expectedTypes.isEmpty() && expectedTypes.peek() == null) { + // Skip the value and continue to next state. + parserContexts.pop(); + if (parserContexts.peek() == ParserContext.MAP) { + return FIELD_END_STATE; + } + return ARRAY_ELEMENT_END_STATE; + } + Map remainingFields = fieldHierarchy.pop(); restType.pop(); for (Field field : remainingFields.values()) { @@ -274,11 +275,21 @@ private State finalizeNonArrayObject() throws JsonParserException { } private State finalizeObject() { - if (this.nodesStack.isEmpty()) { + // Skip the value and continue to next state. + parserContexts.pop(); + + if (!expectedTypes.isEmpty() && expectedTypes.peek() == null) { + if (parserContexts.peek() == ParserContext.MAP) { + return FIELD_END_STATE; + } + return ARRAY_ELEMENT_END_STATE; + } + + if (nodesStack.isEmpty()) { return DOC_END_STATE; } - Object parentNode = this.nodesStack.pop(); + Object parentNode = nodesStack.pop(); if (TypeUtils.getReferredType(TypeUtils.getType(parentNode)).getTag() == TypeTags.RECORD_TYPE_TAG || TypeUtils.getReferredType(TypeUtils.getType(parentNode)).getTag() == TypeTags.MAP_TAG) { currentJsonNode = parentNode; @@ -300,6 +311,10 @@ private State finalizeObject() { return ARRAY_ELEMENT_END_STATE; } + public enum ParserContext { + MAP, + ARRAY + } /** @@ -334,8 +349,10 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J sm.processLocation(ch); if (ch == '{') { sm.currentJsonNode = JsonCreator.initRecordValue(sm.expectedTypes.peek()); + sm.parserContexts.push(JsonParser.StateMachine.ParserContext.MAP); state = FIRST_FIELD_READY_STATE; } else if (ch == '[') { + sm.parserContexts.push(JsonParser.StateMachine.ParserContext.ARRAY); sm.currentJsonNode = JsonCreator.initArrayValue(sm.expectedTypes.peek()); state = FIRST_ARRAY_ELEMENT_READY_STATE; } else if (StateMachine.isWhitespace(ch)) { @@ -437,14 +454,21 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J // Get member type of the array and set as expected type. sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), sm.arrayIndexes.peek())); - sm.currentJsonNode = JsonCreator.initNewMapValue(sm); + Optional> nextMap = JsonCreator.initNewMapValue(sm); + if (nextMap.isPresent()) { + sm.currentJsonNode = nextMap.get(); + } else { + // This will restrict from checking the fieldHierarchy. + sm.jsonFieldDepth++; + } state = FIRST_FIELD_READY_STATE; } else if (ch == '[') { // Get member type of the array and set as expected type. sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), sm.arrayIndexes.peek())); sm.arrayIndexes.push(0); - sm.currentJsonNode = JsonCreator.initNewArrayValue(sm); + Optional nextArray = JsonCreator.initNewArrayValue(sm); + nextArray.ifPresent(array -> sm.currentJsonNode = array); state = FIRST_ARRAY_ELEMENT_READY_STATE; } else if (ch == ']') { state = sm.finalizeObject(); @@ -518,13 +542,20 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), sm.arrayIndexes.peek())); sm.arrayIndexes.push(0); - sm.currentJsonNode = JsonCreator.initNewMapValue(sm); + Optional> nextMap = JsonCreator.initNewMapValue(sm); + if (nextMap.isPresent()) { + sm.currentJsonNode = nextMap.get(); + } else { + // This will restrict from checking the fieldHierarchy. + sm.jsonFieldDepth++; + } state = FIRST_FIELD_READY_STATE; } else if (ch == '[') { sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), sm.arrayIndexes.peek())); sm.arrayIndexes.push(0); - sm.currentJsonNode = JsonCreator.initNewArrayValue(sm); + Optional nextArray = JsonCreator.initNewArrayValue(sm); + nextArray.ifPresent(array -> sm.currentJsonNode = array); state = FIRST_ARRAY_ELEMENT_READY_STATE; } else { sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), @@ -578,6 +609,8 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J fieldType = sm.currentField.getFieldType(); } sm.expectedTypes.push(fieldType); + } else if (sm.expectedTypes.peek() == null) { + sm.expectedTypes.push(null); } state = END_FIELD_NAME_STATE; } else if (ch == REV_SOL) { @@ -644,13 +677,22 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J state = STRING_FIELD_VALUE_STATE; sm.currentQuoteChar = ch; } else if (ch == '{') { - sm.currentJsonNode = JsonCreator.initNewMapValue(sm); + Optional> nextMap = JsonCreator.initNewMapValue(sm); + if (nextMap.isPresent()) { + sm.currentJsonNode = nextMap.get(); + } else { + // This will restrict from checking the fieldHierarchy. + sm.jsonFieldDepth++; + } state = FIRST_FIELD_READY_STATE; } else if (ch == '[') { sm.arrayIndexes.push(0); - sm.currentJsonNode = JsonCreator.initNewArrayValue(sm); - JsonCreator.updateRecordFieldValue(StringUtils.fromString(sm.currentField.getFieldName()), - sm.nodesStack.peek(), sm.currentJsonNode); + Optional nextArray = JsonCreator.initNewArrayValue(sm); + if (nextArray.isPresent()) { + sm.currentJsonNode = nextArray.get(); + JsonCreator.updateRecordFieldValue(StringUtils.fromString(sm.currentField.getFieldName()), + sm.nodesStack.peek(), sm.currentJsonNode); + } state = FIRST_ARRAY_ELEMENT_READY_STATE; } else { state = NON_STRING_FIELD_VALUE_STATE; @@ -765,10 +807,17 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J sm.processLocation(ch); if (ch == '{') { state = FIRST_FIELD_READY_STATE; - sm.currentJsonNode = JsonCreator.initNewMapValue(sm); + Optional> nextMap = JsonCreator.initNewMapValue(sm); + if (nextMap.isPresent()) { + sm.currentJsonNode = nextMap.get(); + } else { + // This will restrict from checking the fieldHierarchy. + sm.jsonFieldDepth++; + } } else if (ch == '[') { state = FIRST_ARRAY_ELEMENT_READY_STATE; - sm.currentJsonNode = JsonCreator.initNewArrayValue(sm); + Optional nextArray = JsonCreator.initNewArrayValue(sm); + nextArray.ifPresent(bArray -> sm.currentJsonNode = bArray); } else if (ch == '}') { sm.processNonStringValue(); state = sm.finalizeNonArrayObject(); @@ -809,15 +858,22 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J sm.processLocation(ch); if (ch == '{') { state = FIRST_FIELD_READY_STATE; - sm.currentJsonNode = JsonCreator.initNewMapValue(sm); + Optional> nextMap = JsonCreator.initNewMapValue(sm); + if (nextMap.isPresent()) { + sm.currentJsonNode = nextMap.get(); + } else { + // This will restrict from checking the fieldHierarchy. + sm.jsonFieldDepth++; + } } else if (ch == '[') { state = FIRST_ARRAY_ELEMENT_READY_STATE; - sm.currentJsonNode = JsonCreator.initNewArrayValue(sm); + Optional nextArray = JsonCreator.initNewArrayValue(sm); + nextArray.ifPresent(bArray -> sm.currentJsonNode = bArray); } else if (ch == ']') { sm.processNonStringValue(); - sm.expectedTypes.pop(); sm.arrayIndexes.pop(); state = sm.finalizeObject(); + sm.expectedTypes.pop(); } else if (ch == ',') { sm.processNonStringValue(); state = NON_FIRST_ARRAY_ELEMENT_READY_STATE; @@ -935,6 +991,7 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J state = NON_FIRST_FIELD_READY_STATE; } else if (ch == '}') { state = sm.finalizeNonArrayObject(); + sm.expectedTypes.pop(); } else { StateMachine.throwExpected(",", "}"); } @@ -968,9 +1025,9 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J int arrayIndex = sm.arrayIndexes.pop(); sm.arrayIndexes.push(arrayIndex + 1); } else if (ch == ']') { - sm.expectedTypes.pop(); sm.arrayIndexes.pop(); state = sm.finalizeObject(); + sm.expectedTypes.pop(); } else { StateMachine.throwExpected(",", "]"); } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/Constants.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/Constants.java index fb7d116..5bdd873 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/Constants.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/Constants.java @@ -1,8 +1,25 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.data.jsondata.utils; import io.ballerina.runtime.api.PredefinedTypes; import io.ballerina.runtime.api.creators.TypeCreator; -import io.ballerina.runtime.api.types.ArrayType; import io.ballerina.runtime.api.types.MapType; public class Constants { From 4b0376d987c06004fa6be476b0728e4ff2912e4c Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Fri, 26 Jan 2024 11:47:29 +0530 Subject: [PATCH 05/27] Add projection for array type --- .../stdlib/data/jsondata/json/JsonCreator.java | 12 ++++++++++-- .../stdlib/data/jsondata/json/JsonParser.java | 11 +++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java index 5cfe9a1..fcec8eb 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java @@ -156,14 +156,22 @@ static Object convertAndUpdateCurrentJsonNode(JsonParser.StateMachine sm, BStrin type + "' in field '" + getCurrentFieldPath(sm) + "'"); } - switch (TypeUtils.getType(currentJson).getTag()) { + Type currentJsonNodeType = TypeUtils.getType(currentJson); + + switch (currentJsonNodeType.getTag()) { case TypeTags.MAP_TAG: case TypeTags.RECORD_TYPE_TAG: ((BMap) currentJson).put(StringUtils.fromString(sm.fieldNames.pop()), convertedValue); return currentJson; case TypeTags.ARRAY_TAG: - ((BArray) currentJson).append(convertedValue); + // Handle projection in array. + ArrayType arrayType = (ArrayType) currentJsonNodeType; + if (arrayType.getState() == ArrayType.ArrayState.CLOSED && + arrayType.getSize() <= sm.arrayIndexes.peek()) { + return currentJson; + } + ((BArray) currentJson).add(sm.arrayIndexes.peek(), convertedValue); return currentJson; case TypeTags.TUPLE_TAG: ((BArray) currentJson).add(sm.arrayIndexes.peek(), convertedValue); diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java index 41d19fb..a1d2c05 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java @@ -22,6 +22,7 @@ import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.creators.ErrorCreator; import io.ballerina.runtime.api.flags.SymbolFlags; +import io.ballerina.runtime.api.types.ArrayType; import io.ballerina.runtime.api.types.Field; import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.Type; @@ -296,9 +297,16 @@ private State finalizeObject() { return FIELD_END_STATE; } + Type parentNodeType = TypeUtils.getType(parentNode); switch (TypeUtils.getType(parentNode).getTag()) { case TypeTags.ARRAY_TAG: - ((BArray) parentNode).append(currentJsonNode); + // Handle projection in array. + ArrayType arrayType = (ArrayType) parentNodeType; + if (arrayType.getState() == ArrayType.ArrayState.CLOSED && + arrayType.getSize() <= arrayIndexes.peek()) { + break; + } + ((BArray) parentNode).add(arrayIndexes.peek(), currentJsonNode); break; case TypeTags.TUPLE_TAG: ((BArray) parentNode).add(arrayIndexes.peek(), currentJsonNode); @@ -541,7 +549,6 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J } else if (ch == '{') { sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), sm.arrayIndexes.peek())); - sm.arrayIndexes.push(0); Optional> nextMap = JsonCreator.initNewMapValue(sm); if (nextMap.isPresent()) { sm.currentJsonNode = nextMap.get(); From d49b38c20aaa1cde68da085813b32a076c45dc83 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Fri, 26 Jan 2024 16:55:18 +0530 Subject: [PATCH 06/27] Support map type in fromJsonStringWithType --- .../stdlib/data/jsondata/json/JsonCreator.java | 18 ++++++++++++++---- .../stdlib/data/jsondata/json/JsonParser.java | 8 ++++++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java index fcec8eb..82a080d 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java @@ -21,6 +21,7 @@ import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.types.ArrayType; +import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.TupleType; import io.ballerina.runtime.api.types.Type; @@ -45,11 +46,15 @@ public class JsonCreator { static BMap initRecordValue(Type expectedType) throws JsonParser.JsonParserException { - if (expectedType.getTag() != TypeTags.RECORD_TYPE_TAG) { - throw new JsonParser.JsonParserException("expected record type for input type"); - } - return ValueCreator.createRecordValue((RecordType) expectedType); + switch (expectedType.getTag()) { + case TypeTags.RECORD_TYPE_TAG: + return ValueCreator.createRecordValue((RecordType) expectedType); + case TypeTags.MAP_TAG: + return ValueCreator.createMapValue((MapType) expectedType); + default: + throw new JsonParser.JsonParserException("expected record type for input type"); + } } static BArray initArrayValue(Type expectedType) throws JsonParser.JsonParserException { @@ -83,6 +88,11 @@ static Optional> initNewMapValue(JsonParser.StateMachine s sm.fieldHierarchy.push(new HashMap<>(recordType.getFields())); sm.restType.push(recordType.getRestFieldType()); break; + case TypeTags.MAP_TAG: + nextMapValue = ValueCreator.createMapValue((MapType) currentType); + sm.fieldHierarchy.push(new HashMap<>()); + sm.restType.push(((MapType) currentType).getConstrainedType()); + break; case TypeTags.JSON_TAG: nextMapValue = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); sm.fieldHierarchy.push(new HashMap<>()); diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java index a1d2c05..7916568 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java @@ -24,6 +24,7 @@ import io.ballerina.runtime.api.flags.SymbolFlags; import io.ballerina.runtime.api.types.ArrayType; import io.ballerina.runtime.api.types.Field; +import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.utils.StringUtils; @@ -208,8 +209,11 @@ public Object execute(Reader reader, Type type) throws BError, JsonParserExcepti // case TypeTags.JSON_TAG: // case TypeTags.ANYDATA_TAG: // case TypeTags.UNION_TAG: -// case TypeTags.MAP_TAG: -// break; + case TypeTags.MAP_TAG: + expectedTypes.push(type); + fieldHierarchy.push(new HashMap<>()); + restType.push(((MapType) type).getConstrainedType()); + break; default: throw ErrorCreator.createError(StringUtils.fromString("unsupported type: " + type)); From fd44ed1524582e8af4ad3e5052f2ca0c5b9aec4a Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Mon, 29 Jan 2024 11:46:22 +0530 Subject: [PATCH 07/27] Support json and anydata as expected type --- .../stdlib/data/jsondata/FromString.java | 24 ++++++++++-- .../data/jsondata/json/JsonCreator.java | 37 +++++++++++++------ .../stdlib/data/jsondata/json/JsonParser.java | 19 ++++++---- 3 files changed, 59 insertions(+), 21 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java index af175d2..e43c234 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java @@ -18,16 +18,21 @@ package io.ballerina.stdlib.data.jsondata; +import io.ballerina.runtime.api.PredefinedTypes; import io.ballerina.runtime.api.TypeTags; +import io.ballerina.runtime.api.creators.ErrorCreator; +import io.ballerina.runtime.api.creators.TypeCreator; import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.types.ReferenceType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.UnionType; +import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.values.BDecimal; import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.api.values.BTypedesc; +import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -66,6 +71,8 @@ public static Object fromStringWithType(BString string, Type expType) { return stringToNull(value); case TypeTags.UNION_TAG: return stringToUnion(string, (UnionType) expType); + case TypeTags.JSON_TAG: + return stringToJson(string); case TypeTags.TYPE_REFERENCED_TYPE_TAG: return fromStringWithType(string, ((ReferenceType) expType).getReferredType()); default: @@ -134,6 +141,19 @@ private static Object stringToUnion(BString string, UnionType expType) throws Nu return returnError(string.getValue(), expType.toString()); } + private static Object stringToJson(BString string) { + ArrayList jsonMembers = new ArrayList<>(); + jsonMembers.add(PredefinedTypes.TYPE_NULL); + jsonMembers.add(PredefinedTypes.TYPE_BOOLEAN); + jsonMembers.add(PredefinedTypes.TYPE_INT); + jsonMembers.add(PredefinedTypes.TYPE_FLOAT); + jsonMembers.add(PredefinedTypes.TYPE_DECIMAL); + jsonMembers.add(PredefinedTypes.TYPE_STRING); + + UnionType jsonType = TypeCreator.createUnionType(jsonMembers); + return stringToUnion(string, jsonType); + } + private static boolean hasFloatOrDecimalLiteralSuffix(String value) { int length = value.length(); if (length == 0) { @@ -152,8 +172,6 @@ private static boolean hasFloatOrDecimalLiteralSuffix(String value) { } private static BError returnError(String string, String expType) { -// return DiagnosticLog.error(DiagnosticErrorCode.CANNOT_CONVERT_TO_EXPECTED_TYPE, -// PredefinedTypes.TYPE_STRING.getName(), string, expType); - return null; + return ErrorCreator.createError(StringUtils.fromString("Cannot convert to the exptype")); } } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java index 82a080d..36f465b 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java @@ -18,6 +18,7 @@ package io.ballerina.stdlib.data.jsondata.json; +import io.ballerina.runtime.api.PredefinedTypes; import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.types.ArrayType; @@ -45,25 +46,34 @@ */ public class JsonCreator { - static BMap initRecordValue(Type expectedType) throws JsonParser.JsonParserException { - + static BMap initRootMapValue(Type expectedType) + throws JsonParser.JsonParserException { switch (expectedType.getTag()) { case TypeTags.RECORD_TYPE_TAG: return ValueCreator.createRecordValue((RecordType) expectedType); case TypeTags.MAP_TAG: return ValueCreator.createMapValue((MapType) expectedType); + case TypeTags.JSON_TAG: + return ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); + case TypeTags.ANYDATA_TAG: + return ValueCreator.createMapValue(Constants.ANYDATA_MAP_TYPE); default: throw new JsonParser.JsonParserException("expected record type for input type"); } } static BArray initArrayValue(Type expectedType) throws JsonParser.JsonParserException { - if (expectedType.getTag() == TypeTags.TUPLE_TAG) { - return ValueCreator.createTupleValue((TupleType) expectedType); - } else if (expectedType.getTag() == TypeTags.ARRAY_TAG) { - return ValueCreator.createArrayValue((ArrayType) expectedType); - } else { - throw new JsonParser.JsonParserException("expected array or tuple type for input type"); + switch (expectedType.getTag()) { + case TypeTags.TUPLE_TAG: + return ValueCreator.createTupleValue((TupleType) expectedType); + case TypeTags.ARRAY_TAG: + return ValueCreator.createArrayValue((ArrayType) expectedType); + case TypeTags.JSON_TAG: + return ValueCreator.createArrayValue(PredefinedTypes.TYPE_JSON_ARRAY); + case TypeTags.ANYDATA_TAG: + return ValueCreator.createArrayValue(PredefinedTypes.TYPE_ANYDATA_ARRAY); + default: + throw new JsonParser.JsonParserException("expected array or tuple type for input type"); } } @@ -97,13 +107,11 @@ static Optional> initNewMapValue(JsonParser.StateMachine s nextMapValue = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); sm.fieldHierarchy.push(new HashMap<>()); sm.restType.push(sm.definedJsonType); - sm.jsonFieldDepth++; break; case TypeTags.ANYDATA_TAG: nextMapValue = ValueCreator.createMapValue(Constants.ANYDATA_MAP_TYPE); sm.fieldHierarchy.push(new HashMap<>()); sm.restType.push(sm.definedJsonType); - sm.jsonFieldDepth++; break; default: throw new JsonParser.JsonParserException("invalid type in field " + getCurrentFieldPath(sm)); @@ -159,7 +167,7 @@ static String getCurrentFieldPath(JsonTraverse.JsonTree jsonTree) { static Object convertAndUpdateCurrentJsonNode(JsonParser.StateMachine sm, BString value, Type type) throws JsonParser.JsonParserException { Object currentJson = sm.currentJsonNode; - Object convertedValue = FromString.fromStringWithType(value, type); + Object convertedValue = convertToExpectedType(value, type); // TODO: Remove null case after properly returning error. if (convertedValue == null || convertedValue instanceof BError) { throw new JsonParser.JsonParserException("incompatible value '" + value + "' for type '" + @@ -191,6 +199,13 @@ static Object convertAndUpdateCurrentJsonNode(JsonParser.StateMachine sm, BStrin } } + private static Object convertToExpectedType(BString value, Type type) { + if (type.getTag() == TypeTags.ANYDATA_TAG) { + return FromString.fromStringWithType(value, PredefinedTypes.TYPE_JSON); + } + return FromString.fromStringWithType(value, type); + } + static void updateRecordFieldValue(BString fieldName, Object parent, Object currentJson) { switch (TypeUtils.getType(parent).getTag()) { case TypeTags.MAP_TAG: diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java index 7916568..8cdfb21 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java @@ -130,6 +130,7 @@ static class StateMachine { Object currentJsonNode; Deque nodesStack; + // TODO: Need group same level field and keep the hierarchy. Deque fieldNames; private StringBuilder hexBuilder = new StringBuilder(4); @@ -206,8 +207,12 @@ public Object execute(Reader reader, Type type) throws BError, JsonParserExcepti case TypeTags.STRING_TAG: expectedTypes.push(type); break; -// case TypeTags.JSON_TAG: -// case TypeTags.ANYDATA_TAG: + case TypeTags.JSON_TAG: + case TypeTags.ANYDATA_TAG: + expectedTypes.push(type); + fieldHierarchy.push(new HashMap<>()); + restType.push(type); + break; // case TypeTags.UNION_TAG: case TypeTags.MAP_TAG: expectedTypes.push(type); @@ -295,13 +300,13 @@ private State finalizeObject() { } Object parentNode = nodesStack.pop(); - if (TypeUtils.getReferredType(TypeUtils.getType(parentNode)).getTag() == TypeTags.RECORD_TYPE_TAG || - TypeUtils.getReferredType(TypeUtils.getType(parentNode)).getTag() == TypeTags.MAP_TAG) { + Type parentNodeType = TypeUtils.getType(parentNode); + int parentNodeTypeTag = TypeUtils.getReferredType(parentNodeType).getTag(); + if (parentNodeTypeTag == TypeTags.RECORD_TYPE_TAG || parentNodeTypeTag == TypeTags.MAP_TAG) { currentJsonNode = parentNode; return FIELD_END_STATE; } - Type parentNodeType = TypeUtils.getType(parentNode); switch (TypeUtils.getType(parentNode).getTag()) { case TypeTags.ARRAY_TAG: // Handle projection in array. @@ -360,7 +365,7 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J ch = buff[i]; sm.processLocation(ch); if (ch == '{') { - sm.currentJsonNode = JsonCreator.initRecordValue(sm.expectedTypes.peek()); + sm.currentJsonNode = JsonCreator.initRootMapValue(sm.expectedTypes.peek()); sm.parserContexts.push(JsonParser.StateMachine.ParserContext.MAP); state = FIRST_FIELD_READY_STATE; } else if (ch == '[') { @@ -701,7 +706,7 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J Optional nextArray = JsonCreator.initNewArrayValue(sm); if (nextArray.isPresent()) { sm.currentJsonNode = nextArray.get(); - JsonCreator.updateRecordFieldValue(StringUtils.fromString(sm.currentField.getFieldName()), + JsonCreator.updateRecordFieldValue(StringUtils.fromString(sm.fieldNames.peek()), sm.nodesStack.peek(), sm.currentJsonNode); } state = FIRST_ARRAY_ELEMENT_READY_STATE; From f5e79d8cac5a20a9777ebe50a1aa38647acc2597 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Wed, 31 Jan 2024 23:51:36 +0530 Subject: [PATCH 08/27] Correct mistakes in the code and log proper errors --- ballerina/Dependencies.toml | 25 ++++- ballerina/json_api.bal | 4 +- .../stdlib/data/jsondata/FromString.java | 41 +++++--- .../data/jsondata/json/JsonCreator.java | 73 ++++++++------- .../stdlib/data/jsondata/json/JsonParser.java | 93 ++++++++++++------- .../data/jsondata/json/JsonTraverse.java | 43 +++++---- .../stdlib/data/jsondata/json/Native.java | 9 +- .../jsondata/utils/DiagnosticErrorCode.java | 49 ++++++++++ .../data/jsondata/utils/DiagnosticLog.java | 53 +++++++++++ native/src/main/resources/error.properties | 48 ++++++++++ 10 files changed, 334 insertions(+), 104 deletions(-) create mode 100644 native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticErrorCode.java create mode 100644 native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticLog.java create mode 100644 native/src/main/resources/error.properties diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index e52d88c..1d55757 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -12,7 +12,8 @@ org = "ballerina" name = "data.jsondata" version = "0.1.0" dependencies = [ - {org = "ballerina", name = "jballerina.java"} + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "test"} ] modules = [ {org = "ballerina", packageName = "data.jsondata", moduleName = "data.jsondata"} @@ -26,3 +27,25 @@ modules = [ {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} ] +[[package]] +org = "ballerina" +name = "lang.error" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "test" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.error"} +] +modules = [ + {org = "ballerina", packageName = "test", moduleName = "test"} +] + diff --git a/ballerina/json_api.bal b/ballerina/json_api.bal index 56f01d6..9722271 100644 --- a/ballerina/json_api.bal +++ b/ballerina/json_api.bal @@ -17,10 +17,10 @@ import ballerina/jballerina.java; public isolated function fromJsonWithType(json v, Options options = {}, typedesc t = <>) - returns t|error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; + returns t|Error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; public isolated function fromJsonStringWithType(string|byte[]|stream s, Options options = {}, typedesc t = <>) - returns t|error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; + returns t|Error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; # Represent the options that can be used for filtering in the projection. # diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java index e43c234..0166401 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java @@ -27,6 +27,7 @@ import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.UnionType; import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BDecimal; import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BString; @@ -34,6 +35,7 @@ import java.util.ArrayList; import java.util.Comparator; +import java.util.HashMap; import java.util.List; /** @@ -43,6 +45,27 @@ */ public class FromString { + private static final HashMap TYPE_PRIORITY_ORDER = new HashMap<>() {{ + int precedence = 0; + put(TypeTags.INT_TAG, precedence++); + put(TypeTags.FLOAT_TAG, precedence++); + put(TypeTags.DECIMAL_TAG, precedence++); + put(TypeTags.NULL_TAG, precedence++); + put(TypeTags.BOOLEAN_TAG, precedence++); + put(TypeTags.JSON_TAG, precedence++); + put(TypeTags.STRING_TAG, precedence); + }}; + + private static final ArrayList BASIC_JSON_MEMBER_TYPES = new ArrayList<>() {{ + add(PredefinedTypes.TYPE_NULL); + add(PredefinedTypes.TYPE_BOOLEAN); + add(PredefinedTypes.TYPE_INT); + add(PredefinedTypes.TYPE_FLOAT); + add(PredefinedTypes.TYPE_DECIMAL); + add(PredefinedTypes.TYPE_STRING); + }}; + private static final UnionType JSON_TYPE_WITH_BASIC_TYPES = TypeCreator.createUnionType(BASIC_JSON_MEMBER_TYPES); + public static Object fromStringWithType(BString string, BTypedesc typed) { Type expType = typed.getDescribingType(); @@ -72,7 +95,7 @@ public static Object fromStringWithType(BString string, Type expType) { case TypeTags.UNION_TAG: return stringToUnion(string, (UnionType) expType); case TypeTags.JSON_TAG: - return stringToJson(string); + return stringToUnion(string, JSON_TYPE_WITH_BASIC_TYPES); case TypeTags.TYPE_REFERENCED_TYPE_TAG: return fromStringWithType(string, ((ReferenceType) expType).getReferredType()); default: @@ -118,7 +141,8 @@ private static Object stringToNull(String value) throws NumberFormatException { private static Object stringToUnion(BString string, UnionType expType) throws NumberFormatException { List memberTypes = expType.getMemberTypes(); - memberTypes.sort(Comparator.comparingInt(t -> t.getTag())); + memberTypes.sort(Comparator.comparingInt(t -> TYPE_PRIORITY_ORDER.getOrDefault( + TypeUtils.getReferredType(t).getTag(), Integer.MAX_VALUE))); boolean isStringExpType = false; for (Type memberType : memberTypes) { try { @@ -141,19 +165,6 @@ private static Object stringToUnion(BString string, UnionType expType) throws Nu return returnError(string.getValue(), expType.toString()); } - private static Object stringToJson(BString string) { - ArrayList jsonMembers = new ArrayList<>(); - jsonMembers.add(PredefinedTypes.TYPE_NULL); - jsonMembers.add(PredefinedTypes.TYPE_BOOLEAN); - jsonMembers.add(PredefinedTypes.TYPE_INT); - jsonMembers.add(PredefinedTypes.TYPE_FLOAT); - jsonMembers.add(PredefinedTypes.TYPE_DECIMAL); - jsonMembers.add(PredefinedTypes.TYPE_STRING); - - UnionType jsonType = TypeCreator.createUnionType(jsonMembers); - return stringToUnion(string, jsonType); - } - private static boolean hasFloatOrDecimalLiteralSuffix(String value) { int length = value.length(); if (length == 0) { diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java index 36f465b..1398d34 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java @@ -34,9 +34,12 @@ import io.ballerina.runtime.api.values.BString; import io.ballerina.stdlib.data.jsondata.FromString; import io.ballerina.stdlib.data.jsondata.utils.Constants; +import io.ballerina.stdlib.data.jsondata.utils.DiagnosticErrorCode; +import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Optional; /** @@ -46,8 +49,7 @@ */ public class JsonCreator { - static BMap initRootMapValue(Type expectedType) - throws JsonParser.JsonParserException { + static BMap initRootMapValue(Type expectedType) { switch (expectedType.getTag()) { case TypeTags.RECORD_TYPE_TAG: return ValueCreator.createRecordValue((RecordType) expectedType); @@ -58,11 +60,11 @@ static BMap initRootMapValue(Type expectedType) case TypeTags.ANYDATA_TAG: return ValueCreator.createMapValue(Constants.ANYDATA_MAP_TYPE); default: - throw new JsonParser.JsonParserException("expected record type for input type"); + throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, expectedType, "map type"); } } - static BArray initArrayValue(Type expectedType) throws JsonParser.JsonParserException { + static BArray initArrayValue(Type expectedType) { switch (expectedType.getTag()) { case TypeTags.TUPLE_TAG: return ValueCreator.createTupleValue((TupleType) expectedType); @@ -73,12 +75,11 @@ static BArray initArrayValue(Type expectedType) throws JsonParser.JsonParserExce case TypeTags.ANYDATA_TAG: return ValueCreator.createArrayValue(PredefinedTypes.TYPE_ANYDATA_ARRAY); default: - throw new JsonParser.JsonParserException("expected array or tuple type for input type"); + throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, expectedType, "list type"); } } - static Optional> initNewMapValue(JsonParser.StateMachine sm) - throws JsonParser.JsonParserException { + static Optional> initNewMapValue(JsonParser.StateMachine sm) { sm.parserContexts.push(JsonParser.StateMachine.ParserContext.MAP); Type expType = sm.expectedTypes.peek(); if (expType == null) { @@ -106,28 +107,26 @@ static Optional> initNewMapValue(JsonParser.StateMachine s case TypeTags.JSON_TAG: nextMapValue = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); sm.fieldHierarchy.push(new HashMap<>()); - sm.restType.push(sm.definedJsonType); + sm.restType.push(PredefinedTypes.TYPE_JSON); break; case TypeTags.ANYDATA_TAG: nextMapValue = ValueCreator.createMapValue(Constants.ANYDATA_MAP_TYPE); sm.fieldHierarchy.push(new HashMap<>()); - sm.restType.push(sm.definedJsonType); + sm.restType.push(PredefinedTypes.TYPE_JSON); break; default: - throw new JsonParser.JsonParserException("invalid type in field " + getCurrentFieldPath(sm)); + throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE_FOR_FIELD, getCurrentFieldPath(sm)); } Object currentJson = sm.currentJsonNode; int valueTypeTag = TypeUtils.getType(currentJson).getTag(); if (valueTypeTag == TypeTags.MAP_TAG || valueTypeTag == TypeTags.RECORD_TYPE_TAG) { - // TODO: Fix -> Using fieldName as the key is wrong all the time when json as exp type. - ((BMap) currentJson).put(StringUtils.fromString(sm.fieldNames.peek()), - nextMapValue); + ((BMap) currentJson).put(StringUtils.fromString(sm.fieldNames.peek()), nextMapValue); } return Optional.of(nextMapValue); } - static Optional initNewArrayValue(JsonParser.StateMachine sm) throws JsonParser.JsonParserException { + static Optional initNewArrayValue(JsonParser.StateMachine sm) { sm.parserContexts.push(JsonParser.StateMachine.ParserContext.ARRAY); Type expType = sm.expectedTypes.peek(); if (expType == null) { @@ -154,28 +153,15 @@ private static String getCurrentFieldPath(JsonParser.StateMachine sm) { return result.toString(); } - static String getCurrentFieldPath(JsonTraverse.JsonTree jsonTree) { - Iterator itr = jsonTree.fieldNames.descendingIterator(); - - StringBuilder result = new StringBuilder(itr.hasNext() ? itr.next() : ""); - while (itr.hasNext()) { - result.append(".").append(itr.next()); - } - return result.toString(); - } - - static Object convertAndUpdateCurrentJsonNode(JsonParser.StateMachine sm, BString value, Type type) - throws JsonParser.JsonParserException { + static Object convertAndUpdateCurrentJsonNode(JsonParser.StateMachine sm, BString value, Type type) { Object currentJson = sm.currentJsonNode; Object convertedValue = convertToExpectedType(value, type); - // TODO: Remove null case after properly returning error. - if (convertedValue == null || convertedValue instanceof BError) { - throw new JsonParser.JsonParserException("incompatible value '" + value + "' for type '" + - type + "' in field '" + getCurrentFieldPath(sm) + "'"); + if (convertedValue instanceof BError) { + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_VALUE_FOR_FIELD, value, type, + getCurrentFieldPath(sm)); } Type currentJsonNodeType = TypeUtils.getType(currentJson); - switch (currentJsonNodeType.getTag()) { case TypeTags.MAP_TAG: case TypeTags.RECORD_TYPE_TAG: @@ -223,9 +209,32 @@ static Type getMemberType(Type expectedType, int index) { if (expectedType.getTag() == TypeTags.ARRAY_TAG) { return ((ArrayType) expectedType).getElementType(); } else if (expectedType.getTag() == TypeTags.TUPLE_TAG) { - return ((TupleType) expectedType).getTupleTypes().get(index); + TupleType tupleType = (TupleType) expectedType; + List tupleTypes = tupleType.getTupleTypes(); + if (tupleTypes.size() < index + 1) { + return tupleType.getRestType(); + } + return tupleTypes.get(index); } else { return expectedType; } } + + static void validateListSize(int currentIndex, Type expType) { + int expLength = 0; + if (expType == null) { + return; + } + + if (expType.getTag() == TypeTags.ARRAY_TAG) { + expLength = ((ArrayType) expType).getSize(); + } else if (expType.getTag() == TypeTags.TUPLE_TAG) { + TupleType tupleType = (TupleType) expType; + expLength = tupleType.getTupleTypes().size(); + } + + if (expLength >= 0 && expLength > currentIndex + 1) { + throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); + } + } } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java index 8cdfb21..d59b59b 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java @@ -18,21 +18,22 @@ package io.ballerina.stdlib.data.jsondata.json; -import io.ballerina.runtime.api.PredefinedTypes; import io.ballerina.runtime.api.TypeTags; -import io.ballerina.runtime.api.creators.ErrorCreator; import io.ballerina.runtime.api.flags.SymbolFlags; import io.ballerina.runtime.api.types.ArrayType; import io.ballerina.runtime.api.types.Field; import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.UnionType; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; +import io.ballerina.stdlib.data.jsondata.utils.DiagnosticErrorCode; +import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; import org.apache.commons.lang3.StringEscapeUtils; import java.io.IOException; @@ -61,7 +62,7 @@ public class JsonParser { * @throws BError for any parsing error */ public static Object parse(Reader reader, Type type) - throws BError, JsonParserException { + throws BError { StateMachine sm = tlStateMachine.get(); try { return sm.execute(reader, TypeUtils.getReferredType(type)); @@ -126,7 +127,6 @@ static class StateMachine { new StringFieldUnicodeHexProcessingState(); private static final State STRING_VALUE_UNICODE_HEX_PROCESSING_STATE = new StringValueUnicodeHexProcessingState(); - Type definedJsonType = PredefinedTypes.TYPE_JSON; Object currentJsonNode; Deque nodesStack; @@ -185,9 +185,9 @@ private void processLocation(char ch) { } } - public Object execute(Reader reader, Type type) throws BError, JsonParserException { + public Object execute(Reader reader, Type type) throws BError { switch (type.getTag()) { - // TODO: Handle all anydata type except record. + // TODO: Handle readonly and singleton type as expType. case TypeTags.RECORD_TYPE_TAG: RecordType recordType = (RecordType) type; expectedTypes.push(recordType); @@ -213,15 +213,19 @@ public Object execute(Reader reader, Type type) throws BError, JsonParserExcepti fieldHierarchy.push(new HashMap<>()); restType.push(type); break; -// case TypeTags.UNION_TAG: case TypeTags.MAP_TAG: expectedTypes.push(type); fieldHierarchy.push(new HashMap<>()); restType.push(((MapType) type).getConstrainedType()); break; + case TypeTags.UNION_TAG: + if (isSupportedUnionType((UnionType) type)) { + expectedTypes.push(type); + break; + } + throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE, type); default: - throw ErrorCreator.createError(StringUtils.fromString("unsupported type: " + type)); - + throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE, type); } State currentState = DOC_START_STATE; @@ -236,11 +240,29 @@ public Object execute(Reader reader, Type type) throws BError, JsonParserExcepti } return currentJsonNode; } catch (IOException e) { - throw ErrorCreator.createError(StringUtils.fromString("Error reading JSON: " + e.getMessage())); + throw DiagnosticLog.error(DiagnosticErrorCode.JSON_READER_FAILURE, e.getMessage()); } catch (JsonParserException e) { - throw ErrorCreator.createError(StringUtils.fromString(e.getMessage() + " at line: " + this.line + - " column: " + this.column)); + throw DiagnosticLog.error(DiagnosticErrorCode.JSON_PARSER_EXCEPTION, e.getMessage(), line, column); + } + } + + private boolean isSupportedUnionType(UnionType type) { + boolean isContainUnsupportedMember = false; + for (Type memberType : type.getMemberTypes()) { + switch (memberType.getTag()) { + case TypeTags.RECORD_TYPE_TAG: + case TypeTags.OBJECT_TYPE_TAG: + case TypeTags.MAP_TAG: + case TypeTags.JSON_TAG: + case TypeTags.ANYDATA_TAG: + isContainUnsupportedMember = true; + break; + case TypeTags.UNION_TAG: + isContainUnsupportedMember = isSupportedUnionType(type); + break; + } } + return !isContainUnsupportedMember; } private void append(char ch) { @@ -260,7 +282,7 @@ private void growCharBuff() { this.charBuff = newBuff; } - private State finalizeNonArrayObject() throws JsonParserException { + private State finalizeNonArrayObject() { if (jsonFieldDepth > 0) { jsonFieldDepth--; } @@ -278,7 +300,7 @@ private State finalizeNonArrayObject() throws JsonParserException { restType.pop(); for (Field field : remainingFields.values()) { if (SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.REQUIRED)) { - throw new JsonParserException("required field '" + field.getFieldName() + "' not present in JSON"); + throw DiagnosticLog.error(DiagnosticErrorCode.REQUIRED_FIELD_NOT_PRESENT, field.getFieldName()); } } return finalizeObject(); @@ -370,6 +392,11 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J state = FIRST_FIELD_READY_STATE; } else if (ch == '[') { sm.parserContexts.push(JsonParser.StateMachine.ParserContext.ARRAY); + Type expType = sm.expectedTypes.peek(); + // In this point we know rhs is json[] or anydata[] hence init index counter. + if (expType.getTag() == TypeTags.JSON_TAG || expType.getTag() == TypeTags.ANYDATA_TAG) { + sm.arrayIndexes.push(0); + } sm.currentJsonNode = JsonCreator.initArrayValue(sm.expectedTypes.peek()); state = FIRST_ARRAY_ELEMENT_READY_STATE; } else if (StateMachine.isWhitespace(ch)) { @@ -437,6 +464,7 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J continue; } else if (ch == '}') { state = sm.finalizeNonArrayObject(); + sm.expectedTypes.pop(); } else { StateMachine.throwExpected("\"", "}"); } @@ -489,6 +517,7 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J state = FIRST_ARRAY_ELEMENT_READY_STATE; } else if (ch == ']') { state = sm.finalizeObject(); + sm.expectedTypes.pop(); } else { state = NON_STRING_ARRAY_ELEMENT_STATE; sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), @@ -751,13 +780,12 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J } else if (sm.currentField != null) { sm.currentJsonNode = JsonCreator.convertAndUpdateCurrentJsonNode(sm, StringUtils.fromString(s), expType); - // TODO: Why ignore anydata type? - } else if (sm.restType.peek() != null && sm.restType.peek().getTag() != TypeTags.ANYDATA_TAG) { + } else if (sm.restType.peek() != null) { try { sm.currentJsonNode = JsonCreator.convertAndUpdateCurrentJsonNode(sm, StringUtils.fromString(s), expType); // this element will be ignored in projection - } catch (JsonParserException ignored) { } + } catch (BError ignored) { } } state = FIELD_END_STATE; } else if (ch == REV_SOL) { @@ -790,8 +818,7 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J ch = buff[i]; sm.processLocation(ch); if (ch == sm.currentQuoteChar) { - sm.currentJsonNode = JsonCreator.convertAndUpdateCurrentJsonNode(sm, - StringUtils.fromString(sm.value()), sm.expectedTypes.pop()); + sm.processValue(); state = ARRAY_ELEMENT_END_STATE; } else if (ch == REV_SOL) { state = STRING_AE_ESC_CHAR_PROCESSING_STATE; @@ -835,16 +862,18 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J Optional nextArray = JsonCreator.initNewArrayValue(sm); nextArray.ifPresent(bArray -> sm.currentJsonNode = bArray); } else if (ch == '}') { - sm.processNonStringValue(); + sm.processValue(); state = sm.finalizeNonArrayObject(); + sm.expectedTypes.pop(); } else if (ch == ']') { - sm.processNonStringValue(); + sm.processValue(); state = sm.finalizeObject(); + sm.expectedTypes.pop(); } else if (ch == ',') { - sm.processNonStringValue(); + sm.processValue(); state = NON_FIRST_FIELD_READY_STATE; } else if (StateMachine.isWhitespace(ch)) { - sm.processNonStringValue(); + sm.processValue(); state = FIELD_END_STATE; } else if (ch == EOF) { throw new JsonParserException("unexpected end of JSON document"); @@ -886,19 +915,19 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J Optional nextArray = JsonCreator.initNewArrayValue(sm); nextArray.ifPresent(bArray -> sm.currentJsonNode = bArray); } else if (ch == ']') { - sm.processNonStringValue(); - sm.arrayIndexes.pop(); + sm.processValue(); + int currentIndex = sm.arrayIndexes.pop(); state = sm.finalizeObject(); - sm.expectedTypes.pop(); + JsonCreator.validateListSize(currentIndex, sm.expectedTypes.pop()); } else if (ch == ',') { - sm.processNonStringValue(); + sm.processValue(); state = NON_FIRST_ARRAY_ELEMENT_READY_STATE; // Update index of the array element int arrayIndex = sm.arrayIndexes.pop(); sm.arrayIndexes.push(arrayIndex + 1); } else if (StateMachine.isWhitespace(ch)) { - sm.processNonStringValue(); + sm.processValue(); state = ARRAY_ELEMENT_END_STATE; } else if (ch == EOF) { throw new JsonParserException("unexpected end of JSON document"); @@ -947,7 +976,7 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J } } - private void processNonStringValue() throws JsonParserException { + private void processValue() { Type expType = expectedTypes.pop(); BString value = StringUtils.fromString(value()); if (expType == null) { @@ -971,7 +1000,7 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J sm.processLocation(ch); if (StateMachine.isWhitespace(ch) || ch == EOF) { sm.currentJsonNode = null; - sm.processNonStringValue(); + sm.processValue(); state = DOC_END_STATE; } else { sm.append(ch); @@ -1041,9 +1070,9 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J int arrayIndex = sm.arrayIndexes.pop(); sm.arrayIndexes.push(arrayIndex + 1); } else if (ch == ']') { - sm.arrayIndexes.pop(); + int currentIndex = sm.arrayIndexes.pop(); state = sm.finalizeObject(); - sm.expectedTypes.pop(); + JsonCreator.validateListSize(currentIndex, sm.expectedTypes.pop()); } else { StateMachine.throwExpected(",", "]"); } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java index 9e4038e..cfcffa6 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java @@ -18,8 +18,8 @@ package io.ballerina.stdlib.data.jsondata.json; +import io.ballerina.runtime.api.PredefinedTypes; import io.ballerina.runtime.api.TypeTags; -import io.ballerina.runtime.api.creators.ErrorCreator; import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.flags.SymbolFlags; import io.ballerina.runtime.api.types.ArrayType; @@ -29,12 +29,15 @@ import io.ballerina.runtime.api.types.TupleType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.UnionType; +import io.ballerina.runtime.api.utils.JsonUtils; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; +import io.ballerina.stdlib.data.jsondata.utils.DiagnosticErrorCode; +import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; import java.util.ArrayDeque; import java.util.Deque; @@ -95,6 +98,7 @@ public Object traverseJson(Object json, Type type) { traverseMapJsonOrArrayJson(json, referredType); break; case TypeTags.ARRAY_TAG: + rootArray = referredType; currentJsonNode = ValueCreator.createArrayValue((ArrayType) referredType); nodesStack.push(currentJsonNode); traverseMapJsonOrArrayJson(json, referredType); @@ -120,12 +124,12 @@ public Object traverseJson(Object json, Type type) { // Ignore } } - return ErrorCreator.createError(StringUtils.fromString("incompatible type for json: " + type)); + return DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, type, PredefinedTypes.TYPE_ANYDATA); case TypeTags.JSON_TAG: case TypeTags.ANYDATA_TAG: return json; default: - return ErrorCreator.createError(StringUtils.fromString("incompatible type for json: " + type)); + return DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, type, PredefinedTypes.TYPE_ANYDATA); } return currentJsonNode; } @@ -142,7 +146,7 @@ private void traverseMapJsonOrArrayJson(Object json, Type type) { this.fieldHierarchy.pop(); this.restType.pop(); } - throw ErrorCreator.createError(StringUtils.fromString("incompatible type for json: " + type)); + throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE_FOR_FIELD, getCurrentFieldPath()); } nodesStack.pop(); } @@ -167,8 +171,8 @@ private void traverseMapValue(BMap map, Object parentJsonNode) switch (currentFieldTypeTag) { case TypeTags.MAP_TAG: if (!checkTypeCompatibility(((MapType) currentFieldType).getConstrainedType(), mapValue)) { - throw ErrorCreator.createError(StringUtils.fromString("incompatible value '" + mapValue + - "' for type '" + currentFieldType + "' in field '" + getCurrentFieldPath() + "'")); + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_VALUE_FOR_FIELD, mapValue, + currentFieldType, getCurrentFieldPath()); } ((BMap) currentJsonNode).put(StringUtils.fromString(fieldNames.pop()), mapValue); @@ -199,8 +203,7 @@ private void traverseArrayValue(Object json, Object parentJsonNode) { case TypeTags.ARRAY_TAG: int expectedArraySize = ((ArrayType) rootArray).getSize(); if (expectedArraySize > array.getLength()) { - throw ErrorCreator.createError(StringUtils.fromString( - "size mismatch between target and source")); + throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); } if (expectedArraySize == -1) { traverseArrayMembers(array.getLength(), array, parentJsonNode); @@ -212,8 +215,7 @@ private void traverseArrayValue(Object json, Object parentJsonNode) { Type restType = ((TupleType) rootArray).getRestType(); int expectedTupleTypeCount = ((TupleType) rootArray).getTupleTypes().size(); if (expectedTupleTypeCount > array.getLength()) { - throw ErrorCreator.createError(StringUtils.fromString( - "size mismatch between target and source")); + throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); } for (int i = 0; i < array.getLength(); i++) { @@ -225,7 +227,7 @@ private void traverseArrayValue(Object json, Object parentJsonNode) { } else { continue; } - ((BArray) parentJsonNode).append(currentJsonNode); + ((BArray) parentJsonNode).add(i, currentJsonNode); } break; } @@ -236,7 +238,7 @@ private void traverseArrayMembers(long length, BArray array, Object parentJsonNo for (int i = 0; i < length; i++) { Object jsonMember = array.get(i); currentJsonNode = traverseJson(jsonMember, ((ArrayType) rootArray).getElementType()); - ((BArray) parentJsonNode).append(currentJsonNode); + ((BArray) parentJsonNode).add(i, currentJsonNode); } } @@ -284,19 +286,24 @@ private boolean checkTypeCompatibility(Type constraintType, Object json) { private void checkOptionalFieldsAndLogError(Map currentField) { currentField.values().forEach(field -> { if (SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.REQUIRED)) { - throw ErrorCreator.createError(StringUtils.fromString("required field '" + field.getFieldName() + - "' not present in JSON")); + throw DiagnosticLog.error(DiagnosticErrorCode.REQUIRED_FIELD_NOT_PRESENT, field.getFieldName()); } }); } private Object convertToBasicType(Object json, Type targetType) { - Type jsonType = TypeUtils.getType(json); - if (TypeUtils.isSameType(jsonType, targetType)) { + if (targetType.getTag() == TypeTags.READONLY_TAG) { return json; } - return ErrorCreator.createError(StringUtils.fromString("incompatible type expected '" + targetType + - "', found '" + jsonType + "'")); + try { + // TODO: string x = check jsondata:fromJsonWithType(5); should it error? + return JsonUtils.convertJSON(json, targetType); + } catch (Exception e) { + if (fieldNames.isEmpty()) { + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_TYPE, targetType, targetType); + } + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_TYPE, targetType, targetType); + } } private String getCurrentFieldPath() { diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java index 09aaf9e..6654d27 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java @@ -20,6 +20,7 @@ import io.ballerina.runtime.api.Environment; import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.api.values.BTypedesc; @@ -36,8 +37,8 @@ public class Native { public static Object fromJsonWithType(Object json, BMap options, BTypedesc typed) { try { return JsonTraverse.traverse(json, typed.getDescribingType()); - } catch (Exception e) { - return null; + } catch (BError e) { + return e; } } @@ -48,8 +49,8 @@ public static Object fromJsonStringWithType(Environment env, Object json, BMap Date: Tue, 6 Feb 2024 11:56:01 +0530 Subject: [PATCH 09/27] Add tests for Json module --- ballerina/tests/from_json_string_test.bal | 1017 +++++++++++++++++ ballerina/tests/from_json_test.bal | 1016 ++++++++++++++++ ballerina/tests/types.bal | 181 +++ .../data/jsondata/json/JsonCreator.java | 7 +- .../data/jsondata/json/JsonTraverse.java | 29 +- native/src/main/resources/error.properties | 2 +- 6 files changed, 2239 insertions(+), 13 deletions(-) create mode 100644 ballerina/tests/from_json_string_test.bal create mode 100644 ballerina/tests/from_json_test.bal create mode 100644 ballerina/tests/types.bal diff --git a/ballerina/tests/from_json_string_test.bal b/ballerina/tests/from_json_string_test.bal new file mode 100644 index 0000000..1d0e002 --- /dev/null +++ b/ballerina/tests/from_json_string_test.bal @@ -0,0 +1,1017 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you 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. + +import ballerina/test; + +// Possitive tests for fromJsonStringWithType() function. + +@test:Config +isolated function testJsonStringToBasicTypes() returns error? { + int val1 = check fromJsonStringWithType("5"); + test:assertEquals(val1, 5); + + float val2 = check fromJsonStringWithType("5.5"); + test:assertEquals(val2, 5.5); + + decimal val3 = check fromJsonStringWithType("5.5"); + test:assertEquals(val3, 5.5d); + + string val4 = check fromJsonStringWithType("hello"); + test:assertEquals(val4, "hello"); + + boolean val5 = check fromJsonStringWithType("true"); + test:assertEquals(val5, true); + + () val6 = check fromJsonStringWithType("null"); + test:assertEquals(val6, null); +} + +@test:Config +isolated function testSimpleJsonStringToRecord() returns error? { + string j = string `{"a": "hello", "b": 1}`; + + record {|string a; int b;|} recA = check fromJsonStringWithType(j); + test:assertEquals(recA.a, "hello"); + test:assertEquals(recA.b, 1); +} + +@test:Config +isolated function testSimpleJsonStringToRecordWithProjection() returns error? { + string str = string `{"a": "hello", "b": 1}`; + + record {|string a;|} recA = check fromJsonStringWithType(str); + test:assertEquals(recA.length(), 1); + test:assertEquals(recA.a, "hello"); + test:assertEquals(recA, {"a": "hello"}); +} + +@test:Config +isolated function testNestedJsonStringToRecord() returns error? { + string str = string `{ + "a": "hello", + "b": 1, + "c": { + "d": "world", + "e": 2 + } + }`; + + record {|string a; int b; record {|string d; int e;|} c;|} recA = check fromJsonStringWithType(str); + test:assertEquals(recA.length(), 3); + test:assertEquals(recA.a, "hello"); + test:assertEquals(recA.b, 1); + test:assertEquals(recA.c.length(), 2); + test:assertEquals(recA.c.d, "world"); + test:assertEquals(recA.c.e, 2); +} + +@test:Config +isolated function testNestedJsonStringToRecordWithProjection() returns error? { + string str = string `{ + "a": "hello", + "b": 1, + "c": { + "d": "world", + "e": 2 + } + }`; + + record {|string a; record {|string d;|} c;|} recA = check fromJsonStringWithType(str); + test:assertEquals(recA.a, "hello"); + test:assertEquals(recA.c.d, "world"); + test:assertEquals(recA, {"a": "hello", "c": {"d": "world"}}); +} + +@test:Config +isolated function testJsonStringToRecordWithOptionalFields() returns error? { + string str = string `{"a": "hello"}`; + + record {|string a; int b?;|} recA = check fromJsonStringWithType(str); + test:assertEquals(recA.length(), 1); + test:assertEquals(recA.a, "hello"); + test:assertEquals(recA.b, null); +} + +@test:Config +isolated function testJsonStringToRecordWithOptionalFieldsWithProjection() returns error? { + string str = string `{ + "a": "hello", + "b": 1, + "c": { + "d": "world", + "e": 2 + } + }`; + + record {|string a; record {|string d; int f?;|} c;|} recA = check fromJsonStringWithType(str); + test:assertEquals(recA.a, "hello"); + test:assertEquals(recA.c.d, "world"); + test:assertEquals(recA, {"a": "hello", "c": {"d": "world"}}); +} + +@test:Config +isolated function testFromJsonStringWithType1() returns error? { + string str = string `{ + "id": 2, + "name": "Anne", + "address": { + "street": "Main", + "city": "94" + } + }`; + + R x = check fromJsonStringWithType(str); + test:assertEquals(x.id, 2); + test:assertEquals(x.name, "Anne"); + test:assertEquals(x.address.street, "Main"); + test:assertEquals(x.address.city, "94"); +} + +@test:Config +isolated function testMapTypeAsFieldTypeInRecordForJsonString() returns error? { + string str = string `{ + "employees": { + "John": "Manager", + "Anne": "Developer" + } + }`; + + Company x = check fromJsonStringWithType(str); + test:assertEquals(x.employees["John"], "Manager"); + test:assertEquals(x.employees["Anne"], "Developer"); +} + +@test:Config +isolated function testFromJsonStringWithType2() returns error? { + string str = string `{ + "name": "John", + "age": 30, + "address": { + "street": "123 Main St", + "zipcode": 10001, + "coordinates": { + "latitude": 40.7128, + "longitude": -74.0060 + } + } + }`; + + Person x = check fromJsonStringWithType(str); + test:assertEquals(x.length(), 3); + test:assertEquals(x.name, "John"); + test:assertEquals(x.age, 30); + test:assertEquals(x.address.length(), 3); + test:assertEquals(x.address.street, "123 Main St"); + test:assertEquals(x.address.zipcode, 10001); + test:assertEquals(x.address.coordinates.length(), 2); + test:assertEquals(x.address.coordinates.latitude, 40.7128); + test:assertEquals(x.address.coordinates.longitude, -74.0060); +} + +@test:Config +isolated function testFromJsonStringWithType3() returns error? { + string str = string `{ + "title": "To Kill a Mockingbird", + "author": { + "name": "Harper Lee", + "birthdate": "1926-04-28", + "hometown": "Monroeville, Alabama", + "local": false + }, + "price": 10.5, + "publisher": { + "name": "J. B. Lippincott & Co.", + "year": 1960, + "location": "Philadelphia", + "month": 4 + } + }`; + + Book x = check fromJsonStringWithType(str); + test:assertEquals(x.title, "To Kill a Mockingbird"); + test:assertEquals(x.author.name, "Harper Lee"); + test:assertEquals(x.author.birthdate, "1926-04-28"); + test:assertEquals(x.author.hometown, "Monroeville, Alabama"); + test:assertEquals(x.publisher.name, "J. B. Lippincott & Co."); + test:assertEquals(x.publisher.year, 1960); + test:assertEquals(x.publisher["location"], "Philadelphia"); + test:assertEquals(x["price"], 10.5); + test:assertEquals(x.author["local"], false); +} + +@test:Config +isolated function testFromJsonStringWithType4() returns error? { + string str = string `{ + "name": "School Twelve", + "city": 23, + "number": 12, + "section": 2, + "flag": true, + "tp": 12345 + }`; + + School x = check fromJsonStringWithType(str); + test:assertEquals(x.length(), 6); + test:assertEquals(x.name, "School Twelve"); + test:assertEquals(x.number, 12); + test:assertEquals(x.flag, true); + test:assertEquals(x["section"], 2); + test:assertEquals(x["tp"], 12345); +} + +@test:Config +isolated function testFromJsonStringWithType5() returns error? { + string str = string `{ + "intValue": 10, + "floatValue": 10.5, + "stringValue": "test", + "decimalValue": 10.50, + "doNotParse": "abc" + }`; + + TestRecord x = check fromJsonStringWithType(str); + test:assertEquals(x.length(), 5); + test:assertEquals(x.intValue, 10); + test:assertEquals(x.floatValue, 10.5f); + test:assertEquals(x.stringValue, "test"); + test:assertEquals(x.decimalValue, 10.50d); + test:assertEquals(x["doNotParse"], "abc"); +} + +@test:Config +isolated function testFromJsonStringWithType6() returns error? { + string str = string `{ + "id": 1, + "name": "Class A", + "student": { + "id": 2, + "name": "John Doe", + "school": { + "name": "ABC School", + "address": { + "street": "Main St", + "city": "New York" + } + } + }, + "teacher": { + "id": 3, + "name": "Jane Smith" + }, + "monitor": null + }`; + + Class x = check fromJsonStringWithType(str); + test:assertEquals(x.length(), 5); + test:assertEquals(x.id, 1); + test:assertEquals(x.name, "Class A"); + test:assertEquals(x.student.length(), 3); + test:assertEquals(x.student.id, 2); + test:assertEquals(x.student.name, "John Doe"); + test:assertEquals(x.student.school.length(), 2); + test:assertEquals(x.student.school.name, "ABC School"); + test:assertEquals(x.student.school.address.length(), 2); + test:assertEquals(x.student.school.address.street, "Main St"); + test:assertEquals(x.student.school.address.city, "New York"); + test:assertEquals(x.teacher.length(), 2); + test:assertEquals(x.teacher.id, 3); + test:assertEquals(x.teacher.name, "Jane Smith"); + test:assertEquals(x.monitor, null); +} + +@test:Config +isolated function testFromJsonStringWithType7() returns error? { + string nestedJsonStr = string `{ + "intValue": 5, + "floatValue": 2.5, + "stringValue": "nested", + "decimalValue": 5.00 + }`; + + string str = string `{ + "intValue": 10, + "nested1": ${nestedJsonStr} + }`; + + TestRecord2 x = check fromJsonStringWithType(str); + test:assertEquals(x.length(), 2); + test:assertEquals(x.intValue, 10); + test:assertEquals(x.nested1.length(), 4); + test:assertEquals(x.nested1.intValue, 5); +} + +@test:Config +isolated function testFromJsonStringWithType8() returns error? { + string str = string `{ + "street": "Main", + "city": "Mahar", + "house": 94 + }`; + + TestR x = check fromJsonStringWithType(str); + test:assertEquals(x.street, "Main"); + test:assertEquals(x.city, "Mahar"); +} + +@test:Config +isolated function testFromJsonStringWithType9() returns error? { + string str = string `{ + "street": "Main", + "city": "Mahar", + "houses": [94, 95, 96] + }`; + + TestArr1 x = check fromJsonStringWithType(str); + test:assertEquals(x.length(), 3); + test:assertEquals(x.street, "Main"); + test:assertEquals(x.city, "Mahar"); + test:assertEquals(x.houses, [94, 95, 96]); +} + +@test:Config +isolated function testFromJsonStringWithType10() returns error? { + string str = string `{ + "street": "Main", + "city": 11, + "house": [94, "Gedara"] + }`; + + TestArr2 x = check fromJsonStringWithType(str); + test:assertEquals(x.length(), 3); + test:assertEquals(x.street, "Main"); + test:assertEquals(x.city, 11); + test:assertEquals(x.house, [94, "Gedara"]); +} + +@test:Config +isolated function testFromJsonStringWithType11() returns error? { + string str = string `{ + "street": "Main", + "city": "Mahar", + "house": [94, [1, 2, 3]] + }`; + + TestArr3 x = check fromJsonStringWithType(str); + test:assertEquals(x.length(), 3); + test:assertEquals(x.street, "Main"); + test:assertEquals(x.city, "Mahar"); + test:assertEquals(x.house, [94, [1, 2, 3]]); +} + +@test:Config +isolated function testFromJsonStringWithType12() returns error? { + string str = string `{ + "street": "Main", + "city": { + "name": "Mahar", + "code": 94 + }, + "flag": true + }`; + + TestJson x = check fromJsonStringWithType(str); + test:assertEquals(x.length(), 3); + test:assertEquals(x.street, "Main"); + test:assertEquals(x.city, {"name": "Mahar", "code": 94}); +} + +@test:Config +isolated function testFromJsonStringWithType13() returns error? { + string str = string `{ + "street": "Main", + "city": "Mahar", + "house": [94, [1, 3, "4"]] + }`; + + TestArr3 x = check fromJsonStringWithType(str); + test:assertEquals(x.length(), 3); + test:assertEquals(x.street, "Main"); + test:assertEquals(x.city, "Mahar"); + test:assertEquals(x.house, [94, [1, 3, 4]]); +} + +@test:Config +isolated function testFromJsoStringWithType14() returns error? { + string str = string `{ + "id": 12, + "name": "Anne", + "address": { + "id": 34, + "city": "94", + "street": "York road" + } + }`; + + RN x = check fromJsonStringWithType(str); + test:assertEquals(x.length(), 3); + test:assertEquals(x.id, 12); + test:assertEquals(x.name, "Anne"); + test:assertEquals(x.address.length(), 3); + test:assertEquals(x.address.id, 34); + test:assertEquals(x.address.city, "94"); + test:assertEquals(x.address.street, "York road"); +} + +@test:Config +isolated function testFromJsonStringWithType15() returns error? { + string str = string `[1, 2, 3]`; + + IntArr x = check fromJsonStringWithType(str); + test:assertEquals(x, [1, 2, 3]); +} + +@test:Config +isolated function testFromJsonStringWithType16() returns error? { + string str = string `[1, "abc", [3, 4.0]]`; + + TUPLE x = check fromJsonStringWithType(str); + test:assertEquals(x, [1, "abc", [3, 4.0]]); +} + +@test:Config +isolated function testFromJsonStringWithType17() returns error? { + string str = string `{ + "street": "Main", + "city": { + "name": "Mahar", + "code": 94, + "internal": { + "id": 12, + "agent": "Anne" + } + }, + "flag": true + }`; + + TestJson x = check fromJsonStringWithType(str); + test:assertEquals(x.length(), 3); + test:assertEquals(x.street, "Main"); + test:assertEquals(x.city, {"name": "Mahar", "code": 94, "internal": {"id": 12, "agent": "Anne"}}); +} + +@test:Config +isolated function testFromJsonStringWithType18() returns error? { + string str = string `{ + "books": [ + { + "title": "The Great Gatsby", + "author": "F. Scott Fitzgerald" + }, + { + "title": "The Grapes of Wrath", + "author": "John Steinbeck" + }, + { + "title": "Binary Echoes: Unraveling the Digital Web", + "author": "Alexandra Quinn" + } + ] + }`; + + Library x = check fromJsonStringWithType(str); + test:assertEquals(x.books.length(), 2); + test:assertEquals(x.books[0].title, "The Great Gatsby"); + test:assertEquals(x.books[0].author, "F. Scott Fitzgerald"); + test:assertEquals(x.books[1].title, "The Grapes of Wrath"); + test:assertEquals(x.books[1].author, "John Steinbeck"); +} + +type LibraryB record { + [BookA, BookA] books; +}; + +type LibraryC record {| + [BookA, BookA...] books; +|}; + +@test:Config +isolated function testFromJsonStringWithType19() returns error? { + string str = string `{ + "books": [ + { + "title": "The Great Gatsby", + "author": "F. Scott Fitzgerald" + }, + { + "title": "The Grapes of Wrath", + "author": "John Steinbeck" + }, + { + "title": "Binary Echoes: Unraveling the Digital Web", + "author": "Alexandra Quinn" + } + ] + }`; + + LibraryB x = check fromJsonStringWithType(str); + test:assertEquals(x.books.length(), 2); + test:assertEquals(x.books[0].title, "The Great Gatsby"); + test:assertEquals(x.books[0].author, "F. Scott Fitzgerald"); + test:assertEquals(x.books[1].title, "The Grapes of Wrath"); + test:assertEquals(x.books[1].author, "John Steinbeck"); + + LibraryC y = check fromJsonStringWithType(str); + test:assertEquals(y.books.length(), 3); + test:assertEquals(y.books[0].title, "The Great Gatsby"); + test:assertEquals(y.books[0].author, "F. Scott Fitzgerald"); + test:assertEquals(y.books[1].title, "The Grapes of Wrath"); + test:assertEquals(y.books[1].author, "John Steinbeck"); + test:assertEquals(y.books[2].title, "Binary Echoes: Unraveling the Digital Web"); + test:assertEquals(y.books[2].author, "Alexandra Quinn"); +} + +@test:Config +isolated function testFromJsonStringWithType20() returns error? { + string str1 = string `{ + "a": { + "c": "world", + "d": "2" + }, + "b": { + "c": "world", + "d": "2" + } + }`; + + record {| + record {| + string c; + string d; + |}...; + |} val1 = check fromJsonStringWithType(str1); + test:assertEquals(val1.length(), 2); + test:assertEquals(val1["a"]["c"], "world"); + test:assertEquals(val1["a"]["d"], "2"); + test:assertEquals(val1["b"]["c"], "world"); + test:assertEquals(val1["b"]["d"], "2"); + + record {| + map...; + |} val2 = check fromJsonStringWithType(str1); + test:assertEquals(val2.length(), 2); + test:assertEquals(val2["a"]["c"], "world"); + test:assertEquals(val2["a"]["d"], "2"); + test:assertEquals(val2["b"]["c"], "world"); + test:assertEquals(val2["b"]["d"], "2"); + + string str3 = string `{ + "a": [{ + "c": "world", + "d": "2" + }], + "b": [{ + "c": "world", + "d": "2" + }] + }`; + + record {| + record {| + string c; + string d; + |}[]...; + |} val3 = check fromJsonStringWithType(str3); + test:assertEquals(val3.length(), 2); + test:assertEquals(val3["a"], [{ + "c": "world", + "d": "2" + }]); + test:assertEquals(val3["b"], [{ + "c": "world", + "d": "2" + }]); +} + +@test:Config +isolated function testUnionTypeAsExpTypeForFromJsonStringWithType() returns error? { + decimal|float val1 = check fromJsonStringWithType("1.0"); + test:assertEquals(val1, 1.0); + + string str2 = string `{ + "a": "hello", + "b": 1 + }`; + + record {| + decimal|float b; + |} val2 = check fromJsonStringWithType(str2); + test:assertEquals(val2.length(), 1); + test:assertEquals(val2.b, 1.0); + + string str3 = string `{ + "a": { + "b": 1, + "d": { + "e": "false" + } + }, + "c": 2 + }`; + + record {| + record {| decimal|int b; record {| string|boolean e; |} d; |} a; + decimal|float c; + |} val3 = check fromJsonStringWithType(str3); + test:assertEquals(val3.length(), 2); + test:assertEquals(val3.a.length(), 2); + test:assertEquals(val3.a.b, 1); + test:assertEquals(val3.a.d.e, false); + test:assertEquals(val3.c, 2.0); +} + +@test:Config +isolated function testAnydataAsExpTypeForFromJsonStringWithType() returns error? { + string jsonStr1 = string `1`; + anydata val1 = check fromJsonStringWithType(jsonStr1); + test:assertEquals(val1, 1); + + string jsonStr2 = string `{ + "a": "hello", + "b": 1 + }`; + + anydata val2 = check fromJsonStringWithType(jsonStr2); + test:assertEquals(val2, {"a": "hello", "b": 1}); + + string jsonStr3 = string `{ + "a": { + "b": 1, + "d": { + "e": "hello" + } + }, + "c": 2 + }`; + + anydata val3 = check fromJsonStringWithType(jsonStr3); + test:assertEquals(val3, {"a": {"b": 1, "d": {"e": "hello"}}, "c": 2}); + + string jsonStr4 = string `{ + "a": [{ + "b": 1, + "d": { + "e": "hello" + } + }], + "c": 2 + }`; + + anydata val4 = check fromJsonStringWithType(jsonStr4); + test:assertEquals(val4, {"a": [{"b": 1, "d": {"e": "hello"}}], "c": 2}); + + string str5 = string `[[1], 2]`; + anydata val5 = check fromJsonStringWithType(str5); + test:assertEquals(val5, [[1], 2]); +} + +@test:Config +isolated function testJsonAsExpTypeForFromJsonStringWithType() returns error? { + string jsonStr1 = string `1`; + json val1 = check fromJsonStringWithType(jsonStr1); + test:assertEquals(val1, 1); + + string jsonStr2 = string `{ + "a": "hello", + "b": 1 + }`; + + json val2 = check fromJsonStringWithType(jsonStr2); + test:assertEquals(val2, {"a": "hello", "b": 1}); + + string jsonStr3 = string `{ + "a": { + "b": 1, + "d": { + "e": "hello" + } + }, + "c": 2 + }`; + + json val3 = check fromJsonStringWithType(jsonStr3); + test:assertEquals(val3, {"a": {"b": 1, "d": {"e": "hello"}}, "c": 2}); + + string jsonStr4 = string `{ + "a": [{ + "b": 1, + "d": { + "e": "hello" + } + }], + "c": 2 + }`; + + json val4 = check fromJsonStringWithType(jsonStr4); + test:assertEquals(val4, {"a": [{"b": 1, "d": {"e": "hello"}}], "c": 2}); + + string str5 = string `[[1], 2]`; + json val5 = check fromJsonStringWithType(str5); + test:assertEquals(val5, [[1], 2]); +} + +@test:Config +isolated function testMapAsExpTypeForFromJsonStringWithType() returns error? { + string jsonStr1 = string `{ + "a": "hello", + "b": 1 + }`; + + map val1 = check fromJsonStringWithType(jsonStr1); + test:assertEquals(val1, {"a": "hello", "b": "1"}); + + string jsonStr2 = string `{ + "a": "hello", + "b": 1, + "c": { + "d": "world", + "e": 2 + } + }`; + record {| + string a; + int b; + map c; + |} val2 = check fromJsonStringWithType(jsonStr2); + test:assertEquals(val2.a, "hello"); + test:assertEquals(val2.b, 1); + test:assertEquals(val2.c, {"d": "world", "e": "2"}); + + string jsonStr3 = string `{ + "a": { + "c": "world", + "d": 2 + }, + "b": { + "c": "world", + "d": 2 + } + }`; + + map> val3 = check fromJsonStringWithType(jsonStr3); + test:assertEquals(val3, {"a": {"c": "world", "d": "2"}, "b": {"c": "world", "d": "2"}}); + + record {| + map a; + |} val4 = check fromJsonStringWithType(jsonStr3); + test:assertEquals(val4.a, {"c": "world", "d": "2"}); + + map val5 = check fromJsonStringWithType(jsonStr3); + test:assertEquals(val5, {"a": {"c": "world", "d": 2}, "b": {"c": "world", "d": 2}}); +} + +@test:Config +isolated function testProjectionInTupleForFromJsonStringWithType() returns error? { + string str1 = string `[1, 2, 3, 4, 5, 8]`; + [string, float] val1 = check fromJsonStringWithType(str1); + test:assertEquals(val1, ["1", 2.0]); + + string str2 = string `{ + "a": [1, 2, 3, 4, 5, 8] + }`; + record {| [string, float] a; |} val2 = check fromJsonStringWithType(str2); + test:assertEquals(val2.a, ["1", 2.0]); + + string str3 = string `[1, "4"]`; + [float] val3 = check fromJsonStringWithType(str3); + test:assertEquals(val3, [1.0]); + + string str4 = string `["1", {}]`; + [float] val4 = check fromJsonStringWithType(str4); + test:assertEquals(val4, [1.0]); + + string str5 = string `["1", [], {"name": 1}]`; + [float] val5 = check fromJsonStringWithType(str5); + test:assertEquals(val5, [1.0]); +} + +@test:Config +isolated function testProjectionInArrayForFromJsonStringWithType() returns error? { + string strVal = string `[1, 2, 3, 4, 5]`; + int[] val = check fromJsonStringWithType(strVal); + test:assertEquals(val, [1, 2, 3, 4, 5]); + + string strVal2 = string `[1, 2, 3, 4, 5]`; + int[2] val2 = check fromJsonStringWithType(strVal2); + test:assertEquals(val2, [1, 2]); + + string strVal3 = string `{ + "a": [1, 2, 3, 4, 5] + }`; + record {| int[2] a; |} val3 = check fromJsonStringWithType(strVal3); + test:assertEquals(val3, {a: [1, 2]}); + + string strVal4 = string `{ + "a": [1, 2, 3, 4, 5], + "b": [1, 2, 3, 4, 5] + }`; + record {| int[2] a; int[3] b; |} val4 = check fromJsonStringWithType(strVal4); + test:assertEquals(val4, {a: [1, 2], b: [1, 2, 3]}); + + string strVal5 = string `{ + "employees": [ + { "name": "Prakanth", + "age": 26 + }, + { "name": "Kevin", + "age": 25 + } + ] + }`; + record {| record {| string name; int age; |}[1] employees; |} val5 = check fromJsonStringWithType(strVal5); + test:assertEquals(val5, {employees: [{name: "Prakanth", age: 26}]}); +} + +@test:Config +isolated function testProjectionInRecordForFromJsonStringWithType() returns error? { + string jsonStr1 = string `{"name": "John", "age": 30, "city": "New York"}`; + record {| string name; string city; |} val1 = check fromJsonStringWithType(jsonStr1); + test:assertEquals(val1, {name: "John", city: "New York"}); + + string jsonStr2 = string `{"name": John, "age": "30", "city": "New York"}`; + record {| string name; string city; |} val2 = check fromJsonStringWithType(jsonStr2); + test:assertEquals(val2, {name: "John", city: "New York"}); + + string jsonStr3 = string `{ "name": "John", + "company": { + "name": "wso2", + "year": 2024, + "addrees": { + "street": "123", + "city": "Berkeley" + } + }, + "city": "New York" }`; + record {| string name; string city; |} val3 = check fromJsonStringWithType(jsonStr3); + test:assertEquals(val3, {name: "John", city: "New York"}); + + string jsonStr4 = string `{ "name": "John", + "company": [{ + "name": "wso2", + "year": 2024, + "addrees": { + "street": "123", + "city": "Berkeley" + } + }], + "city": "New York" }`; + record {| string name; string city; |} val4 = check fromJsonStringWithType(jsonStr4); + test:assertEquals(val4, {name: "John", city: "New York"}); + + string jsonStr5 = string `{ "name": "John", + "company1": [{ + "name": "wso2", + "year": 2024, + "addrees": { + "street": "123", + "city": "Berkeley" + } + }], + "city": "New York", + "company2": [{ + "name": "amzn", + "year": 2024, + "addrees": { + "street": "123", + "city": "Miami" + } + }] + }`; + record {| string name; string city; |} val5 = check fromJsonStringWithType(jsonStr5); + test:assertEquals(val5, {name: "John", city: "New York"}); +} + +@test:Config +isolated function testArrayOrTupleCaseForFromJsonStringWithType() returns error? { + string jsonStr1 = string `[["1"], 2.0]`; + [[int], float] val1 = check fromJsonStringWithType(jsonStr1); + test:assertEquals(val1, [[1], 2.0]); + + string jsonStr2 = string `[["1", 2], 2.0]`; + [[int, float], string] val2 = check fromJsonStringWithType(jsonStr2); + test:assertEquals(val2, [[1, 2.0], "2.0"]); + + string jsonStr3 = string `[["1", 2], [2, "3"]]`; + int[][] val3 = check fromJsonStringWithType(jsonStr3); + test:assertEquals(val3, [[1, 2], [2, 3]]); + + string jsonStr4 = string `{"val" : [[1, 2], "2.0", 3.0, [5, 6]]}`; + record {| + [[int, float], string, float, [string, int]] val; + |} val4 = check fromJsonStringWithType(jsonStr4); + test:assertEquals(val4, {val: [[1, 2.0], "2.0", 3.0, ["5", 6]]}); + + string jsonStr41 = string `{"val1" : [[1, 2], "2.0", 3.0, [5, 6]], "val2" : [[1, 2], "2.0", 3.0, [5, 6]]}`; + record {| + [[int, float], string, float, [string, int]] val1; + [[float, float], string, float, [string, float]] val2; + |} val41 = check fromJsonStringWithType(jsonStr41); + test:assertEquals(val41, {val1: [[1, 2.0], "2.0", 3.0, ["5", 6]], val2: [[1.0, 2.0], "2.0", 3.0, ["5", 6.0]]}); + + string jsonStr5 = string `{"val" : [["1", 2], [2, "3"]]}`; + record {| + int[][] val; + |} val5 = check fromJsonStringWithType(jsonStr5); + test:assertEquals(val5, {val: [[1, 2], [2, 3]]}); + + string jsonStr6 = string `[{"val" : [["1", 2], [2, "3"]]}]`; + [record {|int[][] val;|}] val6 = check fromJsonStringWithType(jsonStr6); + test:assertEquals(val6, [{val: [[1, 2], [2, 3]]}]); +} + +// Negative tests for fromJsonStringWithType() function. + +@test:Config +isolated function testFromJsonStringWithTypeNegative1() returns error? { + string str = string `{ + "id": 12, + "name": "Anne", + "address": { + "street": "Main", + "city": "94", + "id": true + } + }`; + + RN|Error x = fromJsonStringWithType(str); + test:assertTrue(x is error); + test:assertEquals((x).message(), "incompatible value 'true' for type 'int' in field 'address.id'"); +} + +@test:Config +isolated function testFromJsonStringWithTypeNegative2() returns error? { + string str = string `{ + "id": 12 + }`; + + RN2|Error x = fromJsonStringWithType(str); + test:assertTrue(x is error); + test:assertEquals((x).message(), "required field 'name' not present in JSON"); +} + +@test:Config +isolated function testFromJsonStringWithTypeNegative3() returns error? { + string str = string `{ + "id": 12, + "name": "Anne", + "address": { + "street": "Main", + "city": "94" + } + }`; + + RN|Error x = fromJsonStringWithType(str); + test:assertTrue(x is error); + test:assertEquals((x).message(), "required field 'id' not present in JSON"); +} + +@test:Config +isolated function testFromJsonStringWithTypeNegative4() returns error? { + string str = string `{ + name: "John" + }`; + + int|Error x = fromJsonStringWithType(str); + test:assertTrue(x is error); + test:assertEquals((x).message(), "invalid type 'int' expected 'map type'"); + + Union|Error y = fromJsonStringWithType(str); + test:assertTrue(y is error); + test:assertEquals((y).message(), "invalid type 'ballerina/data.jsondata:0:Union' expected 'map type'"); + + table|Error z = fromJsonStringWithType(str); + test:assertTrue(z is error); + test:assertEquals((z).message(), "unsupported type 'table'"); + + RN2|Error a = fromJsonStringWithType("1"); + test:assertTrue(a is error); + test:assertEquals((a).message(), "incompatible expected type 'data.jsondata:RN2' for value '1'"); +} + +@test:Config +isolated function testFromJsonStringWithTypeNegative5() returns error? { + string str = string `[1, 2]`; + + INTARR|Error x = fromJsonStringWithType(str); + test:assertTrue(x is error); + test:assertEquals((x).message(), "array size is not compatible with the expected size"); + + INTTUPLE|Error y = fromJsonStringWithType(str); + test:assertTrue(y is error); + test:assertEquals((y).message(), "array size is not compatible with the expected size"); +} diff --git a/ballerina/tests/from_json_test.bal b/ballerina/tests/from_json_test.bal new file mode 100644 index 0000000..a666073 --- /dev/null +++ b/ballerina/tests/from_json_test.bal @@ -0,0 +1,1016 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you 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. + +import ballerina/test; + +// Possitive tests for fromJsonWithType() function. + +@test:Config +isolated function testJsonToBasicTypes() returns error? { + int val1 = check fromJsonWithType(5); + test:assertEquals(val1, 5); + + float val2 = check fromJsonWithType(5.5); + test:assertEquals(val2, 5.5); + + decimal val3 = check fromJsonWithType(5.5); + test:assertEquals(val3, 5.5d); + + string val4 = check fromJsonWithType("hello"); + test:assertEquals(val4, "hello"); + + boolean val5 = check fromJsonWithType(true); + test:assertEquals(val5, true); + + () val6 = check fromJsonWithType(null); + test:assertEquals(val6, null); +} + +@test:Config +isolated function testSimpleJsonToRecord() returns error? { + json j = {"a": "hello", "b": 1}; + + record {|string a; int b;|} recA = check fromJsonWithType(j); + test:assertEquals(recA.a, "hello"); + test:assertEquals(recA.b, 1); +} + +@test:Config +isolated function testSimpleJsonToRecordWithProjection() returns error? { + json j = {"a": "hello", "b": 1}; + + record {|string a;|} recA = check fromJsonWithType(j); + test:assertEquals(recA.a, "hello"); + test:assertEquals(recA, {"a": "hello"}); +} + +@test:Config +isolated function testNestedJsonToRecord() returns error? { + json j = { + "a": "hello", + "b": 1, + "c": { + "d": "world", + "e": 2 + } + }; + + record {|string a; int b; record {|string d; int e;|} c;|} recA = check fromJsonWithType(j); + test:assertEquals(recA.a, "hello"); + test:assertEquals(recA.b, 1); + test:assertEquals(recA.c.d, "world"); + test:assertEquals(recA.c.e, 2); +} + +@test:Config +isolated function testNestedJsonToRecordWithProjection() returns error? { + json j = { + "a": "hello", + "b": 1, + "c": { + "d": "world", + "e": 2 + } + }; + + record {|string a; record {|string d;|} c;|} recA = check fromJsonWithType(j); + test:assertEquals(recA.a, "hello"); + test:assertEquals(recA.c.d, "world"); + test:assertEquals(recA, {"a": "hello", "c": {"d": "world"}}); +} + +@test:Config +isolated function testJsonToRecordWithOptionalFields() returns error? { + json j = {"a": "hello"}; + + record {|string a; int b?;|} recA = check fromJsonWithType(j); + test:assertEquals(recA.a, "hello"); + test:assertEquals(recA.b, null); +} + +@test:Config +isolated function testJsonToRecordWithOptionalFieldsWithProjection() returns error? { + json j = { + "a": "hello", + "b": 1, + "c": { + "d": "world", + "e": 2 + } + }; + + record {|string a; record {|string d; int f?;|} c;|} recA = check fromJsonWithType(j); + test:assertEquals(recA.a, "hello"); + test:assertEquals(recA.c.d, "world"); + test:assertEquals(recA, {"a": "hello", "c": {"d": "world"}}); +} + +@test:Config +isolated function testFromJsonWithType1() returns error? { + json jsonContent = { + "id": 2, + "name": "Anne", + "address": { + "street": "Main", + "city": "94" + } + }; + + R x = check fromJsonWithType(jsonContent); + test:assertEquals(x.id, 2); + test:assertEquals(x.name, "Anne"); + test:assertEquals(x.address.street, "Main"); + test:assertEquals(x.address.city, "94"); +} + +@test:Config +isolated function testMapTypeAsFieldTypeInRecord() returns error? { + json jsonContent = { + "employees": { + "John": "Manager", + "Anne": "Developer" + } + }; + + Company x = check fromJsonWithType(jsonContent); + test:assertEquals(x.employees["John"], "Manager"); + test:assertEquals(x.employees["Anne"], "Developer"); +} + +@test:Config +isolated function testFromJsonWithType2() returns error? { + json jsonContent = { + "name": "John", + "age": 30, + "address": { + "street": "123 Main St", + "zipcode": 10001, + "coordinates": { + "latitude": 40.7128, + "longitude": -74.0060 + } + } + }; + + Person x = check fromJsonWithType(jsonContent); + test:assertEquals(x.name, "John"); + test:assertEquals(x.age, 30); + test:assertEquals(x.address.street, "123 Main St"); + test:assertEquals(x.address.zipcode, 10001); + test:assertEquals(x.address.coordinates.latitude, 40.7128); + test:assertEquals(x.address.coordinates.longitude, -74.0060); +} + +@test:Config +isolated function testFromJsonWithType3() returns error? { + json jsonContent = { + "title": "To Kill a Mockingbird", + "author": { + "name": "Harper Lee", + "birthdate": "1926-04-28", + "hometown": "Monroeville, Alabama", + "local": false + }, + "price": 10.5, + "publisher": { + "name": "J. B. Lippincott & Co.", + "year": 1960, + "location": "Philadelphia", + "month": 4 + } + }; + + Book x = check fromJsonWithType(jsonContent); + test:assertEquals(x.title, "To Kill a Mockingbird"); + test:assertEquals(x.author.name, "Harper Lee"); + test:assertEquals(x.author.birthdate, "1926-04-28"); + test:assertEquals(x.author.hometown, "Monroeville, Alabama"); + test:assertEquals(x.publisher.name, "J. B. Lippincott & Co."); + test:assertEquals(x.publisher.year, 1960); + test:assertEquals(x.publisher["location"], "Philadelphia"); + test:assertEquals(x["price"], 10.5); + test:assertEquals(x.author["local"], false); +} + +@test:Config +isolated function testFromJsonWithType4() returns error? { + json jsonContent = { + "name": "School Twelve", + "city": 23, + "number": 12, + "section": 2, + "flag": true, + "tp": 12345 + }; + + School x = check fromJsonWithType(jsonContent); + test:assertEquals(x.name, "School Twelve"); + test:assertEquals(x.number, 12); + test:assertEquals(x.flag, true); + test:assertEquals(x["section"], 2); + test:assertEquals(x["tp"], 12345); +} + +@test:Config +isolated function testFromJsonWithType5() returns error? { + json jsonContent = { + "intValue": 10, + "floatValue": 10.5, + "stringValue": "test", + "decimalValue": 10.50, + "doNotParse": "abc" + }; + + TestRecord x = check fromJsonWithType(jsonContent); + test:assertEquals(x.intValue, 10); + test:assertEquals(x.floatValue, 10.5f); + test:assertEquals(x.stringValue, "test"); + test:assertEquals(x.decimalValue, 10.50d); + test:assertEquals(x["doNotParse"], "abc"); +} + +@test:Config +isolated function testFromJsonWithType6() returns error? { + json jsonContent = { + "id": 1, + "name": "Class A", + "student": { + "id": 2, + "name": "John Doe", + "school": { + "name": "ABC School", + "address": { + "street": "Main St", + "city": "New York" + } + } + }, + "teacher": { + "id": 3, + "name": "Jane Smith" + }, + "monitor": null + }; + + Class x = check fromJsonWithType(jsonContent); + test:assertEquals(x.id, 1); + test:assertEquals(x.name, "Class A"); + test:assertEquals(x.student.id, 2); + test:assertEquals(x.student.name, "John Doe"); + test:assertEquals(x.student.school.name, "ABC School"); + test:assertEquals(x.student.school.address.street, "Main St"); + test:assertEquals(x.student.school.address.city, "New York"); + test:assertEquals(x.teacher.id, 3); + test:assertEquals(x.teacher.name, "Jane Smith"); + test:assertEquals(x.monitor, null); +} + +@test:Config +isolated function testFromJsonWithType7() returns error? { + json nestedJson = { + "intValue": 5, + "floatValue": 2.5, + "stringValue": "nested", + "decimalValue": 5.00 + }; + + json jsonContent = { + "intValue": 10, + "nested1": nestedJson + }; + + TestRecord2 x = check fromJsonWithType(jsonContent); + test:assertEquals(x.intValue, 10); + test:assertEquals(x.nested1.intValue, 5); +} + +@test:Config +isolated function testFromJsonWithType8() returns error? { + json jsonContent = { + "street": "Main", + "city": "Mahar", + "house": 94 + }; + + TestR x = check fromJsonWithType(jsonContent); + test:assertEquals(x.street, "Main"); + test:assertEquals(x.city, "Mahar"); +} + +@test:Config +isolated function testFromJsonWithType9() returns error? { + json jsonContent = { + "street": "Main", + "city": "Mahar", + "houses": [94, 95, 96] + }; + + TestArr1 x = check fromJsonWithType(jsonContent); + test:assertEquals(x.street, "Main"); + test:assertEquals(x.city, "Mahar"); + test:assertEquals(x.houses, [94, 95, 96]); +} + +@test:Config +isolated function testFromJsonWithType10() returns error? { + json jsonContent = { + "street": "Main", + "city": 11, + "house": [94, "Gedara"] + }; + + TestArr2 x = check fromJsonWithType(jsonContent); + test:assertEquals(x.street, "Main"); + test:assertEquals(x.city, 11); + test:assertEquals(x.house, [94, "Gedara"]); +} + +@test:Config +isolated function testFromJsonWithType11() returns error? { + json jsonContent = { + "street": "Main", + "city": "Mahar", + "house": [94, [1, 2, 3]] + }; + + TestArr3 x = check fromJsonWithType(jsonContent); + test:assertEquals(x.street, "Main"); + test:assertEquals(x.city, "Mahar"); + test:assertEquals(x.house, [94, [1, 2, 3]]); +} + +@test:Config +isolated function testFromJsonWithType12() returns error? { + json jsonContent = { + "street": "Main", + "city": { + "name": "Mahar", + "code": 94 + }, + "flag": true + }; + + TestJson x = check fromJsonWithType(jsonContent); + test:assertEquals(x.street, "Main"); + test:assertEquals(x.city, {"name": "Mahar", "code": 94}); +} + +@test:Config +isolated function testFromJsonWithType14() returns error? { + json jsonContent = { + "id": 12, + "name": "Anne", + "address": { + "id": 34, + "city": "94" + } + }; + + RN|Error x = fromJsonWithType(jsonContent); + test:assertTrue(x is error); + test:assertEquals((x).message(), "required field 'street' not present in JSON"); +} + +@test:Config +isolated function testFromJsonWithType15() returns error? { + json jsonContent = [1, 2, 3]; + + IntArr x = check fromJsonWithType(jsonContent); + test:assertEquals(x, [1, 2, 3]); +} + +@test:Config +isolated function testFromJsonWithType16() returns error? { + json jsonContent = [1, "abc", [3, 4.0]]; + + TUPLE|Error x = check fromJsonWithType(jsonContent); + test:assertEquals(x, [1, "abc", [3, 4.0]]); +} + +@test:Config +isolated function testFromJsonWithType17() returns error? { + json jsonContent = { + "street": "Main", + "city": { + "name": "Mahar", + "code": 94, + "internal": { + "id": 12, + "agent": "Anne" + } + }, + "flag": true + }; + + TestJson x = check fromJsonWithType(jsonContent); + test:assertEquals(x.street, "Main"); + test:assertEquals(x.city, {"name": "Mahar", "code": 94, "internal": {"id": 12, "agent": "Anne"}}); +} + +@test:Config +isolated function testFromJsonWithType18() returns error? { + json jsonContent = { + "books": [ + { + "title": "The Great Gatsby", + "author": "F. Scott Fitzgerald" + }, + { + "title": "The Grapes of Wrath", + "author": "John Steinbeck" + }, + { + "title": "Binary Echoes: Unraveling the Digital Web", + "author": "Alexandra Quinn" + } + ] + }; + + Library x = check fromJsonWithType(jsonContent); + test:assertEquals(x.books.length(), 2); + test:assertEquals(x.books[0].title, "The Great Gatsby"); + test:assertEquals(x.books[0].author, "F. Scott Fitzgerald"); + test:assertEquals(x.books[1].title, "The Grapes of Wrath"); + test:assertEquals(x.books[1].author, "John Steinbeck"); +} + +@test:Config +isolated function testFromJsonWithType19() returns error? { + json jsonContent = { + "books": [ + { + "title": "The Great Gatsby", + "author": "F. Scott Fitzgerald" + }, + { + "title": "The Grapes of Wrath", + "author": "John Steinbeck" + }, + { + "title": "Binary Echoes: Unraveling the Digital Web", + "author": "Alexandra Quinn" + } + ] + }; + + LibraryB x = check fromJsonWithType(jsonContent); + test:assertEquals(x.books.length(), 2); + test:assertEquals(x.books[0].title, "The Great Gatsby"); + test:assertEquals(x.books[0].author, "F. Scott Fitzgerald"); + test:assertEquals(x.books[1].title, "The Grapes of Wrath"); + test:assertEquals(x.books[1].author, "John Steinbeck"); + + LibraryC y = check fromJsonWithType(jsonContent); + test:assertEquals(y.books.length(), 3); + test:assertEquals(y.books[0].title, "The Great Gatsby"); + test:assertEquals(y.books[0].author, "F. Scott Fitzgerald"); + test:assertEquals(y.books[1].title, "The Grapes of Wrath"); + test:assertEquals(y.books[1].author, "John Steinbeck"); + test:assertEquals(y.books[2].title, "Binary Echoes: Unraveling the Digital Web"); + test:assertEquals(y.books[2].author, "Alexandra Quinn"); +} + +@test:Config {enable: false} +isolated function testFromJsonWithType20() returns error? { + // TODO: Fix these bugs and enable the tests. + json jsonVal1 = { + "a": { + "c": "world", + "d": "2" + }, + "b": { + "c": "world", + "d": "2" + } + }; + + record {| + record {| + string c; + string d; + |}...; + |} val1 = check fromJsonWithType(jsonVal1); + test:assertEquals(val1.length(), 2); + test:assertEquals(val1["a"]["c"], "world"); + test:assertEquals(val1["a"]["d"], "2"); + test:assertEquals(val1["b"]["c"], "world"); + test:assertEquals(val1["b"]["d"], "2"); + + record {| + map...; + |} val2 = check fromJsonWithType(jsonVal1); + test:assertEquals(val2.length(), 2); + test:assertEquals(val2["a"]["c"], "world"); + test:assertEquals(val2["a"]["d"], "2"); + test:assertEquals(val2["b"]["c"], "world"); + test:assertEquals(val2["b"]["d"], "2"); + + json jsonVal3 = { + "a": [{ + "c": "world", + "d": "2" + }], + "b": [{ + "c": "world", + "d": "2" + }] + }; + + record {| + record {| + string c; + string d; + |}[]...; + |} val3 = check fromJsonWithType(jsonVal3); + test:assertEquals(val3.length(), 2); + test:assertEquals(val3["a"], [{ + "c": "world", + "d": "2" + }]); + test:assertEquals(val3["b"], [{ + "c": "world", + "d": "2" + }]); +} + +@test:Config +isolated function testUnionTypeAsExpTypeForFromJsonWithType() returns error? { + decimal|float val1 = check fromJsonWithType(1.0); + test:assertEquals(val1, 1.0); + + json jsonVal2 = { + "a": "hello", + "b": 1.0 + }; + + record {| + decimal|float b; + |} val2 = check fromJsonWithType(jsonVal2); + test:assertEquals(val2.length(), 1); + test:assertEquals(val2.b, 1.0); + + json jsonVal3 = { + "a": { + "b": 1, + "d": { + "e": false + } + }, + "c": 2.0 + }; + + record {| + record {| decimal|int b; record {| string|boolean e; |} d; |} a; + decimal|float c; + |} val3 = check fromJsonWithType(jsonVal3); + test:assertEquals(val3.length(), 2); + test:assertEquals(val3.a.length(), 2); + test:assertEquals(val3.a.b, 1); + test:assertEquals(val3.a.d.e, false); + test:assertEquals(val3.c, 2.0); +} + +@test:Config +isolated function testAnydataAsExpTypeForFromJsonWithType() returns error? { + anydata val1 = check fromJsonWithType(1); + test:assertEquals(val1, 1); + + json jsonVal2 = { + "a": "hello", + "b": 1 + }; + + anydata val2 = check fromJsonWithType(jsonVal2); + test:assertEquals(val2, {"a": "hello", "b": 1}); + + record {| + record {| + int b; + record {| + string e; + |} d; + |} a; + int c; + |} jsonVal3 = { + "a": { + "b": 1, + "d": { + "e": "hello" + } + }, + "c": 2 + }; + + anydata val3 = check fromJsonWithType(jsonVal3); + test:assertEquals(val3, {"a": {"b": 1, "d": {"e": "hello"}}, "c": 2}); + + record {| + record {| + int b; + record {| + string e; + |} d; + |}[] a; + int c; + |} jsonVal4 = { + "a": [{ + "b": 1, + "d": { + "e": "hello" + } + }], + "c": 2 + }; + + anydata val4 = check fromJsonWithType(jsonVal4); + test:assertEquals(val4, {"a": [{"b": 1, "d": {"e": "hello"}}], "c": 2}); + + [[int], int] str5 = [[1], 2]; + anydata val5 = check fromJsonWithType(str5); + test:assertEquals(val5, [[1], 2]); +} + +@test:Config +isolated function testJsonAsExpTypeForFromJsonWithType() returns error? { + json val1 = check fromJsonWithType(1); + test:assertEquals(val1, 1); + + record {| + string a; + int b; + |} jsonVal2 = { + "a": "hello", + "b": 1 + }; + + json val2 = check fromJsonWithType(jsonVal2); + test:assertEquals(val2, {"a": "hello", "b": 1}); + + record {| + record {| + int b; + record {| + string e; + |} d; + |} a; + int c; + |} jsonVal3 = { + "a": { + "b": 1, + "d": { + "e": "hello" + } + }, + "c": 2 + }; + + json val3 = check fromJsonWithType(jsonVal3); + test:assertEquals(val3, {"a": {"b": 1, "d": {"e": "hello"}}, "c": 2}); + + record {| + record {| + int b; + record {| + string e; + |} d; + |}[] a; + int c; + |} jsonVal4 = { + "a": [{ + "b": 1, + "d": { + "e": "hello" + } + }], + "c": 2 + }; + + json val4 = check fromJsonWithType(jsonVal4); + test:assertEquals(val4, {"a": [{"b": 1, "d": {"e": "hello"}}], "c": 2}); + + [[int], float] jsonVal5 = [[1], 2]; + json val5 = check fromJsonWithType(jsonVal5); + test:assertEquals(val5, [[1], 2.0]); +} + +@test:Config {enable: false} +isolated function testMapAsExpTypeForFromJsonWithType() returns error? { + record {| + string a; + string b; + |} jsonVal1 = { + "a": "hello", + "b": "1" + }; + + map val1 = check fromJsonWithType(jsonVal1); + test:assertEquals(val1, {"a": "hello", "b": "1"}); + + json jsonVal2 = { + "a": "hello", + "b": 1, + "c": { + "d": "world", + "e": "2" + } + }; + record {| + string a; + int b; + map c; + |} val2 = check fromJsonWithType(jsonVal2); + test:assertEquals(val2.a, "hello"); + test:assertEquals(val2.b, 1); + test:assertEquals(val2.c, {"d": "world", "e": "2"}); + + json jsonVal3 = { + "a": { + "c": "world", + "d": "2" + }, + "b": { + "c": "world", + "d": "2" + } + }; + + map> val3 = check fromJsonWithType(jsonVal3); + test:assertEquals(val3, {"a": {"c": "world", "d": "2"}, "b": {"c": "world", "d": "2"}}); + + record {| + map a; + |} val4 = check fromJsonWithType(jsonVal3); + test:assertEquals(val4.a, {"c": "world", "d": "2"}); + + map val5 = check fromJsonWithType(jsonVal3); + test:assertEquals(val5, {"a": {"c": "world", "d": 2}, "b": {"c": "world", "d": 2}}); +} + +@test:Config +isolated function testProjectionInTupleForFromJsonWithType() returns error? { + float[] jsonVal1 = [1, 2, 3, 4, 5, 8]; + [float, float] val1 = check fromJsonWithType(jsonVal1); + test:assertEquals(val1, [1.0, 2.0]); + + record {| + float[] a; + |} jsonVal2 = { + "a": [1, 2, 3, 4, 5, 8] + }; + record {| [float, float] a; |} val2 = check fromJsonWithType(jsonVal2); + test:assertEquals(val2.a, [1.0, 2.0]); + + [int, string] str3 = [1, "4"]; + [int] val3 = check fromJsonWithType(str3); + test:assertEquals(val3, [1]); + + [string, record {|json...;|}] jsonVal4 = ["1", {}]; + [string] val4 = check fromJsonWithType(jsonVal4); + test:assertEquals(val4, ["1"]); + + [string, int[], map] jsonVal5 = ["1", [], {"name": 1}]; + [string] val5 = check fromJsonWithType(jsonVal5); + test:assertEquals(val5, ["1"]); +} + +@test:Config +isolated function testProjectionInArrayForFromJsonWithType() returns error? { + int[2] val1 = check fromJsonWithType([1, 2, 3, 4, 5]); + test:assertEquals(val1, [1, 2]); + + record {| + int[] a; + |} jsonVal2 = { + "a": [1, 2, 3, 4, 5] + }; + record {| int[2] a; |} val2 = check fromJsonWithType(jsonVal2); + test:assertEquals(val2, {a: [1, 2]}); + + json jsonVal3 = { + "a": [1, 2, 3, 4, 5], + "b": [1, 2, 3, 4, 5] + }; + record {| int[2] a; int[3] b; |} val3 = check fromJsonWithType(jsonVal3); + test:assertEquals(val3, {a: [1, 2], b: [1, 2, 3]}); + + json jsonVal4 = { + "employees": [ + { "name": "Prakanth", + "age": 26 + }, + { "name": "Kevin", + "age": 25 + } + ] + }; + record {| record {| string name; int age; |}[1] employees; |} val4 = check fromJsonWithType(jsonVal4); + test:assertEquals(val4, {employees: [{name: "Prakanth", age: 26}]}); +} + +@test:Config +isolated function testProjectionInRecordForFromJsonWithType() returns error? { + json jsonVal1 = {"name": "John", "age": 30, "city": "New York"}; + record {| string name; string city; |} val1 = check fromJsonWithType(jsonVal1); + test:assertEquals(val1, {name: "John", city: "New York"}); + + json jsonVal2 = {"name": "John", "age": "30", "city": "New York"}; + record {| string name; string city; |} val2 = check fromJsonWithType(jsonVal2); + test:assertEquals(val2, {name: "John", city: "New York"}); + + json jsonVal3 = { "name": "John", + "company": { + "name": "wso2", + "year": 2024, + "addrees": { + "street": "123", + "city": "Berkeley" + } + }, + "city": "New York" }; + record {| string name; string city; |} val3 = check fromJsonWithType(jsonVal3); + test:assertEquals(val3, {name: "John", city: "New York"}); + + json jsonVal4 = { "name": "John", + "company": [{ + "name": "wso2", + "year": 2024, + "addrees": { + "street": "123", + "city": "Berkeley" + } + }], + "city": "New York" }; + record {| string name; string city; |} val4 = check fromJsonWithType(jsonVal4); + test:assertEquals(val4, {name: "John", city: "New York"}); + + json jsonVal5 = { "name": "John", + "company1": [{ + "name": "wso2", + "year": 2024, + "addrees": { + "street": "123", + "city": "Berkeley" + } + }], + "city": "New York", + "company2": [{ + "name": "amzn", + "year": 2024, + "addrees": { + "street": "123", + "city": "Miami" + } + }] + }; + record {| string name; string city; |} val5 = check fromJsonWithType(jsonVal5); + test:assertEquals(val5, {name: "John", city: "New York"}); +} + +@test:Config +isolated function testArrayOrTupleCaseForFromJsonWithType() returns error? { + json jsonVal1 = [[1], 2.0]; + [[int], float] val1 = check fromJsonWithType(jsonVal1); + test:assertEquals(val1, [[1], 2.0]); + + json jsonVal2 = [[1, 2], 2.0]; + [[int, int], float] val2 = check fromJsonWithType(jsonVal2); + test:assertEquals(val2, [[1, 2], 2.0]); + + json jsonStr3 = [[1, 2], [2, 3]]; + int[][] val3 = check fromJsonWithType(jsonStr3); + test:assertEquals(val3, [[1, 2], [2, 3]]); + + json jsonVal4 = {"val" : [[1, 2], "2.0", 3.0, [5, 6]]}; + record {| + [[int, int], string, float, [int, int]] val; + |} val4 = check fromJsonWithType(jsonVal4); + test:assertEquals(val4, {val: [[1, 2], "2.0", 3.0, [5, 6]]}); + + json jsonVal41 = {"val1" : [[1, 2], "2.0", 3.0, [5, 6]], "val2" : [[1, 2], "2.0", 3.0, [5, 6]]}; + record {| + [[int, int], string, float, [int, int]] val1; + [[int, int], string, float, [int, int]] val2; + |} val41 = check fromJsonWithType(jsonVal41); + test:assertEquals(val41, {val1: [[1, 2], "2.0", 3.0, [5, 6]], val2: [[1, 2], "2.0", 3.0, [5, 6]]}); + + json jsonVal5 = {"val" : [[1, 2], [2, 3]]}; + record {| + int[][] val; + |} val5 = check fromJsonWithType(jsonVal5); + test:assertEquals(val5, {val: [[1, 2], [2, 3]]}); + + json jsonVal6 = [{"val" : [[1, 2], [2, 3]]}]; + [record {|int[][] val;|}] val6 = check fromJsonWithType(jsonVal6); + test:assertEquals(val6, [{val: [[1, 2], [2, 3]]}]); +} + +// Negative tests for fromJsonWithType() function. + +@test:Config +isolated function testFromJsonWithTypeNegative1() returns error? { + json jsonContent = { + "id": 12, + "name": "Anne", + "address": { + "street": "Main", + "city": "94", + "id": true + } + }; + + RN|Error x = fromJsonWithType(jsonContent); + test:assertTrue(x is error); + test:assertEquals((x).message(), "incompatible value 'true' for type 'int' in field 'address.id'"); +} + +@test:Config +isolated function testFromJsonWithTypeNegative2() returns error? { + json jsonContent = { + "id": 12 + }; + + RN2|Error x = fromJsonWithType(jsonContent); + test:assertTrue(x is error); + test:assertEquals((x).message(), "required field 'name' not present in JSON"); +} + +@test:Config +isolated function testFromJsonWithTypeNegative3() returns error? { + json jsonContent = { + "id": 12, + "name": "Anne", + "address": { + "street": "Main", + "city": "94" + } + }; + + RN|Error x = fromJsonWithType(jsonContent); + test:assertTrue(x is Error); + test:assertEquals((x).message(), "required field 'id' not present in JSON"); +} + +@test:Config +isolated function testFromJsonWithTypeNegative4() returns error? { + json jsonContent = { + name: "John" + }; + + int|Error x = fromJsonWithType(jsonContent); + test:assertTrue(x is error); + test:assertEquals((x).message(), "incompatible expected type 'int' for value '{\"name\":\"John\"}'"); + + Union|Error y = fromJsonWithType(jsonContent); + test:assertTrue(y is error); + test:assertEquals((y).message(), "invalid type 'data.jsondata:Union' expected 'anydata'"); + + table|Error z = fromJsonWithType(jsonContent); + test:assertTrue(z is error); + test:assertEquals((z).message(), "invalid type 'table' expected 'anydata'"); + + RN2|Error a = fromJsonWithType("1"); + test:assertTrue(a is error); + test:assertEquals((a).message(), "incompatible expected type 'data.jsondata:RN2' for value '1'"); +} + +@test:Config +isolated function testFromJsonWithTypeNegative5() returns error? { + json jsonContent = [1, 2]; + + INTARR|Error x = fromJsonWithType(jsonContent); + test:assertTrue(x is error); + test:assertEquals((x).message(), "array size is not compatible with the expected size"); + + INTTUPLE|Error y = fromJsonWithType(jsonContent); + test:assertTrue(y is error); + test:assertEquals((y).message(), "array size is not compatible with the expected size"); +} + +@test:Config +isolated function testFromJsonWithTypeNegative6() { + json jsonContent = { + "street": "Main", + "city": "Mahar", + "house": [94, [1, 3, "4"]] + }; + + TestArr3|Error x = fromJsonWithType(jsonContent); + test:assertTrue(x is Error); + test:assertEquals((x).message(), "incompatible value '4' for type 'int' in field 'house'"); +} diff --git a/ballerina/tests/types.bal b/ballerina/tests/types.bal new file mode 100644 index 0000000..9425404 --- /dev/null +++ b/ballerina/tests/types.bal @@ -0,0 +1,181 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you 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. + +type Address record { + string street; + string city; +}; + +type R record {| + int id; + string name; + Address address; +|}; + +type Company record { + map employees; +}; + +type Coordinates record { + float latitude; + float longitude; +}; + +type AddressWithCord record { + string street; + int zipcode; + Coordinates coordinates; +}; + +type Person record { + string name; + int age; + AddressWithCord address; +}; + +type Author record {| + string name; + string birthdate; + string hometown; + boolean...; +|}; + +type Publisher record {| + string name; + int year; + string...; +|}; + +type Book record {| + string title; + Author author; + Publisher publisher; + float...; +|}; + +type School record {| + string name; + int number; + boolean flag; + int...; +|}; + +type TestRecord record { + int intValue; + float floatValue; + string stringValue; + decimal decimalValue; +}; + +type SchoolAddress record { + string street; + string city; +}; + +type School1 record { + string name; + SchoolAddress address; +}; + +type Student1 record { + int id; + string name; + School1 school; +}; + +type Teacher record { + int id; + string name; +}; + +type Class record { + int id; + string name; + Student1 student; + Teacher teacher; + Student1? monitor; +}; + +type TestRecord2 record { + int intValue; + TestRecord nested1; +}; + +type TestR record {| + string street; + string city; +|}; + +type TestArr1 record { + string street; + string city; + int[] houses; +}; + +type TestArr2 record { + string street; + int city; + [int, string] house; +}; + +type TestArr3 record { + string street; + string city; + [int, int[3]] house; +}; + +type TestJson record { + string street; + json city; + boolean flag; +}; + +type IntArr int[]; + +type TUPLE [int, string, [int, float]]; + +type BookA record {| + string title; + string author; +|}; + +type Library record { + BookA[2] books; +}; + +//////// Types used for Negative cases ///////// + +type AddressN record { + string street; + string city; + int id; +}; + +type RN record {| + int id; + string name; + AddressN address; +|}; + +type RN2 record {| + int id; + string name; +|}; + +type Union int|float; + +type INTARR int[3]; +type INTTUPLE [int, int, int, int...]; diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java index 1398d34..0153573 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java @@ -157,8 +157,11 @@ static Object convertAndUpdateCurrentJsonNode(JsonParser.StateMachine sm, BStrin Object currentJson = sm.currentJsonNode; Object convertedValue = convertToExpectedType(value, type); if (convertedValue instanceof BError) { - throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_VALUE_FOR_FIELD, value, type, - getCurrentFieldPath(sm)); + if (sm.currentField != null) { + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_VALUE_FOR_FIELD, value, type, + getCurrentFieldPath(sm)); + } + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_TYPE, type, value); } Type currentJsonNodeType = TypeUtils.getType(currentJson); diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java index cfcffa6..eb7da34 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java @@ -146,6 +146,10 @@ private void traverseMapJsonOrArrayJson(Object json, Type type) { this.fieldHierarchy.pop(); this.restType.pop(); } + + if (fieldNames.isEmpty()) { + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_TYPE, type, json); + } throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE_FOR_FIELD, getCurrentFieldPath()); } nodesStack.pop(); @@ -201,19 +205,23 @@ private void traverseArrayValue(Object json, Object parentJsonNode) { BArray array = (BArray) json; switch (rootArray.getTag()) { case TypeTags.ARRAY_TAG: - int expectedArraySize = ((ArrayType) rootArray).getSize(); + ArrayType arrayType = (ArrayType) rootArray; + int expectedArraySize = arrayType.getSize(); if (expectedArraySize > array.getLength()) { throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); } + + Type elementType = arrayType.getElementType(); if (expectedArraySize == -1) { - traverseArrayMembers(array.getLength(), array, parentJsonNode); + traverseArrayMembers(array.getLength(), array, elementType, parentJsonNode); } else { - traverseArrayMembers(expectedArraySize, array, parentJsonNode); + traverseArrayMembers(expectedArraySize, array, elementType, parentJsonNode); } break; case TypeTags.TUPLE_TAG: - Type restType = ((TupleType) rootArray).getRestType(); - int expectedTupleTypeCount = ((TupleType) rootArray).getTupleTypes().size(); + TupleType tupleType = (TupleType) rootArray; + Type restType = tupleType.getRestType(); + int expectedTupleTypeCount = tupleType.getTupleTypes().size(); if (expectedTupleTypeCount > array.getLength()) { throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); } @@ -221,7 +229,7 @@ private void traverseArrayValue(Object json, Object parentJsonNode) { for (int i = 0; i < array.getLength(); i++) { Object jsonMember = array.get(i); if (i < expectedTupleTypeCount) { - currentJsonNode = traverseJson(jsonMember, ((TupleType) rootArray).getTupleTypes().get(i)); + currentJsonNode = traverseJson(jsonMember, tupleType.getTupleTypes().get(i)); } else if (restType != null) { currentJsonNode = traverseJson(jsonMember, restType); } else { @@ -234,10 +242,10 @@ private void traverseArrayValue(Object json, Object parentJsonNode) { currentJsonNode = parentJsonNode; } - private void traverseArrayMembers(long length, BArray array, Object parentJsonNode) { + private void traverseArrayMembers(long length, BArray array, Type elementType, Object parentJsonNode) { for (int i = 0; i < length; i++) { Object jsonMember = array.get(i); - currentJsonNode = traverseJson(jsonMember, ((ArrayType) rootArray).getElementType()); + currentJsonNode = traverseJson(jsonMember, elementType); ((BArray) parentJsonNode).add(i, currentJsonNode); } } @@ -300,9 +308,10 @@ private Object convertToBasicType(Object json, Type targetType) { return JsonUtils.convertJSON(json, targetType); } catch (Exception e) { if (fieldNames.isEmpty()) { - throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_TYPE, targetType, targetType); + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_TYPE, targetType, json); } - throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_TYPE, targetType, targetType); + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_VALUE_FOR_FIELD, json, targetType, + getCurrentFieldPath()); } } diff --git a/native/src/main/resources/error.properties b/native/src/main/resources/error.properties index 0278b78..0c66bff 100644 --- a/native/src/main/resources/error.properties +++ b/native/src/main/resources/error.properties @@ -30,7 +30,7 @@ error.json.parser.exception=\ ''{0}'' at line: ''{1}'' column: ''{2}'' error.incompatible.type=\ - incompatible type expected ''{0}'' found ''{1}'' + incompatible expected type ''{0}'' for value ''{1}'' error.array.size.mismatch=\ array size is not compatible with the expected size From 03f90e457ee79fd1ed8f738c90405b0886ef3b36 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Tue, 6 Feb 2024 22:01:05 +0530 Subject: [PATCH 10/27] Refactor code and fix issue with map and rest type --- ballerina/tests/from_json_test.bal | 26 ++-- .../data/jsondata/json/JsonTraverse.java | 126 +++++++----------- 2 files changed, 66 insertions(+), 86 deletions(-) diff --git a/ballerina/tests/from_json_test.bal b/ballerina/tests/from_json_test.bal index a666073..41b5b88 100644 --- a/ballerina/tests/from_json_test.bal +++ b/ballerina/tests/from_json_test.bal @@ -483,7 +483,7 @@ isolated function testFromJsonWithType19() returns error? { test:assertEquals(y.books[2].author, "Alexandra Quinn"); } -@test:Config {enable: false} +@test:Config isolated function testFromJsonWithType20() returns error? { // TODO: Fix these bugs and enable the tests. json jsonVal1 = { @@ -524,8 +524,8 @@ isolated function testFromJsonWithType20() returns error? { "d": "2" }], "b": [{ - "c": "world", - "d": "2" + "c": "war", + "d": "3" }] }; @@ -541,8 +541,8 @@ isolated function testFromJsonWithType20() returns error? { "d": "2" }]); test:assertEquals(val3["b"], [{ - "c": "world", - "d": "2" + "c": "war", + "d": "3" }]); } @@ -706,7 +706,7 @@ isolated function testJsonAsExpTypeForFromJsonWithType() returns error? { test:assertEquals(val5, [[1], 2.0]); } -@test:Config {enable: false} +@test:Config isolated function testMapAsExpTypeForFromJsonWithType() returns error? { record {| string a; @@ -742,13 +742,13 @@ isolated function testMapAsExpTypeForFromJsonWithType() returns error? { "d": "2" }, "b": { - "c": "world", - "d": "2" + "c": "war", + "d": "3" } }; map> val3 = check fromJsonWithType(jsonVal3); - test:assertEquals(val3, {"a": {"c": "world", "d": "2"}, "b": {"c": "world", "d": "2"}}); + test:assertEquals(val3, {"a": {"c": "world", "d": "2"}, "b": {"c": "war", "d": "3"}}); record {| map a; @@ -757,9 +757,9 @@ isolated function testMapAsExpTypeForFromJsonWithType() returns error? { map val5 = check fromJsonWithType(jsonVal3); - test:assertEquals(val5, {"a": {"c": "world", "d": 2}, "b": {"c": "world", "d": 2}}); + test:assertEquals(val5, {"a": {"c": "world", "d": "2"}, "b": {"c": "war", "d": "3"}}); } @test:Config @@ -987,6 +987,10 @@ isolated function testFromJsonWithTypeNegative4() returns error? { RN2|Error a = fromJsonWithType("1"); test:assertTrue(a is error); test:assertEquals((a).message(), "incompatible expected type 'data.jsondata:RN2' for value '1'"); + + string|Error b = fromJsonWithType(1); + test:assertTrue(b is error); + test:assertEquals((b).message(), "incompatible expected type 'string' for value '1'"); } @test:Config diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java index eb7da34..ec587db 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java @@ -29,7 +29,6 @@ import io.ballerina.runtime.api.types.TupleType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.UnionType; -import io.ballerina.runtime.api.utils.JsonUtils; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BArray; @@ -67,21 +66,16 @@ public static Object traverse(Object json, Type type) { } static class JsonTree { - - Object currentJsonNode; Field currentField; Stack> fieldHierarchy = new Stack<>(); Stack restType = new Stack<>(); - Deque nodesStack = new ArrayDeque<>(); Deque fieldNames = new ArrayDeque<>(); Type rootArray; void reset() { - currentJsonNode = null; currentField = null; fieldHierarchy.clear(); restType.clear(); - nodesStack.clear(); fieldNames.clear(); rootArray = null; } @@ -91,24 +85,17 @@ public Object traverseJson(Object json, Type type) { switch (referredType.getTag()) { case TypeTags.RECORD_TYPE_TAG: RecordType recordType = (RecordType) referredType; - this.fieldHierarchy.push(new HashMap<>(recordType.getFields())); - this.restType.push(recordType.getRestFieldType()); - currentJsonNode = ValueCreator.createRecordValue(recordType); - nodesStack.push(currentJsonNode); - traverseMapJsonOrArrayJson(json, referredType); - break; + fieldHierarchy.push(new HashMap<>(recordType.getFields())); + restType.push(recordType.getRestFieldType()); + return traverseMapJsonOrArrayJson(json, ValueCreator.createRecordValue(recordType), referredType); case TypeTags.ARRAY_TAG: rootArray = referredType; - currentJsonNode = ValueCreator.createArrayValue((ArrayType) referredType); - nodesStack.push(currentJsonNode); - traverseMapJsonOrArrayJson(json, referredType); - break; + return traverseMapJsonOrArrayJson(json, ValueCreator.createArrayValue((ArrayType) referredType), + referredType); case TypeTags.TUPLE_TAG: rootArray = referredType; - currentJsonNode = ValueCreator.createTupleValue((TupleType) referredType); - nodesStack.push(currentJsonNode); - traverseMapJsonOrArrayJson(json, referredType); - break; + return traverseMapJsonOrArrayJson(json, ValueCreator.createTupleValue((TupleType) referredType), + referredType); case TypeTags.NULL_TAG: case TypeTags.BOOLEAN_TAG: case TypeTags.INT_TAG: @@ -128,18 +115,21 @@ public Object traverseJson(Object json, Type type) { case TypeTags.JSON_TAG: case TypeTags.ANYDATA_TAG: return json; + case TypeTags.MAP_TAG: + MapType mapType = (MapType) referredType; + fieldHierarchy.push(new HashMap<>()); + restType.push(mapType.getConstrainedType()); + return traverseMapJsonOrArrayJson(json, ValueCreator.createMapValue(mapType), referredType); default: return DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, type, PredefinedTypes.TYPE_ANYDATA); } - return currentJsonNode; } - private void traverseMapJsonOrArrayJson(Object json, Type type) { - Object parentJsonNode = nodesStack.peek(); + private Object traverseMapJsonOrArrayJson(Object json, Object currentJsonNode, Type type) { if (json instanceof BMap map) { - traverseMapValue(map, parentJsonNode); + return traverseMapValue(map, currentJsonNode); } else if (json instanceof BArray) { - traverseArrayValue(json, parentJsonNode); + return traverseArrayValue(json, currentJsonNode); } else { // JSON value not compatible with map or array. if (type.getTag() == TypeTags.RECORD_TYPE_TAG) { @@ -152,17 +142,16 @@ private void traverseMapJsonOrArrayJson(Object json, Type type) { } throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE_FOR_FIELD, getCurrentFieldPath()); } - nodesStack.pop(); } - private void traverseMapValue(BMap map, Object parentJsonNode) { + private Object traverseMapValue(BMap map, Object currentJsonNode) { for (BString key : map.getKeys()) { currentField = fieldHierarchy.peek().remove(key.toString()); if (currentField == null) { // Add to the rest field if (restType.peek() != null) { Type restFieldType = TypeUtils.getReferredType(restType.peek()); - addRestField(restFieldType, key, map.get(key)); + addRestField(restFieldType, key, map.get(key), currentJsonNode); } continue; } @@ -173,14 +162,6 @@ private void traverseMapValue(BMap map, Object parentJsonNode) Object mapValue = map.get(key); switch (currentFieldTypeTag) { - case TypeTags.MAP_TAG: - if (!checkTypeCompatibility(((MapType) currentFieldType).getConstrainedType(), mapValue)) { - throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_VALUE_FOR_FIELD, mapValue, - currentFieldType, getCurrentFieldPath()); - } - ((BMap) currentJsonNode).put(StringUtils.fromString(fieldNames.pop()), - mapValue); - break; case TypeTags.NULL_TAG: case TypeTags.BOOLEAN_TAG: case TypeTags.INT_TAG: @@ -191,17 +172,17 @@ private void traverseMapValue(BMap map, Object parentJsonNode) ((BMap) currentJsonNode).put(StringUtils.fromString(fieldNames.pop()), value); break; default: - currentJsonNode = traverseJson(mapValue, currentFieldType); - ((BMap) parentJsonNode).put(key, currentJsonNode); - currentJsonNode = parentJsonNode; + Object nextJsonNode = traverseJson(mapValue, currentFieldType); + ((BMap) currentJsonNode).put(key, nextJsonNode); } } Map currentField = fieldHierarchy.pop(); checkOptionalFieldsAndLogError(currentField); restType.pop(); + return currentJsonNode; } - private void traverseArrayValue(Object json, Object parentJsonNode) { + private Object traverseArrayValue(Object json, Object currentJsonNode) { BArray array = (BArray) json; switch (rootArray.getTag()) { case TypeTags.ARRAY_TAG: @@ -213,9 +194,9 @@ private void traverseArrayValue(Object json, Object parentJsonNode) { Type elementType = arrayType.getElementType(); if (expectedArraySize == -1) { - traverseArrayMembers(array.getLength(), array, elementType, parentJsonNode); + traverseArrayMembers(array.getLength(), array, elementType, currentJsonNode); } else { - traverseArrayMembers(expectedArraySize, array, elementType, parentJsonNode); + traverseArrayMembers(expectedArraySize, array, elementType, currentJsonNode); } break; case TypeTags.TUPLE_TAG: @@ -228,29 +209,30 @@ private void traverseArrayValue(Object json, Object parentJsonNode) { for (int i = 0; i < array.getLength(); i++) { Object jsonMember = array.get(i); + Object nextJsonNode; if (i < expectedTupleTypeCount) { - currentJsonNode = traverseJson(jsonMember, tupleType.getTupleTypes().get(i)); + nextJsonNode = traverseJson(jsonMember, tupleType.getTupleTypes().get(i)); } else if (restType != null) { - currentJsonNode = traverseJson(jsonMember, restType); + nextJsonNode = traverseJson(jsonMember, restType); } else { continue; } - ((BArray) parentJsonNode).add(i, currentJsonNode); + ((BArray) currentJsonNode).add(i, nextJsonNode); } break; } - currentJsonNode = parentJsonNode; + return currentJsonNode; } - private void traverseArrayMembers(long length, BArray array, Type elementType, Object parentJsonNode) { + private void traverseArrayMembers(long length, BArray array, Type elementType, Object currentJsonNode) { for (int i = 0; i < length; i++) { Object jsonMember = array.get(i); - currentJsonNode = traverseJson(jsonMember, elementType); - ((BArray) parentJsonNode).add(i, currentJsonNode); + ((BArray) currentJsonNode).add(i, traverseJson(jsonMember, elementType)); } } - private void addRestField(Type restFieldType, BString key, Object jsonMember) { + private void addRestField(Type restFieldType, BString key, Object jsonMember, Object currentJsonNode) { + Object nextJsonValue; switch (restFieldType.getTag()) { case TypeTags.ANYDATA_TAG: case TypeTags.JSON_TAG: @@ -266,25 +248,19 @@ private void addRestField(Type restFieldType, BString key, Object jsonMember) { } break; default: - return; + nextJsonValue = traverseJson(jsonMember, restFieldType); + ((BMap) currentJsonNode).put(key, nextJsonValue); + break; } } - private boolean checkTypeCompatibility(Type constraintType, Object json) { - if (json instanceof BMap) { - BMap map = (BMap) json; - for (BString key : map.getKeys()) { - if (!checkTypeCompatibility(constraintType, map.get(key))) { - return false; - } - } - return true; - } else if ((json instanceof BString && constraintType.getTag() == TypeTags.STRING_TAG) - || (json instanceof Long && constraintType.getTag() == TypeTags.INT_TAG) - || (json instanceof Double && (constraintType.getTag() == TypeTags.FLOAT_TAG - || constraintType.getTag() == TypeTags.DECIMAL_TAG)) - || (Boolean.class.isInstance(json) && constraintType.getTag() == TypeTags.BOOLEAN_TAG) - || (json == null && constraintType.getTag() == TypeTags.NULL_TAG)) { + private boolean checkTypeCompatibility(Type type, Object json) { + if ((json instanceof BString && type.getTag() == TypeTags.STRING_TAG) + || (json instanceof Long && type.getTag() == TypeTags.INT_TAG) + || (json instanceof Double && (type.getTag() == TypeTags.FLOAT_TAG + || type.getTag() == TypeTags.DECIMAL_TAG)) + || (Boolean.class.isInstance(json) && type.getTag() == TypeTags.BOOLEAN_TAG) + || (json == null && type.getTag() == TypeTags.NULL_TAG)) { return true; } else { return false; @@ -303,16 +279,16 @@ private Object convertToBasicType(Object json, Type targetType) { if (targetType.getTag() == TypeTags.READONLY_TAG) { return json; } - try { - // TODO: string x = check jsondata:fromJsonWithType(5); should it error? - return JsonUtils.convertJSON(json, targetType); - } catch (Exception e) { - if (fieldNames.isEmpty()) { - throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_TYPE, targetType, json); - } - throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_VALUE_FOR_FIELD, json, targetType, - getCurrentFieldPath()); + + if (checkTypeCompatibility(targetType, json)) { + return json; + } + + if (fieldNames.isEmpty()) { + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_TYPE, targetType, json); } + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_VALUE_FOR_FIELD, json, targetType, + getCurrentFieldPath()); } private String getCurrentFieldPath() { From 14b444827a38115b2fbb383bbb671b70506f86a8 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Wed, 7 Feb 2024 14:12:40 +0530 Subject: [PATCH 11/27] Support byte block stream --- ballerina/Dependencies.toml | 23 +++ ballerina/tests/stream_large_file_test.bal | 156 ++++++++++++++++ .../io/BallerinaByteBlockInputStream.java | 167 ++++++++++++++++++ .../data/jsondata/io/DataReaderTask.java | 105 +++++++++++ .../jsondata/io/DataReaderThreadPool.java | 50 ++++++ .../stdlib/data/jsondata/json/Native.java | 25 ++- 6 files changed, 525 insertions(+), 1 deletion(-) create mode 100644 ballerina/tests/stream_large_file_test.bal create mode 100644 native/src/main/java/io/ballerina/stdlib/data/jsondata/io/BallerinaByteBlockInputStream.java create mode 100644 native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderTask.java create mode 100644 native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderThreadPool.java diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 1d55757..8f644bd 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -12,6 +12,7 @@ org = "ballerina" name = "data.jsondata" version = "0.1.0" dependencies = [ + {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "test"} ] @@ -19,6 +20,19 @@ modules = [ {org = "ballerina", packageName = "data.jsondata", moduleName = "data.jsondata"} ] +[[package]] +org = "ballerina" +name = "io" +version = "1.6.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + [[package]] org = "ballerina" name = "jballerina.java" @@ -36,6 +50,15 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + [[package]] org = "ballerina" name = "test" diff --git a/ballerina/tests/stream_large_file_test.bal b/ballerina/tests/stream_large_file_test.bal new file mode 100644 index 0000000..c05dd66 --- /dev/null +++ b/ballerina/tests/stream_large_file_test.bal @@ -0,0 +1,156 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you 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. + +import ballerina/io; +import ballerina/test; + +const LARGE_JSON_FILE = "build//resources//large_data.json"; +const POSTIONS = { + "Associate Tech Lead": 2000, + "Software Engineer": 1500, + "Intern": 200, + "Senior Software Engineer": 1800, + "Tech Lead": 3000, + "Architect": 5000 +}; +const PRODUCTS = ["IAM", "Ballerina", "MI", "APIM", "CHOREO", "ASGADIO"]; + +type CompanyR1 record {| + EmployeeR1[] employees; + CustomerR1[] customers; +|}; + +type EmployeeR1 record {| + int id; + string product; + string position; + int salary; +|}; + +type CustomerR1 record {| + int id; + string name; + string product; +|}; + +@test:Config +function testLargeFileStream() returns error? { + stream dataStream = check io:fileReadBlocksAsStream(LARGE_JSON_FILE); + CompanyR1 company = check fromJsonStringWithType(dataStream); + test:assertEquals(company.employees.length(), 1001); + test:assertEquals(company.customers.length(), 1001); + + test:assertEquals(company.employees[0].id, 0); + test:assertEquals(company.employees[0].product, "IAM"); + test:assertEquals(company.employees[0].position, "Associate Tech Lead"); + test:assertEquals(company.employees[0].salary, 2000); + test:assertEquals(company.customers[0].id, 0); + test:assertEquals(company.customers[0].name, "Customer0"); + test:assertEquals(company.customers[0].product, "IAM"); + + test:assertEquals(company.employees[1000].id, 1000); + test:assertEquals(company.employees[1000].product, "CHOREO"); + test:assertEquals(company.employees[1000].position, "Tech Lead"); + test:assertEquals(company.employees[1000].salary, 3000); + test:assertEquals(company.customers[1000].id, 1000); + test:assertEquals(company.customers[1000].name, "Customer1000"); + test:assertEquals(company.customers[1000].product, "CHOREO"); +} + +type EmployeeR2 record {| + int id; + string position; +|}; + +type CustomerR2 record {| + int id; + string product; +|}; + +@test:Config +function testLargeFileStreamWithProjection() returns error? { + stream dataStream = check io:fileReadBlocksAsStream(LARGE_JSON_FILE); + record {| + EmployeeR2[5] employees; + CustomerR2[9] customers; + |} company = check fromJsonStringWithType(dataStream); + test:assertEquals(company.employees.length(), 5); + test:assertEquals(company.customers.length(), 9); + + test:assertEquals(company.employees[0].length(), 2); + test:assertEquals(company.employees[0].id, 0); + test:assertEquals(company.employees[0].position, "Associate Tech Lead"); + test:assertEquals(company.customers[0].length(), 2); + test:assertEquals(company.customers[0].id, 0); + test:assertEquals(company.customers[0].product, "IAM"); + + test:assertEquals(company.employees[4].length(), 2); + test:assertEquals(company.employees[4].id, 4); + test:assertEquals(company.employees[4].position, "Tech Lead"); + test:assertEquals(company.customers[4].length(), 2); + test:assertEquals(company.customers[4].id, 4); + test:assertEquals(company.customers[4].product, "CHOREO"); +} + +@test:BeforeSuite +function createLargeFile() returns error? { + io:WritableByteChannel wbc = check io:openWritableFile(LARGE_JSON_FILE); + string begin = string `{`; + string end = "}\n"; + _ = check wbc.write(begin.toBytes(), 0); + + _ = check wbc.write(string `"employees": + [ + `.toBytes(), 0); + _ = check wbc.write(createEmployee(0).toString().toBytes(), 0); + foreach int i in 1 ... 1000 { + _ = check wbc.write(",\n ".toBytes(), 0); + _ = check wbc.write(createEmployee(i).toString().toBytes(), 0); + } + _ = check wbc.write("\n ],\n".toBytes(), 0); + + _ = check wbc.write(string `"customers": + [ + `.toBytes(), 0); + _ = check wbc.write(createCustomer(0).toString().toBytes(), 0); + foreach int i in 1...1000 { + _ = check wbc.write(",\n ".toBytes(), 0); + _ = check wbc.write(createCustomer(i).toString().toBytes(), 0); + } + _ = check wbc.write("\n ]\n".toBytes(), 0); + + + _ = check wbc.write(end.toBytes(), 0); + _ = check wbc.close(); +} + +function createEmployee(int id) returns EmployeeR1 { + string position = POSTIONS.keys()[id % POSTIONS.keys().length()]; + return { + "id": id, + "product": PRODUCTS[id % PRODUCTS.length()], + "position": position, + "salary": POSTIONS[position] ?: 0 + }; +} + +function createCustomer(int id) returns CustomerR1 { + return { + "id": id, + "name": "Customer" + id.toString(), + "product": PRODUCTS[id % PRODUCTS.length()] + }; +} diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/BallerinaByteBlockInputStream.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/BallerinaByteBlockInputStream.java new file mode 100644 index 0000000..1e3588b --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/BallerinaByteBlockInputStream.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.data.jsondata.io; + +import io.ballerina.runtime.api.Environment; +import io.ballerina.runtime.api.async.Callback; +import io.ballerina.runtime.api.async.StrandMetadata; +import io.ballerina.runtime.api.types.MethodType; +import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.values.BArray; +import io.ballerina.runtime.api.values.BError; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BObject; +import io.ballerina.runtime.api.values.BString; +import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +/** + * Java Input Stream based on Ballerina byte block stream. stream + * + * @since 0.1.0 + */ +public class BallerinaByteBlockInputStream extends InputStream { + + private final BObject iterator; + private final Environment env; + private final String nextMethodName; + private final Type returnType; + private final String strandName; + private final StrandMetadata metadata; + private final Map properties; + private final AtomicBoolean done = new AtomicBoolean(false); + private final MethodType closeMethod; + private final Consumer futureResultConsumer; + + private byte[] currentChunk = new byte[0]; + private int nextChunkIndex = 0; + + public BallerinaByteBlockInputStream(Environment env, BObject iterator, MethodType nextMethod, + MethodType closeMethod, Consumer futureResultConsumer) { + this.env = env; + this.iterator = iterator; + this.nextMethodName = nextMethod.getName(); + this.returnType = nextMethod.getReturnType(); + this.closeMethod = closeMethod; + this.strandName = env.getStrandName().orElse(""); + this.metadata = env.getStrandMetadata(); + this.properties = Map.of(); + this.futureResultConsumer = futureResultConsumer; + } + + @Override + public int read() { + if (done.get()) { + return -1; + } + if (hasBytesInCurrentChunk()) { + return currentChunk[nextChunkIndex++]; + } + // Need to get a new block from the stream, before reading again. + nextChunkIndex = 0; + try { + if (readNextChunk()) { + return read(); + } + } catch (InterruptedException e) { + BError error = DiagnosticLog.getJsonError("Cannot read the stream, interrupted error"); + futureResultConsumer.accept(error); + return -1; + } + return -1; + } + + @Override + public void close() throws IOException { + super.close(); + Semaphore semaphore = new Semaphore(0); + if (closeMethod != null) { + env.getRuntime().invokeMethodAsyncSequentially(iterator, closeMethod.getName(), strandName, metadata, + new Callback() { + @Override + public void notifyFailure(BError bError) { + semaphore.release(); + } + + @Override + public void notifySuccess(Object result) { + semaphore.release(); + } + }, properties, returnType); + } + try { + semaphore.acquire(); + } catch (InterruptedException e) { + throw new IOException("Error while closing the stream", e); + } + } + + private boolean hasBytesInCurrentChunk() { + return currentChunk.length != 0 && nextChunkIndex < currentChunk.length; + } + + private boolean readNextChunk() throws InterruptedException { + Semaphore semaphore = new Semaphore(0); + Callback callback = new Callback() { + + @Override + public void notifyFailure(BError bError) { + // Panic with an error + done.set(true); + futureResultConsumer.accept(bError); + currentChunk = new byte[0]; + semaphore.release(); + // TODO : Should we panic here? + } + + @Override + public void notifySuccess(Object result) { + if (result == null) { + done.set(true); + currentChunk = new byte[0]; + semaphore.release(); + return; + } + if (result instanceof BMap) { + BMap valueRecord = (BMap) result; + final BString value = Arrays.stream(valueRecord.getKeys()).findFirst().get(); + final BArray arrayValue = valueRecord.getArrayValue(value); + currentChunk = arrayValue.getByteArray(); + semaphore.release(); + } else { + // Case where Completes with an error + done.set(true); + semaphore.release(); + } + } + + }; + env.getRuntime().invokeMethodAsyncSequentially(iterator, nextMethodName, strandName, metadata, callback, + properties, returnType); + semaphore.acquire(); + return !done.get(); + } +} diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderTask.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderTask.java new file mode 100644 index 0000000..159c980 --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderTask.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.data.jsondata.io; + +import io.ballerina.runtime.api.Environment; +import io.ballerina.runtime.api.Future; +import io.ballerina.runtime.api.types.MethodType; +import io.ballerina.runtime.api.types.ObjectType; +import io.ballerina.runtime.api.utils.TypeUtils; +import io.ballerina.runtime.api.values.BObject; +import io.ballerina.runtime.api.values.BTypedesc; +import io.ballerina.stdlib.data.jsondata.json.JsonParser; +import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; + +import java.io.InputStreamReader; +import java.util.function.Consumer; + +/** + * This class will read data from a Ballerina Stream of byte blocks, in non-blocking manner. + * + * @since 0.1.0 + */ +public class DataReaderTask implements Runnable { + + private static final String METHOD_NAME_NEXT = "next"; + private static final String METHOD_NAME_CLOSE = "close"; + + private final Environment env; + private final BObject iteratorObj; + private final Future future; + private final BTypedesc typed; + + public DataReaderTask(Environment env, BObject iteratorObj, Future future, BTypedesc typed) { + this.env = env; + this.iteratorObj = iteratorObj; + this.future = future; + this.typed = typed; + } + + static MethodType resolveNextMethod(BObject iterator) { + MethodType method = getMethodType(iterator, METHOD_NAME_NEXT); + if (method != null) { + return method; + } + throw new IllegalStateException("next method not found in the iterator object"); + } + + static MethodType resolveCloseMethod(BObject iterator) { + return getMethodType(iterator, METHOD_NAME_CLOSE); + } + + private static MethodType getMethodType(BObject iterator, String methodNameClose) { + ObjectType objectType = (ObjectType) TypeUtils.getReferredType(iterator.getOriginalType()); + MethodType[] methods = objectType.getMethods(); + // Assumes compile-time validation of the iterator object + for (MethodType method : methods) { + if (method.getName().equals(methodNameClose)) { + return method; + } + } + return null; + } + + @Override + public void run() { + ResultConsumer resultConsumer = new ResultConsumer<>(future); + try (var byteBlockSteam = new BallerinaByteBlockInputStream(env, iteratorObj, resolveNextMethod(iteratorObj), + resolveCloseMethod(iteratorObj), resultConsumer)) { + Object result = JsonParser.parse(new InputStreamReader(byteBlockSteam), typed.getDescribingType()); + future.complete(result); + } catch (Exception e) { + future.complete(DiagnosticLog.getJsonError("Error occurred while reading the stream: " + e.getMessage())); + } + } + + /** + * This class will hold module related utility functions. + * + * @param The type of the result + * @param future The future to complete + * @since 0.1.0 + */ + public record ResultConsumer(Future future) implements Consumer { + + @Override + public void accept(T t) { + future.complete(t); + } + } +} diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderThreadPool.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderThreadPool.java new file mode 100644 index 0000000..80f73de --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderThreadPool.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.data.jsondata.io; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Thread pool for data reader. + * + * @since 0.1.0 + */ +public class DataReaderThreadPool { + + // TODO : Make this configurable, in Ballerina Library. + public static final ExecutorService EXECUTOR_SERVICE = new ThreadPoolExecutor(0, 50, 60L, TimeUnit.SECONDS, + new SynchronousQueue<>(), + new DataThreadFactory()); + + /** + * Thread factory for data reader. + */ + static class DataThreadFactory implements ThreadFactory { + + @Override + public Thread newThread(Runnable runnable) { + Thread ballerinaSql = new Thread(runnable); + ballerinaSql.setName("bal-data-xmldata-thread"); + return ballerinaSql; + } + } +} diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java index 6654d27..c658984 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java @@ -19,12 +19,22 @@ package io.ballerina.stdlib.data.jsondata.json; import io.ballerina.runtime.api.Environment; +import io.ballerina.runtime.api.Future; import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BObject; +import io.ballerina.runtime.api.values.BStream; import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.api.values.BTypedesc; +import io.ballerina.stdlib.data.jsondata.io.DataReaderTask; +import io.ballerina.stdlib.data.jsondata.io.DataReaderThreadPool; +import io.ballerina.stdlib.data.jsondata.utils.DiagnosticErrorCode; +import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; import java.io.StringReader; /** @@ -48,10 +58,23 @@ public static Object fromJsonStringWithType(Environment env, Object json, BMap Date: Thu, 8 Feb 2024 21:54:50 +0530 Subject: [PATCH 12/27] Implement compiler plugin --- ballerina/CompilerPlugin.toml | 6 + ballerina/build.gradle | 11 +- build-config/resources/CompilerPlugin.toml | 6 + compiler-plugin-test/build.gradle | 96 ++++++++ .../jsondata/compiler/CompilerPluginTest.java | 83 +++++++ .../compiler/CompilerPluginTestUtils.java | 46 ++++ .../sample_package_1/Ballerina.toml | 4 + .../sample_package_1/sample.bal | 5 + .../sample_package_2/Ballerina.toml | 4 + .../sample_package_2/sample.bal | 7 + .../sample_package_3/Ballerina.toml | 4 + .../sample_package_3/sample.bal | 23 ++ .../sample_package_4/Ballerina.toml | 4 + .../sample_package_4/sample.bal | 20 ++ .../src/test/resources/testng.xml | 27 +++ compiler-plugin/build.gradle | 74 ++++++ .../data/jsondata/compiler/Constants.java | 27 +++ .../compiler/JsondataCodeAnalyzer.java | 37 +++ .../compiler/JsondataCompilerPlugin.java | 33 +++ .../compiler/JsondataDiagnosticCodes.java | 53 +++++ .../JsondataRecordFieldValidator.java | 217 ++++++++++++++++++ .../src/main/java/module-info.java | 23 ++ .../jsondata/io/DataReaderThreadPool.java | 6 +- settings.gradle | 4 + 24 files changed, 815 insertions(+), 5 deletions(-) create mode 100644 ballerina/CompilerPlugin.toml create mode 100644 build-config/resources/CompilerPlugin.toml create mode 100644 compiler-plugin-test/build.gradle create mode 100644 compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTest.java create mode 100644 compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTestUtils.java create mode 100644 compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_1/Ballerina.toml create mode 100644 compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_1/sample.bal create mode 100644 compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_2/Ballerina.toml create mode 100644 compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_2/sample.bal create mode 100644 compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/Ballerina.toml create mode 100644 compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/sample.bal create mode 100644 compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/Ballerina.toml create mode 100644 compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/sample.bal create mode 100644 compiler-plugin-test/src/test/resources/testng.xml create mode 100644 compiler-plugin/build.gradle create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCodeAnalyzer.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCompilerPlugin.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataDiagnosticCodes.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataRecordFieldValidator.java create mode 100644 compiler-plugin/src/main/java/module-info.java diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml new file mode 100644 index 0000000..4daf114 --- /dev/null +++ b/ballerina/CompilerPlugin.toml @@ -0,0 +1,6 @@ +[plugin] +id = "constraint-compiler-plugin" +class = "io.ballerina.stdlib.data.jsondata.compiler.JsondataCompilerPlugin" + +[[dependency]] +path = "../compiler-plugin/build/libs/data.jsondata-compiler-plugin-0.1.0-SNAPSHOT.jar" diff --git a/ballerina/build.gradle b/ballerina/build.gradle index 5027152..ed5c3fc 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -40,6 +40,8 @@ def packageOrg = "ballerina" def tomlVersion = stripBallerinaExtensionVersion("${project.version}") def ballerinaTomlFilePlaceHolder = new File("${project.rootDir}/build-config/resources/Ballerina.toml") def ballerinaTomlFile = new File("$project.projectDir/Ballerina.toml") +def compilerPluginTomlFilePlaceHolder = new File("${project.rootDir}/build-config/resources/CompilerPlugin.toml") +def compilerPluginTomlFile = new File("$project.projectDir/CompilerPlugin.toml") def stripBallerinaExtensionVersion(String extVersion) { if (extVersion.matches(project.ext.timestampedVersionRegex)) { @@ -68,6 +70,9 @@ task updateTomlFiles { def newConfig = ballerinaTomlFilePlaceHolder.text.replace("@project.version@", project.version) newConfig = newConfig.replace("@toml.version@", tomlVersion) ballerinaTomlFile.text = newConfig + + def newCompilerPluginToml = compilerPluginTomlFilePlaceHolder.text.replace("@project.version@", project.version) + compilerPluginTomlFile.text = newCompilerPluginToml } } @@ -76,9 +81,9 @@ task commitTomlFiles { project.exec { ignoreExitValue true if (Os.isFamily(Os.FAMILY_WINDOWS)) { - commandLine 'cmd', '/c', "git commit -m \"[Automated] Update the native jar versions\" Ballerina.toml Dependencies.toml" + commandLine 'cmd', '/c', "git commit Ballerina.toml Dependencies.toml CompilerPlugin.toml -m '[Automated] Update the native jar versions'" } else { - commandLine 'sh', '-c', "git commit -m '[Automated] Update the native jar versions' Ballerina.toml Dependencies.toml" + commandLine 'sh', '-c', "git commit Ballerina.toml Dependencies.toml CompilerPlugin.toml -m '[Automated] Update the native jar versions'" } } } @@ -113,6 +118,8 @@ updateTomlFiles.dependsOn copyStdlibs build.dependsOn "generatePomFileForMavenPublication" build.dependsOn ":${packageName}-native:build" +build.dependsOn ":${packageName}-compiler-plugin:build" build.dependsOn deleteDependencyTomlFiles test.dependsOn ":${packageName}-native:build" +test.dependsOn ":${packageName}-compiler-plugin:build" diff --git a/build-config/resources/CompilerPlugin.toml b/build-config/resources/CompilerPlugin.toml new file mode 100644 index 0000000..6eabf88 --- /dev/null +++ b/build-config/resources/CompilerPlugin.toml @@ -0,0 +1,6 @@ +[plugin] +id = "constraint-compiler-plugin" +class = "io.ballerina.stdlib.data.jsondata.compiler.JsondataCompilerPlugin" + +[[dependency]] +path = "../compiler-plugin/build/libs/data.jsondata-compiler-plugin-@project.version@.jar" diff --git a/compiler-plugin-test/build.gradle b/compiler-plugin-test/build.gradle new file mode 100644 index 0000000..efc4838 --- /dev/null +++ b/compiler-plugin-test/build.gradle @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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. + */ + +plugins { + id 'java' + id 'checkstyle' + id 'com.github.spotbugs' +} + +description = 'Ballerina - Jsondata Compiler Plugin Tests' + +dependencies { + checkstyle project(':checkstyle') + checkstyle "com.puppycrawl.tools:checkstyle:${puppycrawlCheckstyleVersion}" + + implementation project(':data.jsondata-compiler-plugin') + + testImplementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}" + testImplementation group: 'org.ballerinalang', name: 'ballerina-tools-api', version: "${ballerinaLangVersion}" + testImplementation group: 'org.ballerinalang', name: 'ballerina-parser', version: "${ballerinaLangVersion}" + testImplementation group: 'org.testng', name: 'testng', version: "${testngVersion}" +} + +tasks.withType(Checkstyle) { + exclude '**/module-info.java' +} + +checkstyle { + toolVersion "${project.puppycrawlCheckstyleVersion}" + configFile rootProject.file("build-config/checkstyle/build/checkstyle.xml") + configProperties = ["suppressionFile" : file("${rootDir}/build-config/checkstyle/build/suppressions.xml")] +} + +checkstyleTest.dependsOn(":checkstyle:downloadCheckstyleRuleFiles") + +spotbugsTest { + effort "max" + reportLevel "low" + reportsDir = file("$project.buildDir/reports/spotbugs") + reports { + html.enabled true + text.enabled = true + } + def excludeFile = file("${project.projectDir}/spotbugs-exclude.xml") + if(excludeFile.exists()) { + excludeFilter = excludeFile + } +} + +spotbugsMain { + enabled false +} + +checkstyleMain { + enabled false +} + +compileJava { + doFirst { + options.compilerArgs = [ + '--module-path', classpath.asPath, + ] + classpath = files() + } +} + +test { + systemProperty "ballerina.offline.flag", "true" + useTestNG() + finalizedBy jacocoTestReport +} + +jacocoTestReport { + dependsOn test + reports { + xml.required = true + } + sourceSets project(':data.jsondata-compiler-plugin').sourceSets.main +} + +test.dependsOn ":data.jsondata-ballerina:build" diff --git a/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTest.java b/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTest.java new file mode 100644 index 0000000..6fdf111 --- /dev/null +++ b/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.data.jsondata.compiler; + +import io.ballerina.projects.DiagnosticResult; +import io.ballerina.tools.diagnostics.Diagnostic; +import io.ballerina.tools.diagnostics.DiagnosticSeverity; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * This class includes tests for Ballerina Jsondata compiler plugin. + */ +public class CompilerPluginTest { + + static final String UNSUPPORTED_UNION_TYPE = + "unsupported union type: union type does not support multiple complex types"; + + @Test + public void testInvalidExpectedUnionType1() { + DiagnosticResult diagnosticResult = + CompilerPluginTestUtils.loadPackage("sample_package_1").getCompilation().diagnosticResult(); + List errorDiagnosticsList = diagnosticResult.diagnostics().stream() + .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) + .collect(Collectors.toList()); + Assert.assertEquals(errorDiagnosticsList.size(), 1); + Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), UNSUPPORTED_UNION_TYPE); + } + + @Test + public void testInvalidExpectedUnionType2() { + DiagnosticResult diagnosticResult = + CompilerPluginTestUtils.loadPackage("sample_package_2").getCompilation().diagnosticResult(); + List errorDiagnosticsList = diagnosticResult.diagnostics().stream() + .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) + .collect(Collectors.toList()); + Assert.assertEquals(errorDiagnosticsList.size(), 1); + Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), UNSUPPORTED_UNION_TYPE); + } + + @Test + public void testInvalidRecordFieldType1() { + DiagnosticResult diagnosticResult = + CompilerPluginTestUtils.loadPackage("sample_package_3").getCompilation().diagnosticResult(); + List errorDiagnosticsList = diagnosticResult.diagnostics().stream() + .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) + .collect(Collectors.toList()); + Assert.assertEquals(errorDiagnosticsList.size(), 2); + Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), UNSUPPORTED_UNION_TYPE); + Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), UNSUPPORTED_UNION_TYPE); + } + + @Test + public void testInvalidRecordFieldType2() { + DiagnosticResult diagnosticResult = + CompilerPluginTestUtils.loadPackage("sample_package_4").getCompilation().diagnosticResult(); + List errorDiagnosticsList = diagnosticResult.diagnostics().stream() + .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) + .collect(Collectors.toList()); + Assert.assertEquals(errorDiagnosticsList.size(), 2); + Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), UNSUPPORTED_UNION_TYPE); + Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), UNSUPPORTED_UNION_TYPE); + } +} diff --git a/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTestUtils.java b/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTestUtils.java new file mode 100644 index 0000000..59d2e55 --- /dev/null +++ b/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTestUtils.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.data.jsondata.compiler; + +import io.ballerina.projects.Package; +import io.ballerina.projects.ProjectEnvironmentBuilder; +import io.ballerina.projects.directory.BuildProject; +import io.ballerina.projects.environment.Environment; +import io.ballerina.projects.environment.EnvironmentBuilder; + +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Utility functions related to compiler plugins tests. + */ +public class CompilerPluginTestUtils { + private static final Path RESOURCE_DIRECTORY = Paths.get("src", "test", "resources", "ballerina_sources") + .toAbsolutePath(); + private static final Path DISTRIBUTION_PATH = Paths.get("../", "target", "ballerina-runtime") + .toAbsolutePath(); + + static Package loadPackage(String path) { + Path projectDirPath = RESOURCE_DIRECTORY.resolve(path); + Environment environment = EnvironmentBuilder.getBuilder().setBallerinaHome(DISTRIBUTION_PATH).build(); + ProjectEnvironmentBuilder projectEnvironmentBuilder = ProjectEnvironmentBuilder.getBuilder(environment); + BuildProject project = BuildProject.load(projectEnvironmentBuilder, projectDirPath); + return project.currentPackage(); + } +} diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_1/Ballerina.toml b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_1/Ballerina.toml new file mode 100644 index 0000000..f38a465 --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_1/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "jsondata_test" +name = "sample_1" +version = "0.1.0" diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_1/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_1/sample.bal new file mode 100644 index 0000000..a091071 --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_1/sample.bal @@ -0,0 +1,5 @@ +import ballerina/data.jsondata; + +public function main() returns error? { + int|record {| int a;|}|record {| int b;|} val = check jsondata:fromJsonStringWithType("1"); +} diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_2/Ballerina.toml b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_2/Ballerina.toml new file mode 100644 index 0000000..a662ba2 --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_2/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "jsondata_test" +name = "sample_2" +version = "0.1.0" diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_2/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_2/sample.bal new file mode 100644 index 0000000..b60447d --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_2/sample.bal @@ -0,0 +1,7 @@ +import ballerina/data.jsondata; + +type Union int|record {| int a;|}|record {| int b;|}; + +public function main() returns error? { + Union val = check jsondata:fromJsonStringWithType("1"); +} diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/Ballerina.toml b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/Ballerina.toml new file mode 100644 index 0000000..2f1fe15 --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "jsondata_test" +name = "sample_3" +version = "0.1.0" diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/sample.bal new file mode 100644 index 0000000..845f93e --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/sample.bal @@ -0,0 +1,23 @@ +import ballerina/data.jsondata; + +type Person record {| + string? name; + record {|string street; string country;|}|map address; + record {|string street; string country;|}|json company; +|}; + + +public function main() returns error? { + string str = string `{ + "name": "John", + "address": { + "street": "Main Street", + "country": "USA" + }, + "company": { + "street": "Main Street", + "country": "USA" + } + }`; + Person val = check jsondata:fromJsonStringWithType(str); +} diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/Ballerina.toml b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/Ballerina.toml new file mode 100644 index 0000000..18f1274 --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "jsondata_test" +name = "sample_4" +version = "0.1.0" diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/sample.bal new file mode 100644 index 0000000..b5ab36c --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/sample.bal @@ -0,0 +1,20 @@ +import ballerina/data.jsondata; + +type Person record {| + string? name; + record {|string street; string country;|}|map address; + record {|string street; string country;|}|json company; +|}; + +string str = string `{ + "name": "John", + "address": { + "street": "Main Street", + "country": "USA" + }, + "company": { + "street": "Main Street", + "country": "USA" + } + }`; +Person val = check jsondata:fromJsonStringWithType(str); diff --git a/compiler-plugin-test/src/test/resources/testng.xml b/compiler-plugin-test/src/test/resources/testng.xml new file mode 100644 index 0000000..4a44310 --- /dev/null +++ b/compiler-plugin-test/src/test/resources/testng.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + diff --git a/compiler-plugin/build.gradle b/compiler-plugin/build.gradle new file mode 100644 index 0000000..0aed114 --- /dev/null +++ b/compiler-plugin/build.gradle @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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. + */ + +plugins { + id 'java' + id 'checkstyle' + id 'com.github.spotbugs' +} + +description = 'Ballerina - JsonData Compiler Plugin' + +dependencies { + checkstyle project(':checkstyle') + checkstyle "com.puppycrawl.tools:checkstyle:${puppycrawlCheckstyleVersion}" + + implementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}" + implementation group: 'org.ballerinalang', name: 'ballerina-tools-api', version: "${ballerinaLangVersion}" + implementation group: 'org.ballerinalang', name: 'ballerina-parser', version: "${ballerinaLangVersion}" +} + +def excludePattern = '**/module-info.java' +tasks.withType(Checkstyle) { + exclude excludePattern +} + +checkstyle { + toolVersion "${project.puppycrawlCheckstyleVersion}" + configFile rootProject.file("build-config/checkstyle/build/checkstyle.xml") + configProperties = ["suppressionFile" : file("${rootDir}/build-config/checkstyle/build/suppressions.xml")] +} + +checkstyleMain.dependsOn(":checkstyle:downloadCheckstyleRuleFiles") + +spotbugsMain { + effort "max" + reportLevel "low" + reportsDir = file("$project.buildDir/reports/spotbugs") + reports { + html.enabled true + text.enabled = true + } + def excludeFile = file("${rootDir}/spotbugs-exclude.xml") + if(excludeFile.exists()) { + excludeFilter = excludeFile + } +} + +spotbugsMain { + enabled false +} + +compileJava { + doFirst { + options.compilerArgs = [ + '--module-path', classpath.asPath, + ] + classpath = files() + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java new file mode 100644 index 0000000..b0ba079 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.data.jsondata.compiler; + +/** + * Constants for Jsondata's compiler plugin. + */ +public class Constants { + static final String FROM_JSON_STRING_WITH_TYPE = "fromJsonStringWithType"; + static final String FROM_JSON_WITH_TYPE = "fromJsonWithType"; +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCodeAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCodeAnalyzer.java new file mode 100644 index 0000000..221200b --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCodeAnalyzer.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.data.jsondata.compiler; + +import io.ballerina.compiler.syntax.tree.SyntaxKind; +import io.ballerina.projects.plugins.CodeAnalysisContext; +import io.ballerina.projects.plugins.CodeAnalyzer; + +import java.util.List; + +/** + * Jsondata Code Analyzer. + */ +public class JsondataCodeAnalyzer extends CodeAnalyzer { + + @Override + public void init(CodeAnalysisContext codeAnalysisContext) { + codeAnalysisContext.addSyntaxNodeAnalysisTask(new JsondataRecordFieldValidator(), + List.of(SyntaxKind.MODULE_PART)); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCompilerPlugin.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCompilerPlugin.java new file mode 100644 index 0000000..6ec85e1 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCompilerPlugin.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.data.jsondata.compiler; + +import io.ballerina.projects.plugins.CompilerPlugin; +import io.ballerina.projects.plugins.CompilerPluginContext; + +/** + * Compiler plugin for Jsondata's utils functions. + */ +public class JsondataCompilerPlugin extends CompilerPlugin { + + @Override + public void init(CompilerPluginContext compilerPluginContext) { + compilerPluginContext.addCodeAnalyzer(new JsondataCodeAnalyzer()); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataDiagnosticCodes.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataDiagnosticCodes.java new file mode 100644 index 0000000..3185a93 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataDiagnosticCodes.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.data.jsondata.compiler; + +import io.ballerina.tools.diagnostics.DiagnosticSeverity; + +import static io.ballerina.tools.diagnostics.DiagnosticSeverity.ERROR; + +public enum JsondataDiagnosticCodes { + + UNSUPPORTED_UNION_TYPE("BDE202", + "unsupported union type: union type does not support multiple complex types", ERROR); + + private final String code; + private final String message; + private final DiagnosticSeverity severity; + + JsondataDiagnosticCodes(String code, String message, DiagnosticSeverity severity) { + this.code = code; + this.message = message; + this.severity = severity; + } + + public String getCode() { + return code; + } + + public String getMessage() { + return message; + } + + public DiagnosticSeverity getSeverity() { + return severity; + } + +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataRecordFieldValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataRecordFieldValidator.java new file mode 100644 index 0000000..622752e --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataRecordFieldValidator.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.data.jsondata.compiler; + +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.ArrayTypeSymbol; +import io.ballerina.compiler.api.symbols.RecordFieldSymbol; +import io.ballerina.compiler.api.symbols.RecordTypeSymbol; +import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.api.symbols.TupleTypeSymbol; +import io.ballerina.compiler.api.symbols.TypeDescKind; +import io.ballerina.compiler.api.symbols.TypeReferenceTypeSymbol; +import io.ballerina.compiler.api.symbols.TypeSymbol; +import io.ballerina.compiler.api.symbols.UnionTypeSymbol; +import io.ballerina.compiler.api.symbols.VariableSymbol; +import io.ballerina.compiler.syntax.tree.CheckExpressionNode; +import io.ballerina.compiler.syntax.tree.ChildNodeList; +import io.ballerina.compiler.syntax.tree.ExpressionNode; +import io.ballerina.compiler.syntax.tree.FunctionCallExpressionNode; +import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; +import io.ballerina.compiler.syntax.tree.ModuleMemberDeclarationNode; +import io.ballerina.compiler.syntax.tree.ModulePartNode; +import io.ballerina.compiler.syntax.tree.ModuleVariableDeclarationNode; +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.SyntaxKind; +import io.ballerina.compiler.syntax.tree.VariableDeclarationNode; +import io.ballerina.projects.plugins.AnalysisTask; +import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; +import io.ballerina.tools.diagnostics.Diagnostic; +import io.ballerina.tools.diagnostics.DiagnosticFactory; +import io.ballerina.tools.diagnostics.DiagnosticInfo; +import io.ballerina.tools.diagnostics.DiagnosticSeverity; +import io.ballerina.tools.diagnostics.Location; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Jsondata Record Field Validator. + */ +public class JsondataRecordFieldValidator implements AnalysisTask { + + private SemanticModel semanticModel; + private final HashMap allDiagnosticInfo = new HashMap<>(); + Location currentLocation; + + @Override + public void perform(SyntaxNodeAnalysisContext ctx) { + semanticModel = ctx.semanticModel(); + List diagnostics = semanticModel.diagnostics(); + boolean erroneousCompilation = diagnostics.stream() + .anyMatch(d -> d.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)); + if (erroneousCompilation) { + return; + } + + ModulePartNode rootNode = (ModulePartNode) ctx.node(); + for (ModuleMemberDeclarationNode member : rootNode.members()) { + switch (member.kind()) { + case FUNCTION_DEFINITION -> processFunctionDefinitionNode((FunctionDefinitionNode) member, ctx); + case MODULE_VAR_DECL -> + processModuleVariableDeclarationNode((ModuleVariableDeclarationNode) member, ctx); + } + } + } + + private void processFunctionDefinitionNode(FunctionDefinitionNode functionDefinitionNode, + SyntaxNodeAnalysisContext ctx) { + ChildNodeList childNodeList = functionDefinitionNode.functionBody().children(); + for (Node node : childNodeList) { + if (node.kind() != SyntaxKind.LOCAL_VAR_DECL) { + continue; + } + VariableDeclarationNode variableDeclarationNode = (VariableDeclarationNode) node; + Optional initializer = variableDeclarationNode.initializer(); + if (initializer.isEmpty() || !isFromJsonFunctionFromXmldata(initializer.get())) { + continue; + } + + currentLocation = variableDeclarationNode.typedBindingPattern().typeDescriptor().location(); + Optional symbol = semanticModel.symbol(variableDeclarationNode.typedBindingPattern()); + if (symbol.isEmpty()) { + continue; + } + validateExpectedType(((VariableSymbol) symbol.get()).typeDescriptor(), ctx); + } + } + + private boolean isFromJsonFunctionFromXmldata(ExpressionNode expressionNode) { + if (expressionNode.kind() == SyntaxKind.CHECK_EXPRESSION) { + expressionNode = ((CheckExpressionNode) expressionNode).expression(); + } + + if (expressionNode.kind() != SyntaxKind.FUNCTION_CALL) { + return false; + } + String functionName = ((FunctionCallExpressionNode) expressionNode).functionName().toSourceCode().trim(); + return functionName.contains(Constants.FROM_JSON_STRING_WITH_TYPE); + } + + private void validateExpectedType(TypeSymbol typeSymbol, SyntaxNodeAnalysisContext ctx) { + typeSymbol.getLocation().ifPresent(location -> currentLocation = location); + switch (typeSymbol.typeKind()) { + case UNION -> { + validateUnionType((UnionTypeSymbol) typeSymbol, typeSymbol.getLocation(), ctx); + } + case RECORD -> { + validateRecordType((RecordTypeSymbol) typeSymbol, ctx); + } + case ARRAY -> { + validateExpectedType(((ArrayTypeSymbol) typeSymbol).memberTypeDescriptor(), ctx); + } + case TUPLE -> { + validateTupleType((TupleTypeSymbol) typeSymbol, ctx); + } + case TYPE_REFERENCE -> { + validateExpectedType(((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor(), ctx); + } + } + } + + private void validateTupleType(TupleTypeSymbol tupleTypeSymbol, SyntaxNodeAnalysisContext ctx) { + for (TypeSymbol memberType : tupleTypeSymbol.memberTypeDescriptors()) { + validateExpectedType(memberType, ctx); + } + } + + private void validateRecordType(RecordTypeSymbol recordTypeSymbol, SyntaxNodeAnalysisContext ctx) { + for (Map.Entry entry : recordTypeSymbol.fieldDescriptors().entrySet()) { + RecordFieldSymbol fieldSymbol = entry.getValue(); + TypeSymbol typeSymbol = fieldSymbol.typeDescriptor(); + validateRecordFieldType(typeSymbol, fieldSymbol.getLocation(), ctx); + } + } + + private void validateRecordFieldType(TypeSymbol typeSymbol, Optional location, + SyntaxNodeAnalysisContext ctx) { + switch (typeSymbol.typeKind()) { + case UNION -> validateUnionType((UnionTypeSymbol) typeSymbol, location, ctx); + case ARRAY -> validateRecordFieldType(((ArrayTypeSymbol) typeSymbol).memberTypeDescriptor(), location, ctx); + case TYPE_REFERENCE -> + validateRecordFieldType(((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor(), location, ctx); + } + } + + private void validateUnionType(UnionTypeSymbol unionTypeSymbol, Optional location, + SyntaxNodeAnalysisContext ctx) { + int nonPrimitiveMemberCount = 0; + List memberTypeSymbols = unionTypeSymbol.memberTypeDescriptors(); + for (TypeSymbol memberTypeSymbol : memberTypeSymbols) { + if (isPrimitiveType(memberTypeSymbol)) { + continue; + } + nonPrimitiveMemberCount++; + } + + if (nonPrimitiveMemberCount > 1) { + reportDiagnosticInfo(ctx, location, JsondataDiagnosticCodes.UNSUPPORTED_UNION_TYPE); + } + } + + private boolean isPrimitiveType(TypeSymbol typeSymbol) { + TypeDescKind kind = typeSymbol.typeKind(); + if (kind == TypeDescKind.TYPE_REFERENCE) { + kind = ((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor().typeKind(); + } + + return kind == TypeDescKind.INT || kind == TypeDescKind.FLOAT || kind == TypeDescKind.DECIMAL + || kind == TypeDescKind.STRING || kind == TypeDescKind.BOOLEAN || kind == TypeDescKind.BYTE + || kind == TypeDescKind.NIL; + } + + private void reportDiagnosticInfo(SyntaxNodeAnalysisContext ctx, Optional location, + JsondataDiagnosticCodes diagnosticsCodes) { + Location pos = location.orElseGet(() -> currentLocation); + DiagnosticInfo diagnosticInfo = new DiagnosticInfo(diagnosticsCodes.getCode(), + diagnosticsCodes.getMessage(), diagnosticsCodes.getSeverity()); + if (allDiagnosticInfo.containsKey(pos) && allDiagnosticInfo.get(pos).equals(diagnosticInfo)) { + return; + } + allDiagnosticInfo.put(pos, diagnosticInfo); + ctx.reportDiagnostic(DiagnosticFactory.createDiagnostic(diagnosticInfo, pos)); + } + + private void processModuleVariableDeclarationNode(ModuleVariableDeclarationNode moduleVariableDeclarationNode, + SyntaxNodeAnalysisContext ctx) { + Optional initializer = moduleVariableDeclarationNode.initializer(); + if (initializer.isEmpty() || !isFromJsonFunctionFromXmldata(initializer.get())) { + return; + } + + Optional symbol = semanticModel.symbol(moduleVariableDeclarationNode.typedBindingPattern()); + if (symbol.isEmpty()) { + return; + } + TypeSymbol typeSymbol = ((VariableSymbol) symbol.get()).typeDescriptor(); + validateExpectedType(typeSymbol, ctx); + } +} diff --git a/compiler-plugin/src/main/java/module-info.java b/compiler-plugin/src/main/java/module-info.java new file mode 100644 index 0000000..aa99b17 --- /dev/null +++ b/compiler-plugin/src/main/java/module-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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. + */ + +module io.ballerina.stdlib.jsondata.compiler { + requires io.ballerina.lang; + requires io.ballerina.tools.api; + requires io.ballerina.parser; +} diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderThreadPool.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderThreadPool.java index 80f73de..2cab568 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderThreadPool.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderThreadPool.java @@ -42,9 +42,9 @@ static class DataThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable runnable) { - Thread ballerinaSql = new Thread(runnable); - ballerinaSql.setName("bal-data-xmldata-thread"); - return ballerinaSql; + Thread ballerinaData = new Thread(runnable); + ballerinaData.setName("bal-data-jsondata-thread"); + return ballerinaData; } } } diff --git a/settings.gradle b/settings.gradle index 52fea65..3061afb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -24,10 +24,14 @@ rootProject.name = 'data.jsondata' include(':checkstyle') include(':data.jsondata-native') include(':data.jsondata-ballerina') +include(':data.jsondata-compiler-plugin') +include(':data.jsondata-compiler-plugin-tests') project(':checkstyle').projectDir = file("build-config${File.separator}checkstyle") project(':data.jsondata-native').projectDir = file('native') project(':data.jsondata-ballerina').projectDir = file('ballerina') +project(':data.jsondata-compiler-plugin').projectDir = file('compiler-plugin') +project(':data.jsondata-compiler-plugin-tests').projectDir = file('compiler-plugin-test') gradleEnterprise { buildScan { From 7447adc88e82db84f451d4bc30d0d6a1863148d1 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Fri, 9 Feb 2024 14:20:10 +0530 Subject: [PATCH 13/27] Add toJson function and update api docs --- ballerina/Ballerina.toml | 2 +- ballerina/Dependencies.toml | 2 +- ballerina/json_api.bal | 19 +++++ ballerina/tests/to_json_test.bal | 82 +++++++++++++++++++ build-config/resources/Ballerina.toml | 2 +- gradle.properties | 2 +- .../io/BallerinaByteBlockInputStream.java | 2 +- .../stdlib/data/jsondata/json/Native.java | 7 +- native/src/main/java/module-info.java | 2 +- 9 files changed, 113 insertions(+), 7 deletions(-) create mode 100644 ballerina/tests/to_json_test.bal diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 1784a11..53dacfc 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -7,7 +7,7 @@ keywords = ["json"] repository = "https://github.com/ballerina-platform/module-ballerina.jsondata" #icon = "icon.png" license = ["Apache-2.0"] -distribution = "2201.8.1" +distribution = "2201.8.4" [platform.java17] graalvmCompatible = true diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 8f644bd..636b88c 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.8.1" +distribution-version = "2201.8.4" [[package]] org = "ballerina" diff --git a/ballerina/json_api.bal b/ballerina/json_api.bal index 9722271..861fd23 100644 --- a/ballerina/json_api.bal +++ b/ballerina/json_api.bal @@ -16,12 +16,31 @@ import ballerina/jballerina.java; +# Convert value of type `json` to subtype of `anydata`. +# +# + v - Source JSON value +# + options - Options to be used for filtering in the projection +# + t - Target type +# + return - On success, returns the given target type value, else returns an `jsondata:Error` public isolated function fromJsonWithType(json v, Options options = {}, typedesc t = <>) returns t|Error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; +# Converts JSON string, byte[] or byte-block-stream to subtype of anydata. +# +# + s - Source JSON string value or byte[] or byte-block-stream +# + options - Options to be used for filtering in the projection +# + t - Target type +# + return - On success, returns the given target type value, else returns an `jsondata:Error` public isolated function fromJsonStringWithType(string|byte[]|stream s, Options options = {}, typedesc t = <>) returns t|Error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; +# Converts a value of type `anydata` to `json`. +# +# + v - Source anydata value +# + return - representation of `v` as value of type json +public isolated function toJson(anydata v) + returns json|Error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; + # Represent the options that can be used for filtering in the projection. # # + numericPreference - field description diff --git a/ballerina/tests/to_json_test.bal b/ballerina/tests/to_json_test.bal new file mode 100644 index 0000000..c18be05 --- /dev/null +++ b/ballerina/tests/to_json_test.bal @@ -0,0 +1,82 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you 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. + +import ballerina/test; + +@test:Config +function testToJsonWithBasicType() { + string name = "Kanth"; + json|Error j = toJson(name); + test:assertTrue(j is json); + test:assertEquals(j, "Kanth"); + + int age = 26; + json|Error j2 = toJson(age); + test:assertTrue(j2 is json); + test:assertEquals(j2, 26); + + float height = 5.6; + json|Error j3 = toJson(height); + test:assertTrue(j3 is json); + test:assertEquals(j3, 5.6); + + boolean isStudent = false; + json|Error j4 = toJson(isStudent); + test:assertTrue(j4 is json); + test:assertEquals(j4, false); + + json|Error j5 = toJson(()); + test:assertTrue(j5 is json); + test:assertEquals(j5, ()); +} + +type Student record { + string name; + int age; +}; + +@test:Config +function testToJsonWithRecord1() { + Student s = {name: "Kanth", age: 26}; + json|Error j = toJson(s); + test:assertTrue(j is json); + test:assertEquals(j, {name: "Kanth", age: 26}); +} + +type Address2 record {| + string country; + string city; + json...; +|}; + +@test:Config +function testToJsonWithRecord2() { + Address2 addr1 = {country: "x", city: "y", "street": "z", "no": 3}; + json|Error jsonaddr1 = toJson(addr1); + test:assertTrue(jsonaddr1 is json); + test:assertEquals(jsonaddr1, {country: "x", city: "y", "street": "z", "no": 3}); +} + +@test:Config +function testToJsonWithXML() { + xml x1 = xml ` + Some + Writer + `; + json|Error j = toJson(x1); + test:assertTrue(j is json); + test:assertEquals(j, x1.toString()); +} diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index bff9a3f..ad278d2 100644 --- a/build-config/resources/Ballerina.toml +++ b/build-config/resources/Ballerina.toml @@ -7,7 +7,7 @@ keywords = ["json"] repository = "https://github.com/ballerina-platform/module-ballerina.jsondata" #icon = "icon.png" license = ["Apache-2.0"] -distribution = "2201.8.1" +distribution = "2201.8.4" [platform.java17] graalvmCompatible = true diff --git a/gradle.properties b/gradle.properties index 538c62a..b37a623 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ org.gradle.caching=true group=io.ballerina.stdlib version=0.1.0-SNAPSHOT -ballerinaLangVersion=2201.8.1 +ballerinaLangVersion=2201.8.4 checkstyleToolVersion=10.12.0 puppycrawlCheckstyleVersion=10.12.0 diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/BallerinaByteBlockInputStream.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/BallerinaByteBlockInputStream.java index 1e3588b..f065610 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/BallerinaByteBlockInputStream.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/BallerinaByteBlockInputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java index c658984..d2ccfd7 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java @@ -21,6 +21,7 @@ import io.ballerina.runtime.api.Environment; import io.ballerina.runtime.api.Future; import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.utils.JsonUtils; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BMap; @@ -38,7 +39,7 @@ import java.io.StringReader; /** - * This class is used to convert json inform of string, byte[], byte-stream to record or json type. + * Json conversions. * * @since 0.1.0 */ @@ -77,4 +78,8 @@ public static Object fromJsonStringWithType(Environment env, Object json, BMap Date: Fri, 9 Feb 2024 14:22:33 +0530 Subject: [PATCH 14/27] Add stdlib workflow files --- .github/CODEOWNERS | 7 ++++ .github/pull_request_template.md | 12 ++++++ .../workflows/build-timestamped-master.yml | 18 +++++++++ .../workflows/build-with-bal-test-graalvm.yml | 37 +++++++++++++++++++ .github/workflows/central-publish.yml | 21 +++++++++++ .github/workflows/publish-release.yml | 16 ++++++++ .github/workflows/pull-request.yml | 14 +++++++ .github/workflows/stale-check.yml | 19 ++++++++++ .github/workflows/trivy-scan.yml | 13 +++++++ 9 files changed, 157 insertions(+) create mode 100644 .github/CODEOWNERS create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/build-timestamped-master.yml create mode 100644 .github/workflows/build-with-bal-test-graalvm.yml create mode 100644 .github/workflows/central-publish.yml create mode 100644 .github/workflows/publish-release.yml create mode 100644 .github/workflows/pull-request.yml create mode 100644 .github/workflows/stale-check.yml create mode 100644 .github/workflows/trivy-scan.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..c5ebbcf --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,7 @@ +# Lines starting with '#' are comments. +# Each line is a file pattern followed by one or more owners. + +# See: https://help.github.com/articles/about-codeowners/ + +# These owners will be the default owners for everything in the repo. +* @hasithaa @prakanth97 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..96b073c --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,12 @@ +## Purpose + +Fixes: + +## Examples + +## Checklist +- [ ] Linked to an issue +- [ ] Updated the changelog +- [ ] Added tests +- [ ] Updated the spec +- [ ] Checked native-image compatibility diff --git a/.github/workflows/build-timestamped-master.yml b/.github/workflows/build-timestamped-master.yml new file mode 100644 index 0000000..dc0b8e4 --- /dev/null +++ b/.github/workflows/build-timestamped-master.yml @@ -0,0 +1,18 @@ +name: Build + +on: + push: + branches: + - main + paths-ignore: + - '*.md' + - 'docs/**' + - 'load-tests/**' + workflow_dispatch: + +jobs: + call_workflow: + name: Run Build Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-standard-library/.github/workflows/build-timestamp-master-template.yml@main + secrets: inherit diff --git a/.github/workflows/build-with-bal-test-graalvm.yml b/.github/workflows/build-with-bal-test-graalvm.yml new file mode 100644 index 0000000..fc149e3 --- /dev/null +++ b/.github/workflows/build-with-bal-test-graalvm.yml @@ -0,0 +1,37 @@ +name: GraalVM Check + +on: + workflow_dispatch: + inputs: + lang_tag: + description: Branch/Release Tag of the Ballerina Lang + required: true + default: master + lang_version: + description: Ballerina Lang Version (If given ballerina lang build will be skipped) + required: false + default: '' + native_image_options: + description: Default native-image options + required: false + default: '' + schedule: + - cron: '30 18 * * *' + pull_request: + branches: + - main + types: [ opened, synchronize, reopened, labeled, unlabeled ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +jobs: + call_stdlib_workflow: + name: Run StdLib Workflow + if: ${{ github.event_name != 'schedule' || (github.event_name == 'schedule' && github.repository_owner == 'ballerina-platform') }} + uses: ballerina-platform/ballerina-standard-library/.github/workflows/build-with-bal-test-graalvm-template.yml@main + with: + lang_tag: ${{ inputs.lang_tag }} + lang_version: ${{ inputs.lang_version }} + native_image_options: '-J-Xmx7G ${{ inputs.native_image_options }}' diff --git a/.github/workflows/central-publish.yml b/.github/workflows/central-publish.yml new file mode 100644 index 0000000..c0bd478 --- /dev/null +++ b/.github/workflows/central-publish.yml @@ -0,0 +1,21 @@ +name: Publish to the Ballerina central + +on: + workflow_dispatch: + inputs: + environment: + type: choice + description: Select Environment + required: true + options: + - DEV CENTRAL + - STAGE CENTRAL + +jobs: + call_workflow: + name: Run Central Publish Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-standard-library/.github/workflows/central-publish-template.yml@main + secrets: inherit + with: + environment: ${{ github.event.inputs.environment }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000..d5031f5 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,16 @@ +name: Publish Release + +on: + workflow_dispatch: + repository_dispatch: + types: [stdlib-release-pipeline] + +jobs: + call_workflow: + name: Run Release Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-standard-library/.github/workflows/release-package-template.yml@main + secrets: inherit + with: + package-name: data.jsondata + package-org: ballerina diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 0000000..48bfdc3 --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,14 @@ +name: Pull Request + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +on: pull_request + +jobs: + call_workflow: + name: Run PR Build Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-standard-library/.github/workflows/pull-request-build-template.yml@main + secrets: inherit diff --git a/.github/workflows/stale-check.yml b/.github/workflows/stale-check.yml new file mode 100644 index 0000000..8763360 --- /dev/null +++ b/.github/workflows/stale-check.yml @@ -0,0 +1,19 @@ +name: 'Close stale pull requests' + +on: + schedule: + - cron: '30 19 * * *' + workflow_dispatch: + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v3 + with: + stale-pr-message: 'This PR has been open for more than 15 days with no activity. This will be closed in 3 days unless the `stale` label is removed or commented.' + close-pr-message: 'Closed PR due to inactivity for more than 18 days.' + days-before-pr-stale: 15 + days-before-pr-close: 3 + days-before-issue-stale: -1 + days-before-issue-close: -1 diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml new file mode 100644 index 0000000..b9b5a62 --- /dev/null +++ b/.github/workflows/trivy-scan.yml @@ -0,0 +1,13 @@ +name: Trivy + +on: + workflow_dispatch: + schedule: + - cron: '30 20 * * *' + +jobs: + call_workflow: + name: Run Trivy Scan Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-standard-library/.github/workflows/trivy-scan-template.yml@main + secrets: inherit From 8d6724a91acaf197fed7a14b11fbf9cb53b53bf0 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:02:04 +0530 Subject: [PATCH 15/27] Add README files and name annotation support --- README.md | 265 +++++++++++++++++- ballerina/Package.md | 210 +++++++++++++- ballerina/json_api.bal | 10 + ballerina/tests/from_json_string_test.bal | 12 + ballerina/tests/types.bal | 9 + .../data/jsondata/compiler/Constants.java | 1 + .../JsondataRecordFieldValidator.java | 20 +- .../data/jsondata/json/JsonCreator.java | 40 ++- .../stdlib/data/jsondata/json/JsonParser.java | 14 +- .../data/jsondata/json/JsonTraverse.java | 3 +- .../stdlib/data/jsondata/utils/Constants.java | 6 + .../jsondata/utils/DiagnosticErrorCode.java | 3 +- native/src/main/resources/error.properties | 3 + 13 files changed, 564 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index f2bc4a0..1a3638f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,265 @@ -# module-ballerina-data.jsondata +# Ballerina JSON Data Library + The Ballerina JSON Data Library is a comprehensive toolkit designed to facilitate the handling and manipulation of JSON data within Ballerina applications. It streamlines the process of converting JSON data to native Ballerina data types, enabling developers to work with JSON content seamlessly and efficiently. + +## Features + +- **Versatile JSON Data Input**: Accept JSON data as a json, a string, byte array, or a stream and convert it into a subtype of anydata value. +- **JSON to anydata Value Conversion**: Transform JSON data into expected type which is subtype of anydata. +- **Projection Support**: Perform selective conversion of JSON data subsets into anydata values through projection. + +## Usage + +### Converting JSON Document value to a record value + +To convert an JSON document value to a Record value, you can utilize the `fromJsonWithType` function provided by the library. The example below showcases the transformation of an JSON document value into a Record value. + +```ballerina +import ballerina/data.jsondata; +import ballerina/io; + +type Book record { + string name; + string author; + int year; +}; + +public function main() returns error? { + json jsonContent = { + "name": "Clean Code", + "author": "Robert C. Martin", + "year": 2008 + }; + + Book book = check jsondata:fromJsonWithType(jsonContent); + io:println(b); +} +``` + +### Converting external JSON document to a record value + +For transforming JSON content from an external source into a Record value, the `fromJsonStringWithType` function can be used. This external source can be in the form of a string or a byte array/byte stream that houses the JSON data. This is commonly extracted from files or network sockets. The example below demonstrates the conversion of an JSON value from an external source into a Record value. + +```ballerina +import ballerina/data.jsondata; +import ballerina/io; + +type Book record { + string name; + string author; + int year; +}; + +public function main() returns error? { + string jsonContent = check io:fileReadString("path/to/file.json"); + Book book = check jsondata:fromJsonStringWithType(jsonContent); + io:println(book); +} +``` + +Make sure to handle possible errors that may arise during the file reading or JSON to anydata conversion process. The `check` keyword is utilized to handle these errors, but more sophisticated error handling can be implemented as per your requirements. + +## JSON to anydata representation + +The conversion of JSON data to subtype of anydata representation is a fundamental feature of the library. + +### JSON Object + +The JSON Object can be represented as a record value in Ballerina which facilitates a structured and type-safe approach to handling JSON data. + +Take for instance the following JSON Object snippet: +```json +{ + "author": "Robert C. Martin", + "books": [ + { + "name": "Clean Code", + "year": 2008 + }, + { + "name": "Clean Architecture", + "year": 2017 + } + ] +} +``` + +This JSON Object can be represented as a record value in Ballerina as follows: +```ballerina +type Author record { + string author; + Book[] books; +}; + +type Book record { + string name; + int year; +}; + +public function main() returns error? { + json jsonContent = { + "author": "Robert C. Martin", + "books": [ + { + "name": "Clean Code", + "year": 2008 + }, + { + "name": "Clean Architecture", + "year": 2017 + } + ] + }; + + Author author = check jsondata:fromJsonWithType(jsonContent); + io:println(author); +} +``` + +### JSON Array + +The JSON Array can be represented as an array/tuple values in Ballerina. + +```json +[ + { + "name": "Clean Code", + "year": 2008 + }, + { + "name": "Clean Architecture", + "year": 2017 + } +] +``` + +This JSON Array can be converted as an array/tuple in Ballerina as follows: +```ballerina +type Book record { + string name; + int year; +}; + +public function main() returns error? { + json jsonContent = [ + { + "name": "Clean Code", + "year": 2008 + }, + { + "name": "Clean Architecture", + "year": 2017 + } + ]; + + Book[] bookArr = check jsondata:fromJsonWithType(jsonContent); + io:println(bookArr); + + [Book, Book] bookTuple = check jsondata:fromJsonWithType(jsonContent); + io:println(bookTuple); +} +``` + +### Controlling the JSON to record conversion + +The library allows for selective conversion of JSON into records through the use of fields. This is beneficial when the JSON data contains elements that are not necessary to be transformed into record fields. + +```json +{ + "name": "Clean Code", + "author": "Robert C. Martin", + "year": 2008, + "publisher": "Prentice Hall" +} +``` + +The JSON data above contains `publisher` and `year` fields which are not required to be converted into a record field. + +```ballerina +type Book record {| + string name; + string author; +|}; + +public function main() returns error? { + json jsonContent = { + "name": "Clean Code", + "author": "Robert C. Martin", + "year": 2008, + "publisher": "Prentice Hall" + }; + + Book book = check jsondata:fromJsonWithType(jsonContent); + io:println(book); +} +``` + +However, if the rest field is utilized (or if the record type is defined as an open record), all elements in the JSON data will be transformed into record fields: + +```ballerina +type Book record { + string name; + string author; +} +``` + +In this instance, all other elements in the JSON data, such as `year` and `publisher` will be transformed into `string` type fields with the corresponding json object member as the key. + +This behavior extends to arrays as well. + +The process of projecting JSON data into a record supports various use cases, including the filtering out of unnecessary elements. This functionality is anticipated to be enhanced in the future to accommodate more complex scenarios, such as filtering values based on regular expressions, among others. + +## Issues and projects + +Issues and Projects tabs are disabled for this repository as this is part of the Ballerina standard library. To report bugs, request new features, start new discussions, view project boards, etc. please visit Ballerina standard library [parent repository](https://github.com/ballerina-platform/ballerina-standard-library). + +This repository only contains the source code for the package. + +## Building from the source + +### Set up the prerequisites + +1. Download and install Java SE Development Kit (JDK) version 17 (from one of the following locations). + * [Oracle](https://www.oracle.com/java/technologies/downloads/) + * [OpenJDK](https://adoptium.net/) + +2. Export your GitHub personal access token with the read package permissions as follows. + + export packageUser= + export packagePAT= + +### Building the source + +Execute the commands below to build from source. + +1. To build the library: + + ./gradlew clean build + +2. Publish ZIP artifact to the local `.m2` repository: + + ./gradlew clean build publishToMavenLocal + +3. Publish the generated artifacts to the local Ballerina central repository: + + ./gradlew clean build -PpublishToLocalCentral=true + +4. Publish the generated artifacts to the Ballerina central repository: + + ./gradlew clean build -PpublishToCentral=true + +## Contributing to Ballerina + +As an open source project, Ballerina welcomes contributions from the community. + +For more information, go to the [contribution guidelines](https://github.com/ballerina-platform/ballerina-lang/blob/master/CONTRIBUTING.md). + +## Code of conduct + +All contributors are encouraged to read the [Ballerina code of conduct](https://ballerina.io/code-of-conduct). + +## Useful links + +[//]: # (* For more information go to the [`jsondata` library](https://lib.ballerina.io/ballerina/data.jsondata/latest).) +* Chat live with us via our [Discord server](https://discord.gg/ballerinalang). +* Post all technical questions on Stack Overflow with the [#ballerina](https://stackoverflow.com/questions/tagged/ballerina) tag. diff --git a/ballerina/Package.md b/ballerina/Package.md index f2bc4a0..4628e64 100644 --- a/ballerina/Package.md +++ b/ballerina/Package.md @@ -1,2 +1,210 @@ -# module-ballerina-data.jsondata +# Ballerina JSON Data Library + The Ballerina JSON Data Library is a comprehensive toolkit designed to facilitate the handling and manipulation of JSON data within Ballerina applications. It streamlines the process of converting JSON data to native Ballerina data types, enabling developers to work with JSON content seamlessly and efficiently. + +## Features + +- **Versatile JSON Data Input**: Accept JSON data as a json, a string, byte array, or a stream and convert it into a subtype of anydata value. +- **JSON to anydata Value Conversion**: Transform JSON data into expected type which is subtype of anydata. +- **Projection Support**: Perform selective conversion of JSON data subsets into anydata values through projection. + +## Usage + +### Converting JSON Document value to a record value + +To convert an JSON document value to a Record value, you can utilize the `fromJsonWithType` function provided by the library. The example below showcases the transformation of an JSON document value into a Record value. + +```ballerina +import ballerina/data.jsondata; +import ballerina/io; + +type Book record { + string name; + string author; + int year; +}; + +public function main() returns error? { + json jsonContent = { + "name": "Clean Code", + "author": "Robert C. Martin", + "year": 2008 + }; + + Book book = check jsondata:fromJsonWithType(jsonContent); + io:println(b); +} +``` + +### Converting external JSON document to a record value + +For transforming JSON content from an external source into a Record value, the `fromJsonStringWithType` function can be used. This external source can be in the form of a string or a byte array/byte stream that houses the JSON data. This is commonly extracted from files or network sockets. The example below demonstrates the conversion of an JSON value from an external source into a Record value. + +```ballerina +import ballerina/data.jsondata; +import ballerina/io; + +type Book record { + string name; + string author; + int year; +}; + +public function main() returns error? { + string jsonContent = check io:fileReadString("path/to/file.json"); + Book book = check jsondata:fromJsonStringWithType(jsonContent); + io:println(book); +} +``` + +Make sure to handle possible errors that may arise during the file reading or JSON to anydata conversion process. The `check` keyword is utilized to handle these errors, but more sophisticated error handling can be implemented as per your requirements. + +## JSON to anydata representation + +The conversion of JSON data to subtype of anydata representation is a fundamental feature of the library. + +### JSON Object + +The JSON Object can be represented as a record value in Ballerina which facilitates a structured and type-safe approach to handling JSON data. + +Take for instance the following JSON Object snippet: +```json +{ + "author": "Robert C. Martin", + "books": [ + { + "name": "Clean Code", + "year": 2008 + }, + { + "name": "Clean Architecture", + "year": 2017 + } + ] +} +``` + +This JSON Object can be represented as a record value in Ballerina as follows: +```ballerina +type Author record { + string author; + Book[] books; +}; + +type Book record { + string name; + int year; +}; + +public function main() returns error? { + json jsonContent = { + "author": "Robert C. Martin", + "books": [ + { + "name": "Clean Code", + "year": 2008 + }, + { + "name": "Clean Architecture", + "year": 2017 + } + ] + }; + + Author author = check jsondata:fromJsonWithType(jsonContent); + io:println(author); +} +``` + +### JSON Array + +The JSON Array can be represented as an array/tuple values in Ballerina. + +```json +[ + { + "name": "Clean Code", + "year": 2008 + }, + { + "name": "Clean Architecture", + "year": 2017 + } +] +``` + +This JSON Array can be converted as an array/tuple in Ballerina as follows: +```ballerina +type Book record { + string name; + int year; +}; + +public function main() returns error? { + json jsonContent = [ + { + "name": "Clean Code", + "year": 2008 + }, + { + "name": "Clean Architecture", + "year": 2017 + } + ]; + + Book[] bookArr = check jsondata:fromJsonWithType(jsonContent); + io:println(bookArr); + + [Book, Book] bookTuple = check jsondata:fromJsonWithType(jsonContent); + io:println(bookTuple); +} +``` + +### Controlling the JSON to record conversion + +The library allows for selective conversion of JSON into records through the use of fields. This is beneficial when the JSON data contains elements that are not necessary to be transformed into record fields. + +```json +{ + "name": "Clean Code", + "author": "Robert C. Martin", + "year": 2008, + "publisher": "Prentice Hall" +} +``` + +The JSON data above contains `publisher` and `year` fields which are not required to be converted into a record field. + +```ballerina +type Book record {| + string name; + string author; +|}; + +public function main() returns error? { + json jsonContent = { + "name": "Clean Code", + "author": "Robert C. Martin", + "year": 2008, + "publisher": "Prentice Hall" + }; + + Book book = check jsondata:fromJsonWithType(jsonContent); + io:println(book); +} +``` + +However, if the rest field is utilized (or if the record type is defined as an open record), all elements in the JSON data will be transformed into record fields: + +```ballerina +type Book record { + string name; + string author; +} +``` + +In this instance, all other elements in the JSON data, such as `year` and `publisher` will be transformed into `string` type fields with the corresponding json object member as the key. + +This behavior extends to arrays as well. + +The process of projecting JSON data into a record supports various use cases, including the filtering out of unnecessary elements. This functionality is anticipated to be enhanced in the future to accommodate more complex scenarios, such as filtering values based on regular expressions, among others. diff --git a/ballerina/json_api.bal b/ballerina/json_api.bal index 861fd23..55c378e 100644 --- a/ballerina/json_api.bal +++ b/ballerina/json_api.bal @@ -51,3 +51,13 @@ public type Options record { # Represents the error type of the ballerina/data.jsondata module. This error type represents any error that can occur # during the execution of jsondata APIs. public type Error distinct error; + +# Defines the name of the JSON Object key. +# +# + value - The name of the JSON Object key. +public type NameConfig record {| + string value; +|}; + +# The annotation is used to overwrite the existing record field name. +public const annotation NameConfig Name on record field; diff --git a/ballerina/tests/from_json_string_test.bal b/ballerina/tests/from_json_string_test.bal index 1d0e002..96bc228 100644 --- a/ballerina/tests/from_json_string_test.bal +++ b/ballerina/tests/from_json_string_test.bal @@ -1015,3 +1015,15 @@ isolated function testFromJsonStringWithTypeNegative5() returns error? { test:assertTrue(y is error); test:assertEquals((y).message(), "array size is not compatible with the expected size"); } + +@test:Config +isolated function testDuplicateFieldInRecordTypeWithFromJsonStringWithType() returns error? { + string str = string `{ + "title": "Clean Code", + "author": "Robert C. Martin", + `; + + BookN|Error x = fromJsonStringWithType(str); + test:assertTrue(x is error); + test:assertEquals((x).message(), "duplicate field 'author'"); +} diff --git a/ballerina/tests/types.bal b/ballerina/tests/types.bal index 9425404..6d60df0 100644 --- a/ballerina/tests/types.bal +++ b/ballerina/tests/types.bal @@ -179,3 +179,12 @@ type Union int|float; type INTARR int[3]; type INTTUPLE [int, int, int, int...]; + +type BookN record { + string title; + @Name { + value: "author" + } + string name; + string author; +}; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java index b0ba079..728fa0b 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java @@ -24,4 +24,5 @@ public class Constants { static final String FROM_JSON_STRING_WITH_TYPE = "fromJsonStringWithType"; static final String FROM_JSON_WITH_TYPE = "fromJsonWithType"; + public static final String NAME = "Name"; } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataRecordFieldValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataRecordFieldValidator.java index 622752e..a598643 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataRecordFieldValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataRecordFieldValidator.java @@ -119,21 +119,11 @@ private boolean isFromJsonFunctionFromXmldata(ExpressionNode expressionNode) { private void validateExpectedType(TypeSymbol typeSymbol, SyntaxNodeAnalysisContext ctx) { typeSymbol.getLocation().ifPresent(location -> currentLocation = location); switch (typeSymbol.typeKind()) { - case UNION -> { - validateUnionType((UnionTypeSymbol) typeSymbol, typeSymbol.getLocation(), ctx); - } - case RECORD -> { - validateRecordType((RecordTypeSymbol) typeSymbol, ctx); - } - case ARRAY -> { - validateExpectedType(((ArrayTypeSymbol) typeSymbol).memberTypeDescriptor(), ctx); - } - case TUPLE -> { - validateTupleType((TupleTypeSymbol) typeSymbol, ctx); - } - case TYPE_REFERENCE -> { - validateExpectedType(((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor(), ctx); - } + case UNION -> validateUnionType((UnionTypeSymbol) typeSymbol, typeSymbol.getLocation(), ctx); + case RECORD -> validateRecordType((RecordTypeSymbol) typeSymbol, ctx); + case ARRAY -> validateExpectedType(((ArrayTypeSymbol) typeSymbol).memberTypeDescriptor(), ctx); + case TUPLE -> validateTupleType((TupleTypeSymbol) typeSymbol, ctx); + case TYPE_REFERENCE -> validateExpectedType(((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor(), ctx); } } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java index 0153573..d705b2e 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java @@ -22,6 +22,7 @@ import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.types.ArrayType; +import io.ballerina.runtime.api.types.Field; import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.TupleType; @@ -40,6 +41,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Optional; /** @@ -52,7 +54,7 @@ public class JsonCreator { static BMap initRootMapValue(Type expectedType) { switch (expectedType.getTag()) { case TypeTags.RECORD_TYPE_TAG: - return ValueCreator.createRecordValue((RecordType) expectedType); + return ValueCreator.createRecordValue(expectedType.getPackage(), expectedType.getName()); case TypeTags.MAP_TAG: return ValueCreator.createMapValue((MapType) expectedType); case TypeTags.JSON_TAG: @@ -95,7 +97,7 @@ static Optional> initNewMapValue(JsonParser.StateMachine s switch (currentType.getTag()) { case TypeTags.RECORD_TYPE_TAG: RecordType recordType = (RecordType) currentType; - nextMapValue = ValueCreator.createRecordValue(recordType); + nextMapValue = ValueCreator.createRecordValue(expType.getPackage(), expType.getName()); sm.fieldHierarchy.push(new HashMap<>(recordType.getFields())); sm.restType.push(recordType.getRestFieldType()); break; @@ -240,4 +242,38 @@ static void validateListSize(int currentIndex, Type expType) { throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); } } + + static Map getAllFieldsInRecord(RecordType recordType) { + BMap annotations = recordType.getAnnotations(); + Map modifiedNames = new HashMap<>(); + for (BString annotationKey : annotations.getKeys()) { + String keyStr = annotationKey.getValue(); + if (!keyStr.contains(Constants.FIELD)) { + continue; + } + String fieldName = keyStr.split(Constants.FIELD_REGEX)[1]; + Map fieldAnnotation = (Map) annotations.get(annotationKey); + modifiedNames.put(fieldName, getModifiedName(fieldAnnotation, fieldName)); + } + + Map fields = new HashMap<>(); + Map recordFields = recordType.getFields(); + for (String key : recordFields.keySet()) { + String fieldName = modifiedNames.getOrDefault(key, key); + if (fields.containsKey(fieldName)) { + throw DiagnosticLog.error(DiagnosticErrorCode.DUPLICATE_FIELD, fieldName); + } + fields.put(fieldName, recordFields.get(key)); + } + return fields; + } + + static String getModifiedName(Map fieldAnnotation, String fieldName) { + for (BString key : fieldAnnotation.keySet()) { + if (key.getValue().endsWith(Constants.NAME)) { + return ((Map) fieldAnnotation.get(key)).get(Constants.VALUE).toString(); + } + } + return fieldName; + } } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java index d59b59b..3c6233f 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java @@ -191,7 +191,7 @@ public Object execute(Reader reader, Type type) throws BError { case TypeTags.RECORD_TYPE_TAG: RecordType recordType = (RecordType) type; expectedTypes.push(recordType); - fieldHierarchy.push(new HashMap<>(recordType.getFields())); + fieldHierarchy.push(JsonCreator.getAllFieldsInRecord(recordType)); restType.push(recordType.getRestFieldType()); break; case TypeTags.ARRAY_TAG: @@ -532,7 +532,6 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J } return state; } - } /** @@ -561,7 +560,6 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J sm.index = i + 1; return state; } - } /** @@ -616,7 +614,6 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J } return state; } - } private String value() { @@ -627,7 +624,6 @@ private String value() { private String processFieldName() { String value = this.value(); - this.fieldNames.push(value); return value; } @@ -651,12 +647,14 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J if (sm.currentField == null) { fieldType = sm.restType.peek(); } else { + jsonFieldName = sm.currentField.getFieldName(); fieldType = sm.currentField.getFieldType(); } sm.expectedTypes.push(fieldType); } else if (sm.expectedTypes.peek() == null) { sm.expectedTypes.push(null); } + sm.fieldNames.push(jsonFieldName); state = END_FIELD_NAME_STATE; } else if (ch == REV_SOL) { state = FIELD_NAME_ESC_CHAR_PROCESSING_STATE; @@ -672,7 +670,6 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J sm.index = i + 1; return state; } - } /** @@ -700,7 +697,6 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J sm.index = i + 1; return state; } - } /** @@ -751,7 +747,6 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J } return state; } - } /** @@ -802,7 +797,6 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J sm.index = i + 1; return state; } - } /** @@ -941,7 +935,6 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J sm.index = i + 1; return state; } - } /** @@ -1014,7 +1007,6 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J StringUtils.fromString(sm.value()), sm.expectedTypes.peek()); return state; } - } /** diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java index ec587db..c5c1b5c 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java @@ -87,7 +87,8 @@ public Object traverseJson(Object json, Type type) { RecordType recordType = (RecordType) referredType; fieldHierarchy.push(new HashMap<>(recordType.getFields())); restType.push(recordType.getRestFieldType()); - return traverseMapJsonOrArrayJson(json, ValueCreator.createRecordValue(recordType), referredType); + return traverseMapJsonOrArrayJson(json, + ValueCreator.createRecordValue(type.getPackage(), type.getName()), referredType); case TypeTags.ARRAY_TAG: rootArray = referredType; return traverseMapJsonOrArrayJson(json, ValueCreator.createArrayValue((ArrayType) referredType), diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/Constants.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/Constants.java index 5bdd873..244bde9 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/Constants.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/Constants.java @@ -21,8 +21,14 @@ import io.ballerina.runtime.api.PredefinedTypes; import io.ballerina.runtime.api.creators.TypeCreator; import io.ballerina.runtime.api.types.MapType; +import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.values.BString; public class Constants { public static final MapType JSON_MAP_TYPE = TypeCreator.createMapType(PredefinedTypes.TYPE_JSON); public static final MapType ANYDATA_MAP_TYPE = TypeCreator.createMapType(PredefinedTypes.TYPE_ANYDATA); + public static final BString VALUE = StringUtils.fromString("value"); + public static final String FIELD = "$field$."; + public static final String FIELD_REGEX = "\\$field\\$\\."; + public static final String NAME = "Name"; } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticErrorCode.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticErrorCode.java index 666e9a3..c060f52 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticErrorCode.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticErrorCode.java @@ -33,7 +33,8 @@ public enum DiagnosticErrorCode { INVALID_TYPE("JSON_ERROR_006", "invalid.type"), INCOMPATIBLE_VALUE_FOR_FIELD("JSON_ERROR_007", "incompatible.value.for.field"), REQUIRED_FIELD_NOT_PRESENT("JSON_ERROR_008", "required.field.not.present"), - INVALID_TYPE_FOR_FIELD("JSON_ERROR_009", "invalid.type.for.field"); + INVALID_TYPE_FOR_FIELD("JSON_ERROR_009", "invalid.type.for.field"), + DUPLICATE_FIELD("JSON_ERROR_010", "duplicate.field"); String diagnosticId; String messageKey; diff --git a/native/src/main/resources/error.properties b/native/src/main/resources/error.properties index 0c66bff..aebfabb 100644 --- a/native/src/main/resources/error.properties +++ b/native/src/main/resources/error.properties @@ -46,3 +46,6 @@ error.required.field.not.present=\ error.invalid.type.for.field=\ invalid type for field ''{0}'' + +error.duplicate.field=\ + duplicate field ''{0}'' From 695f718467ad78d11256987274326ea08130a9b8 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Fri, 16 Feb 2024 14:56:24 +0530 Subject: [PATCH 16/27] Address review suggestions --- ballerina/Ballerina.toml | 1 - build-config/resources/Ballerina.toml | 1 - .../sample_package_3/sample.bal | 2 +- .../sample_package_4/sample.bal | 2 +- .../src/test/resources/testng.xml | 2 +- .../compiler/JsondataCodeAnalyzer.java | 11 ++-- .../compiler/JsondataCompilerPlugin.java | 8 +-- .../compiler/JsondataDiagnosticCodes.java | 2 - ...idator.java => JsondataTypeValidator.java} | 5 +- .../stdlib/data/jsondata/FromString.java | 17 ++--- .../jsondata/io/DataReaderThreadPool.java | 11 ++-- .../data/jsondata/json/JsonCreator.java | 18 ++++-- .../stdlib/data/jsondata/json/JsonParser.java | 62 +++++-------------- .../data/jsondata/json/JsonTraverse.java | 13 ++-- .../jsondata/utils/DiagnosticErrorCode.java | 3 +- native/src/main/resources/error.properties | 3 + 16 files changed, 62 insertions(+), 99 deletions(-) rename compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/{JsondataRecordFieldValidator.java => JsondataTypeValidator.java} (97%) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 53dacfc..c76ee48 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -5,7 +5,6 @@ version = "0.1.0" authors = ["Ballerina"] keywords = ["json"] repository = "https://github.com/ballerina-platform/module-ballerina.jsondata" -#icon = "icon.png" license = ["Apache-2.0"] distribution = "2201.8.4" diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index ad278d2..c214868 100644 --- a/build-config/resources/Ballerina.toml +++ b/build-config/resources/Ballerina.toml @@ -5,7 +5,6 @@ version = "@toml.version@" authors = ["Ballerina"] keywords = ["json"] repository = "https://github.com/ballerina-platform/module-ballerina.jsondata" -#icon = "icon.png" license = ["Apache-2.0"] distribution = "2201.8.4" diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/sample.bal index 845f93e..b82566e 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/sample.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/sample.bal @@ -19,5 +19,5 @@ public function main() returns error? { "country": "USA" } }`; - Person val = check jsondata:fromJsonStringWithType(str); + Person _ = check jsondata:fromJsonStringWithType(str); } diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/sample.bal index b5ab36c..c7d5d0e 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/sample.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/sample.bal @@ -17,4 +17,4 @@ string str = string `{ "country": "USA" } }`; -Person val = check jsondata:fromJsonStringWithType(str); +Person _ = check jsondata:fromJsonStringWithType(str); diff --git a/compiler-plugin-test/src/test/resources/testng.xml b/compiler-plugin-test/src/test/resources/testng.xml index 4a44310..ea625e7 100644 --- a/compiler-plugin-test/src/test/resources/testng.xml +++ b/compiler-plugin-test/src/test/resources/testng.xml @@ -21,7 +21,7 @@ - + diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCodeAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCodeAnalyzer.java index 221200b..959445d 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCodeAnalyzer.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCodeAnalyzer.java @@ -28,10 +28,9 @@ * Jsondata Code Analyzer. */ public class JsondataCodeAnalyzer extends CodeAnalyzer { - - @Override - public void init(CodeAnalysisContext codeAnalysisContext) { - codeAnalysisContext.addSyntaxNodeAnalysisTask(new JsondataRecordFieldValidator(), - List.of(SyntaxKind.MODULE_PART)); - } + @Override + public void init(CodeAnalysisContext codeAnalysisContext) { + codeAnalysisContext.addSyntaxNodeAnalysisTask(new JsondataTypeValidator(), + List.of(SyntaxKind.MODULE_PART)); + } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCompilerPlugin.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCompilerPlugin.java index 6ec85e1..e7238f9 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCompilerPlugin.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCompilerPlugin.java @@ -26,8 +26,8 @@ */ public class JsondataCompilerPlugin extends CompilerPlugin { - @Override - public void init(CompilerPluginContext compilerPluginContext) { - compilerPluginContext.addCodeAnalyzer(new JsondataCodeAnalyzer()); - } + @Override + public void init(CompilerPluginContext compilerPluginContext) { + compilerPluginContext.addCodeAnalyzer(new JsondataCodeAnalyzer()); + } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataDiagnosticCodes.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataDiagnosticCodes.java index 3185a93..bb8c523 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataDiagnosticCodes.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataDiagnosticCodes.java @@ -16,7 +16,6 @@ * under the License. */ - package io.ballerina.stdlib.data.jsondata.compiler; import io.ballerina.tools.diagnostics.DiagnosticSeverity; @@ -49,5 +48,4 @@ public String getMessage() { public DiagnosticSeverity getSeverity() { return severity; } - } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataRecordFieldValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java similarity index 97% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataRecordFieldValidator.java rename to compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java index a598643..f3939e6 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataRecordFieldValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java @@ -56,7 +56,7 @@ /** * Jsondata Record Field Validator. */ -public class JsondataRecordFieldValidator implements AnalysisTask { +public class JsondataTypeValidator implements AnalysisTask { private SemanticModel semanticModel; private final HashMap allDiagnosticInfo = new HashMap<>(); @@ -201,7 +201,6 @@ private void processModuleVariableDeclarationNode(ModuleVariableDeclarationNode if (symbol.isEmpty()) { return; } - TypeSymbol typeSymbol = ((VariableSymbol) symbol.get()).typeDescriptor(); - validateExpectedType(typeSymbol, ctx); + validateExpectedType(((VariableSymbol) symbol.get()).typeDescriptor(), ctx); } } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java index 0166401..3d8fcb1 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java @@ -20,18 +20,18 @@ import io.ballerina.runtime.api.PredefinedTypes; import io.ballerina.runtime.api.TypeTags; -import io.ballerina.runtime.api.creators.ErrorCreator; import io.ballerina.runtime.api.creators.TypeCreator; import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.types.ReferenceType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.UnionType; -import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BDecimal; import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.api.values.BTypedesc; +import io.ballerina.stdlib.data.jsondata.utils.DiagnosticErrorCode; +import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; import java.util.ArrayList; import java.util.Comparator; @@ -143,14 +143,10 @@ private static Object stringToUnion(BString string, UnionType expType) throws Nu List memberTypes = expType.getMemberTypes(); memberTypes.sort(Comparator.comparingInt(t -> TYPE_PRIORITY_ORDER.getOrDefault( TypeUtils.getReferredType(t).getTag(), Integer.MAX_VALUE))); - boolean isStringExpType = false; for (Type memberType : memberTypes) { try { Object result = fromStringWithType(string, memberType); - if (result instanceof BString) { - isStringExpType = true; - continue; - } else if (result instanceof BError) { + if (result instanceof BError) { continue; } return result; @@ -158,10 +154,6 @@ private static Object stringToUnion(BString string, UnionType expType) throws Nu // Skip } } - - if (isStringExpType) { - return string; - } return returnError(string.getValue(), expType.toString()); } @@ -183,6 +175,7 @@ private static boolean hasFloatOrDecimalLiteralSuffix(String value) { } private static BError returnError(String string, String expType) { - return ErrorCreator.createError(StringUtils.fromString("Cannot convert to the exptype")); + return DiagnosticLog.error(DiagnosticErrorCode.CANNOT_CONVERT_TO_EXPECTED_TYPE, + PredefinedTypes.TYPE_STRING.getName(), string, expType); } } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderThreadPool.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderThreadPool.java index 2cab568..3418f68 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderThreadPool.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderThreadPool.java @@ -31,9 +31,12 @@ public class DataReaderThreadPool { // TODO : Make this configurable, in Ballerina Library. - public static final ExecutorService EXECUTOR_SERVICE = new ThreadPoolExecutor(0, 50, 60L, TimeUnit.SECONDS, - new SynchronousQueue<>(), - new DataThreadFactory()); + private static final int CORE_POOL_SIZE = 0; + private static final int MAX_POOL_SIZE = 50; + private static final long KEEP_ALIVE_TIME = 60L; + private static final String THREAD_NAME = "bal-data-jsondata-thread"; + public static final ExecutorService EXECUTOR_SERVICE = new ThreadPoolExecutor(CORE_POOL_SIZE, + MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, new SynchronousQueue<>(), new DataThreadFactory()); /** * Thread factory for data reader. @@ -43,7 +46,7 @@ static class DataThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable runnable) { Thread ballerinaData = new Thread(runnable); - ballerinaData.setName("bal-data-jsondata-thread"); + ballerinaData.setName(THREAD_NAME); return ballerinaData; } } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java index d705b2e..aac8ce1 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java @@ -128,10 +128,19 @@ static Optional> initNewMapValue(JsonParser.StateMachine s return Optional.of(nextMapValue); } + static void updateNextMapValue(JsonParser.StateMachine sm) { + Optional> nextMap = initNewMapValue(sm); + if (nextMap.isPresent()) { + sm.currentJsonNode = nextMap.get(); + } else { + // This will restrict from checking the fieldHierarchy. + sm.jsonFieldDepth++; + } + } + static Optional initNewArrayValue(JsonParser.StateMachine sm) { sm.parserContexts.push(JsonParser.StateMachine.ParserContext.ARRAY); - Type expType = sm.expectedTypes.peek(); - if (expType == null) { + if (sm.expectedTypes.peek() == null) { return Optional.empty(); } @@ -141,7 +150,7 @@ static Optional initNewArrayValue(JsonParser.StateMachine sm) { return Optional.ofNullable(nextArrValue); } - sm.nodesStack.push(sm.currentJsonNode); + sm.nodesStack.push(currentJsonNode); return Optional.ofNullable(nextArrValue); } @@ -220,9 +229,8 @@ static Type getMemberType(Type expectedType, int index) { return tupleType.getRestType(); } return tupleTypes.get(index); - } else { - return expectedType; } + return expectedType; } static void validateListSize(int currentIndex, Type expType) { diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java index 3c6233f..94bd731 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java @@ -19,6 +19,7 @@ package io.ballerina.stdlib.data.jsondata.json; import io.ballerina.runtime.api.TypeTags; +import io.ballerina.runtime.api.creators.ErrorCreator; import io.ballerina.runtime.api.flags.SymbolFlags; import io.ballerina.runtime.api.types.ArrayType; import io.ballerina.runtime.api.types.Field; @@ -30,7 +31,6 @@ import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BError; -import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; import io.ballerina.stdlib.data.jsondata.utils.DiagnosticErrorCode; import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; @@ -238,6 +238,10 @@ public Object execute(Reader reader, Type type) throws BError { currentState = currentState.transition(this, buff, this.index, count); } } + currentState = currentState.transition(this, new char[] { EOF }, 0, 1); + if (currentState != DOC_END_STATE) { + throw ErrorCreator.createError(StringUtils.fromString("invalid JSON document")); + } return currentJsonNode; } catch (IOException e) { throw DiagnosticLog.error(DiagnosticErrorCode.JSON_READER_FAILURE, e.getMessage()); @@ -247,7 +251,6 @@ public Object execute(Reader reader, Type type) throws BError { } private boolean isSupportedUnionType(UnionType type) { - boolean isContainUnsupportedMember = false; for (Type memberType : type.getMemberTypes()) { switch (memberType.getTag()) { case TypeTags.RECORD_TYPE_TAG: @@ -255,14 +258,12 @@ private boolean isSupportedUnionType(UnionType type) { case TypeTags.MAP_TAG: case TypeTags.JSON_TAG: case TypeTags.ANYDATA_TAG: - isContainUnsupportedMember = true; - break; + return false; case TypeTags.UNION_TAG: - isContainUnsupportedMember = isSupportedUnionType(type); - break; + return !isSupportedUnionType(type); } } - return !isContainUnsupportedMember; + return true; } private void append(char ch) { @@ -355,7 +356,6 @@ public enum ParserContext { ARRAY } - /** * A specific state in the JSON parsing state machine. */ @@ -371,7 +371,6 @@ interface State { * @return the new resulting state */ State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException; - } /** @@ -499,13 +498,7 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J // Get member type of the array and set as expected type. sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), sm.arrayIndexes.peek())); - Optional> nextMap = JsonCreator.initNewMapValue(sm); - if (nextMap.isPresent()) { - sm.currentJsonNode = nextMap.get(); - } else { - // This will restrict from checking the fieldHierarchy. - sm.jsonFieldDepth++; - } + JsonCreator.updateNextMapValue(sm); state = FIRST_FIELD_READY_STATE; } else if (ch == '[') { // Get member type of the array and set as expected type. @@ -585,13 +578,7 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J } else if (ch == '{') { sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), sm.arrayIndexes.peek())); - Optional> nextMap = JsonCreator.initNewMapValue(sm); - if (nextMap.isPresent()) { - sm.currentJsonNode = nextMap.get(); - } else { - // This will restrict from checking the fieldHierarchy. - sm.jsonFieldDepth++; - } + JsonCreator.updateNextMapValue(sm); state = FIRST_FIELD_READY_STATE; } else if (ch == '[') { sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), @@ -718,13 +705,7 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J state = STRING_FIELD_VALUE_STATE; sm.currentQuoteChar = ch; } else if (ch == '{') { - Optional> nextMap = JsonCreator.initNewMapValue(sm); - if (nextMap.isPresent()) { - sm.currentJsonNode = nextMap.get(); - } else { - // This will restrict from checking the fieldHierarchy. - sm.jsonFieldDepth++; - } + JsonCreator.updateNextMapValue(sm); state = FIRST_FIELD_READY_STATE; } else if (ch == '[') { sm.arrayIndexes.push(0); @@ -843,14 +824,8 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J ch = buff[i]; sm.processLocation(ch); if (ch == '{') { + JsonCreator.updateNextMapValue(sm); state = FIRST_FIELD_READY_STATE; - Optional> nextMap = JsonCreator.initNewMapValue(sm); - if (nextMap.isPresent()) { - sm.currentJsonNode = nextMap.get(); - } else { - // This will restrict from checking the fieldHierarchy. - sm.jsonFieldDepth++; - } } else if (ch == '[') { state = FIRST_ARRAY_ELEMENT_READY_STATE; Optional nextArray = JsonCreator.initNewArrayValue(sm); @@ -896,14 +871,8 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J ch = buff[i]; sm.processLocation(ch); if (ch == '{') { + JsonCreator.updateNextMapValue(sm); state = FIRST_FIELD_READY_STATE; - Optional> nextMap = JsonCreator.initNewMapValue(sm); - if (nextMap.isPresent()) { - sm.currentJsonNode = nextMap.get(); - } else { - // This will restrict from checking the fieldHierarchy. - sm.jsonFieldDepth++; - } } else if (ch == '[') { state = FIRST_ARRAY_ELEMENT_READY_STATE; Optional nextArray = JsonCreator.initNewArrayValue(sm); @@ -975,7 +944,6 @@ private void processValue() { if (expType == null) { return; } - currentJsonNode = JsonCreator.convertAndUpdateCurrentJsonNode(this, value, expType); } @@ -985,7 +953,7 @@ private void processValue() { private static class NonStringValueState implements State { @Override - public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + public State transition(StateMachine sm, char[] buff, int i, int count) { State state = null; char ch; for (; i < count; i++) { @@ -1003,8 +971,6 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J break; } sm.index = i + 1; - sm.currentJsonNode = JsonCreator.convertAndUpdateCurrentJsonNode(sm, - StringUtils.fromString(sm.value()), sm.expectedTypes.peek()); return state; } } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java index c5c1b5c..b43cb46 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java @@ -227,8 +227,7 @@ private Object traverseArrayValue(Object json, Object currentJsonNode) { private void traverseArrayMembers(long length, BArray array, Type elementType, Object currentJsonNode) { for (int i = 0; i < length; i++) { - Object jsonMember = array.get(i); - ((BArray) currentJsonNode).add(i, traverseJson(jsonMember, elementType)); + ((BArray) currentJsonNode).add(i, traverseJson(array.get(i), elementType)); } } @@ -256,16 +255,12 @@ private void addRestField(Type restFieldType, BString key, Object jsonMember, Ob } private boolean checkTypeCompatibility(Type type, Object json) { - if ((json instanceof BString && type.getTag() == TypeTags.STRING_TAG) + return ((json == null && type.getTag() == TypeTags.NULL_TAG) + || (json instanceof BString && type.getTag() == TypeTags.STRING_TAG) || (json instanceof Long && type.getTag() == TypeTags.INT_TAG) || (json instanceof Double && (type.getTag() == TypeTags.FLOAT_TAG || type.getTag() == TypeTags.DECIMAL_TAG)) - || (Boolean.class.isInstance(json) && type.getTag() == TypeTags.BOOLEAN_TAG) - || (json == null && type.getTag() == TypeTags.NULL_TAG)) { - return true; - } else { - return false; - } + || (Boolean.class.isInstance(json) && type.getTag() == TypeTags.BOOLEAN_TAG)); } private void checkOptionalFieldsAndLogError(Map currentField) { diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticErrorCode.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticErrorCode.java index c060f52..113d52c 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticErrorCode.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticErrorCode.java @@ -34,7 +34,8 @@ public enum DiagnosticErrorCode { INCOMPATIBLE_VALUE_FOR_FIELD("JSON_ERROR_007", "incompatible.value.for.field"), REQUIRED_FIELD_NOT_PRESENT("JSON_ERROR_008", "required.field.not.present"), INVALID_TYPE_FOR_FIELD("JSON_ERROR_009", "invalid.type.for.field"), - DUPLICATE_FIELD("JSON_ERROR_010", "duplicate.field"); + DUPLICATE_FIELD("JSON_ERROR_010", "duplicate.field"), + CANNOT_CONVERT_TO_EXPECTED_TYPE("JSON_ERROR_011", "cannot.convert.to.expected.type"); String diagnosticId; String messageKey; diff --git a/native/src/main/resources/error.properties b/native/src/main/resources/error.properties index aebfabb..62d51c4 100644 --- a/native/src/main/resources/error.properties +++ b/native/src/main/resources/error.properties @@ -49,3 +49,6 @@ error.invalid.type.for.field=\ error.duplicate.field=\ duplicate field ''{0}'' + +error.cannot.convert.to.expected.type=\ + ''{0}'' value ''{1}'' cannot be converted to ''{2}'' From 832a74cd0bf648f9d3911f3babaafb2742e92048 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:30:51 +0530 Subject: [PATCH 17/27] Log duplicate field error and address suggestions --- ballerina/init.bal | 2 +- ballerina/json_api.bal | 2 +- ballerina/tests/from_json_string_test.bal | 33 ++++++- ballerina/tests/from_json_test.bal | 32 ++++++- ballerina/tests/stream_large_file_test.bal | 66 ++++++------- ballerina/tests/to_json_test.bal | 2 +- ballerina/tests/types.bal | 14 ++- .../jsondata/compiler/CompilerPluginTest.java | 26 +++++ .../sample_package_5/Ballerina.toml | 4 + .../sample_package_5/sample.bal | 9 ++ .../sample_package_6/Ballerina.toml | 4 + .../sample_package_6/sample.bal | 22 +++++ .../data/jsondata/compiler/Constants.java | 2 + .../compiler/JsondataCodeAnalyzer.java | 2 + .../compiler/JsondataCompilerPlugin.java | 2 + .../compiler/JsondataDiagnosticCodes.java | 10 +- .../compiler/JsondataTypeValidator.java | 94 ++++++++++++++++--- .../stdlib/data/jsondata/FromString.java | 46 ++++----- .../jsondata/io/DataReaderThreadPool.java | 2 + .../data/jsondata/json/JsonCreator.java | 4 + .../stdlib/data/jsondata/json/JsonParser.java | 87 ++++++++--------- .../data/jsondata/json/JsonTraverse.java | 12 ++- .../stdlib/data/jsondata/utils/Constants.java | 5 + .../data/jsondata/utils/DiagnosticLog.java | 2 +- 24 files changed, 360 insertions(+), 124 deletions(-) create mode 100644 compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_5/Ballerina.toml create mode 100644 compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_5/sample.bal create mode 100644 compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_6/Ballerina.toml create mode 100644 compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_6/sample.bal diff --git a/ballerina/init.bal b/ballerina/init.bal index 36362f4..5e3c133 100644 --- a/ballerina/init.bal +++ b/ballerina/init.bal @@ -5,7 +5,7 @@ // in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// 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 diff --git a/ballerina/json_api.bal b/ballerina/json_api.bal index 55c378e..cf667ab 100644 --- a/ballerina/json_api.bal +++ b/ballerina/json_api.bal @@ -5,7 +5,7 @@ // in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// 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 diff --git a/ballerina/tests/from_json_string_test.bal b/ballerina/tests/from_json_string_test.bal index 96bc228..00789bc 100644 --- a/ballerina/tests/from_json_string_test.bal +++ b/ballerina/tests/from_json_string_test.bal @@ -5,7 +5,7 @@ // in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// 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 @@ -934,6 +934,37 @@ isolated function testArrayOrTupleCaseForFromJsonStringWithType() returns error? test:assertEquals(val6, [{val: [[1, 2], [2, 3]]}]); } +@test:Config +function testDuplicateKeyInTheStringSource() returns error? { + string str = string `{ + "id": 1, + "name": "Anne", + "id": 2 + }`; + + record { + int id; + string name; + } employee = check fromJsonStringWithType(str); + test:assertEquals(employee.length(), 2); + test:assertEquals(employee.id, 2); + test:assertEquals(employee.name, "Anne"); +} + +@test:Config +function testNameAnnotationWithFromJsonStringWithType() returns error? { + string jsonStr = string `{ + "id": 1, + "title-name": "Harry Potter", + "author-name": "J.K. Rowling" + }`; + + Book2 book = check fromJsonStringWithType(jsonStr); + test:assertEquals(book.id, 1); + test:assertEquals(book.title, "Harry Potter"); + test:assertEquals(book.author, "J.K. Rowling"); +} + // Negative tests for fromJsonStringWithType() function. @test:Config diff --git a/ballerina/tests/from_json_test.bal b/ballerina/tests/from_json_test.bal index 41b5b88..3c138aa 100644 --- a/ballerina/tests/from_json_test.bal +++ b/ballerina/tests/from_json_test.bal @@ -5,7 +5,7 @@ // in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// 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 @@ -37,6 +37,10 @@ isolated function testJsonToBasicTypes() returns error? { () val6 = check fromJsonWithType(null); test:assertEquals(val6, null); + + decimal dVal = 1.5; + decimal val7 = check fromJsonWithType(dVal); + test:assertEquals(val7, 1.5d); } @test:Config @@ -920,6 +924,20 @@ isolated function testArrayOrTupleCaseForFromJsonWithType() returns error? { test:assertEquals(val6, [{val: [[1, 2], [2, 3]]}]); } +@test:Config +function testNameAnnotationWithFromJsonWithType() returns error? { + json jsonContent = { + "id": 1, + "title-name": "Harry Potter", + "author-name": "J.K. Rowling" + }; + + Book2 book = check fromJsonWithType(jsonContent); + test:assertEquals(book.id, 1); + test:assertEquals(book.title, "Harry Potter"); + test:assertEquals(book.author, "J.K. Rowling"); +} + // Negative tests for fromJsonWithType() function. @test:Config @@ -1018,3 +1036,15 @@ isolated function testFromJsonWithTypeNegative6() { test:assertTrue(x is Error); test:assertEquals((x).message(), "incompatible value '4' for type 'int' in field 'house'"); } + +@test:Config +isolated function testDuplicateFieldInRecordTypeWithFromJsonWithType() returns error? { + json jsonContent = string `{ + "title": "Clean Code", + "author": "Robert C. Martin", + `; + + BookN|Error x = fromJsonWithType(jsonContent); + test:assertTrue(x is error); + test:assertEquals((x).message(), "duplicate field 'author'"); +} diff --git a/ballerina/tests/stream_large_file_test.bal b/ballerina/tests/stream_large_file_test.bal index c05dd66..52a96bc 100644 --- a/ballerina/tests/stream_large_file_test.bal +++ b/ballerina/tests/stream_large_file_test.bal @@ -5,7 +5,7 @@ // in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// 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 @@ -46,6 +46,38 @@ type CustomerR1 record {| string product; |}; +@test:BeforeSuite +function createLargeFile() returns error? { + io:WritableByteChannel wbc = check io:openWritableFile(LARGE_JSON_FILE); + string begin = string `{`; + string end = "}\n"; + _ = check wbc.write(begin.toBytes(), 0); + + _ = check wbc.write(string `"employees": + [ + `.toBytes(), 0); + _ = check wbc.write(createEmployee(0).toString().toBytes(), 0); + foreach int i in 1 ... 1000 { + _ = check wbc.write(",\n ".toBytes(), 0); + _ = check wbc.write(createEmployee(i).toString().toBytes(), 0); + } + _ = check wbc.write("\n ],\n".toBytes(), 0); + + _ = check wbc.write(string `"customers": + [ + `.toBytes(), 0); + _ = check wbc.write(createCustomer(0).toString().toBytes(), 0); + foreach int i in 1...1000 { + _ = check wbc.write(",\n ".toBytes(), 0); + _ = check wbc.write(createCustomer(i).toString().toBytes(), 0); + } + _ = check wbc.write("\n ]\n".toBytes(), 0); + + + _ = check wbc.write(end.toBytes(), 0); + _ = check wbc.close(); +} + @test:Config function testLargeFileStream() returns error? { stream dataStream = check io:fileReadBlocksAsStream(LARGE_JSON_FILE); @@ -105,38 +137,6 @@ function testLargeFileStreamWithProjection() returns error? { test:assertEquals(company.customers[4].product, "CHOREO"); } -@test:BeforeSuite -function createLargeFile() returns error? { - io:WritableByteChannel wbc = check io:openWritableFile(LARGE_JSON_FILE); - string begin = string `{`; - string end = "}\n"; - _ = check wbc.write(begin.toBytes(), 0); - - _ = check wbc.write(string `"employees": - [ - `.toBytes(), 0); - _ = check wbc.write(createEmployee(0).toString().toBytes(), 0); - foreach int i in 1 ... 1000 { - _ = check wbc.write(",\n ".toBytes(), 0); - _ = check wbc.write(createEmployee(i).toString().toBytes(), 0); - } - _ = check wbc.write("\n ],\n".toBytes(), 0); - - _ = check wbc.write(string `"customers": - [ - `.toBytes(), 0); - _ = check wbc.write(createCustomer(0).toString().toBytes(), 0); - foreach int i in 1...1000 { - _ = check wbc.write(",\n ".toBytes(), 0); - _ = check wbc.write(createCustomer(i).toString().toBytes(), 0); - } - _ = check wbc.write("\n ]\n".toBytes(), 0); - - - _ = check wbc.write(end.toBytes(), 0); - _ = check wbc.close(); -} - function createEmployee(int id) returns EmployeeR1 { string position = POSTIONS.keys()[id % POSTIONS.keys().length()]; return { diff --git a/ballerina/tests/to_json_test.bal b/ballerina/tests/to_json_test.bal index c18be05..92154ff 100644 --- a/ballerina/tests/to_json_test.bal +++ b/ballerina/tests/to_json_test.bal @@ -5,7 +5,7 @@ // in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// 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 diff --git a/ballerina/tests/types.bal b/ballerina/tests/types.bal index 6d60df0..d0cc4d5 100644 --- a/ballerina/tests/types.bal +++ b/ballerina/tests/types.bal @@ -5,7 +5,7 @@ // in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// 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 @@ -66,6 +66,18 @@ type Book record {| float...; |}; +type Book2 record { + int id; + @Name { + value: "title-name" + } + string title; + @Name { + value: "author-name" + } + string author; +}; + type School record {| string name; int number; diff --git a/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTest.java b/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTest.java index 6fdf111..acd1789 100644 --- a/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTest.java +++ b/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTest.java @@ -80,4 +80,30 @@ public void testInvalidRecordFieldType2() { Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), UNSUPPORTED_UNION_TYPE); Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), UNSUPPORTED_UNION_TYPE); } + + @Test + public void testDuplicateField1() { + DiagnosticResult diagnosticResult = + CompilerPluginTestUtils.loadPackage("sample_package_5").getCompilation().diagnosticResult(); + List errorDiagnosticsList = diagnosticResult.diagnostics().stream() + .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) + .collect(Collectors.toList()); + Assert.assertEquals(errorDiagnosticsList.size(), 1); + Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), + "invalid field: duplicate field found"); + } + + @Test + public void testDuplicateField2() { + DiagnosticResult diagnosticResult = + CompilerPluginTestUtils.loadPackage("sample_package_6").getCompilation().diagnosticResult(); + List errorDiagnosticsList = diagnosticResult.diagnostics().stream() + .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) + .collect(Collectors.toList()); + Assert.assertEquals(errorDiagnosticsList.size(), 2); + Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), + "invalid field: duplicate field found"); + Assert.assertEquals(errorDiagnosticsList.get(1).diagnosticInfo().messageFormat(), + "invalid field: duplicate field found"); + } } diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_5/Ballerina.toml b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_5/Ballerina.toml new file mode 100644 index 0000000..7f7a2f8 --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_5/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "jsondata_test" +name = "sample_5" +version = "0.1.0" diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_5/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_5/sample.bal new file mode 100644 index 0000000..2e1eb62 --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_5/sample.bal @@ -0,0 +1,9 @@ +import ballerina/data.jsondata; + +type Data record { + @jsondata:Name { + value: "B" + } + string A; + string B; +}; diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_6/Ballerina.toml b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_6/Ballerina.toml new file mode 100644 index 0000000..9ab9d0d --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_6/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "jsondata_test" +name = "sample_6" +version = "0.1.0" diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_6/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_6/sample.bal new file mode 100644 index 0000000..29fea39 --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_6/sample.bal @@ -0,0 +1,22 @@ +import ballerina/data.jsondata; + +public function main() returns error? { + record { + @jsondata:Name { + value: "B" + } + string A; + string B; + } _ = check jsondata:fromJsonWithType({ + "A": "Hello", + "B": "World" + }); + + record { + @jsondata:Name { + value: "B" + } + string A; + string B; + } _ = {A: "Hello", B: "World"}; +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java index 728fa0b..6338faa 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java @@ -20,6 +20,8 @@ /** * Constants for Jsondata's compiler plugin. + * + * @since 0.1.0 */ public class Constants { static final String FROM_JSON_STRING_WITH_TYPE = "fromJsonStringWithType"; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCodeAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCodeAnalyzer.java index 959445d..926d270 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCodeAnalyzer.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCodeAnalyzer.java @@ -26,6 +26,8 @@ /** * Jsondata Code Analyzer. + * + * @since 0.1.0 */ public class JsondataCodeAnalyzer extends CodeAnalyzer { @Override diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCompilerPlugin.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCompilerPlugin.java index e7238f9..a1abf2f 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCompilerPlugin.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCompilerPlugin.java @@ -23,6 +23,8 @@ /** * Compiler plugin for Jsondata's utils functions. + * + * @since 0.1.0 */ public class JsondataCompilerPlugin extends CompilerPlugin { diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataDiagnosticCodes.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataDiagnosticCodes.java index bb8c523..418f52a 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataDiagnosticCodes.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataDiagnosticCodes.java @@ -22,10 +22,16 @@ import static io.ballerina.tools.diagnostics.DiagnosticSeverity.ERROR; +/** + * Diagnostic codes for Jsondata's compiler plugin. + * + * @since 0.1.0 + */ public enum JsondataDiagnosticCodes { - UNSUPPORTED_UNION_TYPE("BDE202", - "unsupported union type: union type does not support multiple complex types", ERROR); + UNSUPPORTED_UNION_TYPE("JSON_ERROR_201", + "unsupported union type: union type does not support multiple complex types", ERROR), + DUPLICATE_FIELD("JSON_ERROR_202", "invalid field: duplicate field found", ERROR); private final String code; private final String message; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java index f3939e6..3852588 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java @@ -19,11 +19,13 @@ package io.ballerina.stdlib.data.jsondata.compiler; import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.AnnotationAttachmentSymbol; import io.ballerina.compiler.api.symbols.ArrayTypeSymbol; import io.ballerina.compiler.api.symbols.RecordFieldSymbol; import io.ballerina.compiler.api.symbols.RecordTypeSymbol; import io.ballerina.compiler.api.symbols.Symbol; import io.ballerina.compiler.api.symbols.TupleTypeSymbol; +import io.ballerina.compiler.api.symbols.TypeDefinitionSymbol; import io.ballerina.compiler.api.symbols.TypeDescKind; import io.ballerina.compiler.api.symbols.TypeReferenceTypeSymbol; import io.ballerina.compiler.api.symbols.TypeSymbol; @@ -39,6 +41,7 @@ import io.ballerina.compiler.syntax.tree.ModuleVariableDeclarationNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.SyntaxKind; +import io.ballerina.compiler.syntax.tree.TypeDefinitionNode; import io.ballerina.compiler.syntax.tree.VariableDeclarationNode; import io.ballerina.projects.plugins.AnalysisTask; import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; @@ -48,13 +51,17 @@ import io.ballerina.tools.diagnostics.DiagnosticSeverity; import io.ballerina.tools.diagnostics.Location; +import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; /** * Jsondata Record Field Validator. + * + * @since 0.1.0 */ public class JsondataTypeValidator implements AnalysisTask { @@ -78,6 +85,8 @@ public void perform(SyntaxNodeAnalysisContext ctx) { case FUNCTION_DEFINITION -> processFunctionDefinitionNode((FunctionDefinitionNode) member, ctx); case MODULE_VAR_DECL -> processModuleVariableDeclarationNode((ModuleVariableDeclarationNode) member, ctx); + case TYPE_DEFINITION -> + processTypeDefinitionNode((TypeDefinitionNode) member, ctx); } } } @@ -91,7 +100,7 @@ private void processFunctionDefinitionNode(FunctionDefinitionNode functionDefini } VariableDeclarationNode variableDeclarationNode = (VariableDeclarationNode) node; Optional initializer = variableDeclarationNode.initializer(); - if (initializer.isEmpty() || !isFromJsonFunctionFromXmldata(initializer.get())) { + if (initializer.isEmpty()) { continue; } @@ -100,11 +109,20 @@ private void processFunctionDefinitionNode(FunctionDefinitionNode functionDefini if (symbol.isEmpty()) { continue; } - validateExpectedType(((VariableSymbol) symbol.get()).typeDescriptor(), ctx); + + TypeSymbol typeSymbol = ((VariableSymbol) symbol.get()).typeDescriptor(); + if (!isFromJsonFunctionFromJsondata(initializer.get())) { + if (typeSymbol.typeKind() == TypeDescKind.RECORD) { + detectDuplicateFields((RecordTypeSymbol) typeSymbol, ctx); + } + continue; + } + + validateExpectedType(typeSymbol, ctx); } } - private boolean isFromJsonFunctionFromXmldata(ExpressionNode expressionNode) { + private boolean isFromJsonFunctionFromJsondata(ExpressionNode expressionNode) { if (expressionNode.kind() == SyntaxKind.CHECK_EXPRESSION) { expressionNode = ((CheckExpressionNode) expressionNode).expression(); } @@ -112,7 +130,7 @@ private boolean isFromJsonFunctionFromXmldata(ExpressionNode expressionNode) { if (expressionNode.kind() != SyntaxKind.FUNCTION_CALL) { return false; } - String functionName = ((FunctionCallExpressionNode) expressionNode).functionName().toSourceCode().trim(); + String functionName = ((FunctionCallExpressionNode) expressionNode).functionName().toString().trim(); return functionName.contains(Constants.FROM_JSON_STRING_WITH_TYPE); } @@ -134,10 +152,11 @@ private void validateTupleType(TupleTypeSymbol tupleTypeSymbol, SyntaxNodeAnalys } private void validateRecordType(RecordTypeSymbol recordTypeSymbol, SyntaxNodeAnalysisContext ctx) { + detectDuplicateFields(recordTypeSymbol, ctx); + for (Map.Entry entry : recordTypeSymbol.fieldDescriptors().entrySet()) { RecordFieldSymbol fieldSymbol = entry.getValue(); - TypeSymbol typeSymbol = fieldSymbol.typeDescriptor(); - validateRecordFieldType(typeSymbol, fieldSymbol.getLocation(), ctx); + validateRecordFieldType(fieldSymbol.typeDescriptor(), fieldSymbol.getLocation(), ctx); } } @@ -156,7 +175,7 @@ private void validateUnionType(UnionTypeSymbol unionTypeSymbol, Optional memberTypeSymbols = unionTypeSymbol.memberTypeDescriptors(); for (TypeSymbol memberTypeSymbol : memberTypeSymbols) { - if (isPrimitiveType(memberTypeSymbol)) { + if (isSupportedUnionMemberType(memberTypeSymbol)) { continue; } nonPrimitiveMemberCount++; @@ -167,15 +186,20 @@ private void validateUnionType(UnionTypeSymbol unionTypeSymbol, Optional { + return true; + } + default -> { + return false; + } + } } private void reportDiagnosticInfo(SyntaxNodeAnalysisContext ctx, Optional location, @@ -193,7 +217,7 @@ private void reportDiagnosticInfo(SyntaxNodeAnalysisContext ctx, Optional initializer = moduleVariableDeclarationNode.initializer(); - if (initializer.isEmpty() || !isFromJsonFunctionFromXmldata(initializer.get())) { + if (initializer.isEmpty() || !isFromJsonFunctionFromJsondata(initializer.get())) { return; } @@ -203,4 +227,50 @@ private void processModuleVariableDeclarationNode(ModuleVariableDeclarationNode } validateExpectedType(((VariableSymbol) symbol.get()).typeDescriptor(), ctx); } + + private void processTypeDefinitionNode(TypeDefinitionNode typeDefinitionNode, SyntaxNodeAnalysisContext ctx) { + Node typeDescriptor = typeDefinitionNode.typeDescriptor(); + if (typeDescriptor.kind() != SyntaxKind.RECORD_TYPE_DESC) { + return; + } + validateRecordTypeDefinition(typeDefinitionNode, ctx); + } + + private void validateRecordTypeDefinition(TypeDefinitionNode typeDefinitionNode, SyntaxNodeAnalysisContext ctx) { + Optional symbol = semanticModel.symbol(typeDefinitionNode); + if (symbol.isEmpty()) { + return; + } + TypeDefinitionSymbol typeDefinitionSymbol = (TypeDefinitionSymbol) symbol.get(); + detectDuplicateFields((RecordTypeSymbol) typeDefinitionSymbol.typeDescriptor(), ctx); + } + + private void detectDuplicateFields(RecordTypeSymbol recordTypeSymbol, SyntaxNodeAnalysisContext ctx) { + List fieldMembers = new ArrayList<>(); + for (Map.Entry entry : recordTypeSymbol.fieldDescriptors().entrySet()) { + RecordFieldSymbol fieldSymbol = entry.getValue(); + String name = getNameFromAnnotation(entry.getKey(), fieldSymbol.annotAttachments()); + if (fieldMembers.contains(name)) { + reportDiagnosticInfo(ctx, fieldSymbol.getLocation(), JsondataDiagnosticCodes.DUPLICATE_FIELD); + return; + } + fieldMembers.add(name); + } + } + + private String getNameFromAnnotation(String fieldName, + List annotationAttachments) { + for (AnnotationAttachmentSymbol annotAttSymbol : annotationAttachments) { + Optional nameAnnot = annotAttSymbol.typeDescriptor().getName(); + if (nameAnnot.isEmpty()) { + continue; + } + String value = nameAnnot.get(); + if (value.equals(Constants.NAME)) { + return ((LinkedHashMap) annotAttSymbol.attachmentValue().orElseThrow().value()) + .get("value").toString(); + } + } + return fieldName; + } } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java index 3d8fcb1..b8c16d8 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java @@ -35,7 +35,6 @@ import java.util.ArrayList; import java.util.Comparator; -import java.util.HashMap; import java.util.List; /** @@ -45,25 +44,24 @@ */ public class FromString { - private static final HashMap TYPE_PRIORITY_ORDER = new HashMap<>() {{ - int precedence = 0; - put(TypeTags.INT_TAG, precedence++); - put(TypeTags.FLOAT_TAG, precedence++); - put(TypeTags.DECIMAL_TAG, precedence++); - put(TypeTags.NULL_TAG, precedence++); - put(TypeTags.BOOLEAN_TAG, precedence++); - put(TypeTags.JSON_TAG, precedence++); - put(TypeTags.STRING_TAG, precedence); - }}; - - private static final ArrayList BASIC_JSON_MEMBER_TYPES = new ArrayList<>() {{ - add(PredefinedTypes.TYPE_NULL); - add(PredefinedTypes.TYPE_BOOLEAN); - add(PredefinedTypes.TYPE_INT); - add(PredefinedTypes.TYPE_FLOAT); - add(PredefinedTypes.TYPE_DECIMAL); - add(PredefinedTypes.TYPE_STRING); - }}; + private static final List TYPE_PRIORITY_ORDER = List.of( + TypeTags.INT_TAG, + TypeTags.FLOAT_TAG, + TypeTags.DECIMAL_TAG, + TypeTags.NULL_TAG, + TypeTags.BOOLEAN_TAG, + TypeTags.JSON_TAG, + TypeTags.STRING_TAG + ); + + private static final List BASIC_JSON_MEMBER_TYPES = List.of( + PredefinedTypes.TYPE_NULL, + PredefinedTypes.TYPE_BOOLEAN, + PredefinedTypes.TYPE_INT, + PredefinedTypes.TYPE_FLOAT, + PredefinedTypes.TYPE_DECIMAL, + PredefinedTypes.TYPE_STRING + ); private static final UnionType JSON_TYPE_WITH_BASIC_TYPES = TypeCreator.createUnionType(BASIC_JSON_MEMBER_TYPES); public static Object fromStringWithType(BString string, BTypedesc typed) { @@ -140,9 +138,11 @@ private static Object stringToNull(String value) throws NumberFormatException { } private static Object stringToUnion(BString string, UnionType expType) throws NumberFormatException { - List memberTypes = expType.getMemberTypes(); - memberTypes.sort(Comparator.comparingInt(t -> TYPE_PRIORITY_ORDER.getOrDefault( - TypeUtils.getReferredType(t).getTag(), Integer.MAX_VALUE))); + List memberTypes = new ArrayList<>(expType.getMemberTypes()); + memberTypes.sort(Comparator.comparingInt(t -> { + int index = TYPE_PRIORITY_ORDER.indexOf(TypeUtils.getReferredType(t).getTag()); + return index == -1 ? Integer.MAX_VALUE : index; + })); for (Type memberType : memberTypes) { try { Object result = fromStringWithType(string, memberType); diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderThreadPool.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderThreadPool.java index 3418f68..ef470e6 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderThreadPool.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderThreadPool.java @@ -40,6 +40,8 @@ public class DataReaderThreadPool { /** * Thread factory for data reader. + * + * @since 0.1.0 */ static class DataThreadFactory implements ThreadFactory { diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java index aac8ce1..e23fb5b 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java @@ -99,21 +99,25 @@ static Optional> initNewMapValue(JsonParser.StateMachine s RecordType recordType = (RecordType) currentType; nextMapValue = ValueCreator.createRecordValue(expType.getPackage(), expType.getName()); sm.fieldHierarchy.push(new HashMap<>(recordType.getFields())); + sm.visitedFieldHierarchy.push(new HashMap<>()); sm.restType.push(recordType.getRestFieldType()); break; case TypeTags.MAP_TAG: nextMapValue = ValueCreator.createMapValue((MapType) currentType); sm.fieldHierarchy.push(new HashMap<>()); + sm.visitedFieldHierarchy.push(new HashMap<>()); sm.restType.push(((MapType) currentType).getConstrainedType()); break; case TypeTags.JSON_TAG: nextMapValue = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); sm.fieldHierarchy.push(new HashMap<>()); + sm.visitedFieldHierarchy.push(new HashMap<>()); sm.restType.push(PredefinedTypes.TYPE_JSON); break; case TypeTags.ANYDATA_TAG: nextMapValue = ValueCreator.createMapValue(Constants.ANYDATA_MAP_TYPE); sm.fieldHierarchy.push(new HashMap<>()); + sm.visitedFieldHierarchy.push(new HashMap<>()); sm.restType.push(PredefinedTypes.TYPE_JSON); break; default: diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java index 94bd731..4482fdf 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java @@ -130,7 +130,6 @@ static class StateMachine { Object currentJsonNode; Deque nodesStack; - // TODO: Need group same level field and keep the hierarchy. Deque fieldNames; private StringBuilder hexBuilder = new StringBuilder(4); @@ -143,6 +142,7 @@ static class StateMachine { private char currentQuoteChar; Field currentField; Stack> fieldHierarchy = new Stack<>(); + Stack> visitedFieldHierarchy = new Stack<>(); Stack restType = new Stack<>(); Stack expectedTypes = new Stack<>(); int jsonFieldDepth = 0; @@ -188,44 +188,40 @@ private void processLocation(char ch) { public Object execute(Reader reader, Type type) throws BError { switch (type.getTag()) { // TODO: Handle readonly and singleton type as expType. - case TypeTags.RECORD_TYPE_TAG: + case TypeTags.RECORD_TYPE_TAG -> { RecordType recordType = (RecordType) type; expectedTypes.push(recordType); fieldHierarchy.push(JsonCreator.getAllFieldsInRecord(recordType)); + visitedFieldHierarchy.push(new HashMap<>()); restType.push(recordType.getRestFieldType()); - break; - case TypeTags.ARRAY_TAG: - case TypeTags.TUPLE_TAG: + } + case TypeTags.ARRAY_TAG, TypeTags.TUPLE_TAG -> { expectedTypes.push(type); arrayIndexes.push(0); - break; - case TypeTags.NULL_TAG: - case TypeTags.BOOLEAN_TAG: - case TypeTags.INT_TAG: - case TypeTags.FLOAT_TAG: - case TypeTags.DECIMAL_TAG: - case TypeTags.STRING_TAG: - expectedTypes.push(type); - break; - case TypeTags.JSON_TAG: - case TypeTags.ANYDATA_TAG: + } + case TypeTags.NULL_TAG, TypeTags.BOOLEAN_TAG, TypeTags.INT_TAG, TypeTags.FLOAT_TAG, + TypeTags.DECIMAL_TAG, TypeTags.STRING_TAG -> + expectedTypes.push(type); + case TypeTags.JSON_TAG, TypeTags.ANYDATA_TAG -> { expectedTypes.push(type); fieldHierarchy.push(new HashMap<>()); + visitedFieldHierarchy.push(new HashMap<>()); restType.push(type); - break; - case TypeTags.MAP_TAG: + } + case TypeTags.MAP_TAG -> { expectedTypes.push(type); fieldHierarchy.push(new HashMap<>()); + visitedFieldHierarchy.push(new HashMap<>()); restType.push(((MapType) type).getConstrainedType()); - break; - case TypeTags.UNION_TAG: + } + case TypeTags.UNION_TAG -> { if (isSupportedUnionType((UnionType) type)) { expectedTypes.push(type); break; } throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE, type); - default: - throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE, type); + } + default -> throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE, type); } State currentState = DOC_START_STATE; @@ -253,14 +249,13 @@ public Object execute(Reader reader, Type type) throws BError { private boolean isSupportedUnionType(UnionType type) { for (Type memberType : type.getMemberTypes()) { switch (memberType.getTag()) { - case TypeTags.RECORD_TYPE_TAG: - case TypeTags.OBJECT_TYPE_TAG: - case TypeTags.MAP_TAG: - case TypeTags.JSON_TAG: - case TypeTags.ANYDATA_TAG: + case TypeTags.RECORD_TYPE_TAG, TypeTags.OBJECT_TYPE_TAG, TypeTags.MAP_TAG, TypeTags.JSON_TAG, + TypeTags.ANYDATA_TAG -> { return false; - case TypeTags.UNION_TAG: + } + case TypeTags.UNION_TAG -> { return !isSupportedUnionType(type); + } } } return true; @@ -298,6 +293,7 @@ private State finalizeNonArrayObject() { } Map remainingFields = fieldHierarchy.pop(); + visitedFieldHierarchy.pop(); restType.pop(); for (Field field : remainingFields.values()) { if (SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.REQUIRED)) { @@ -331,7 +327,7 @@ private State finalizeObject() { } switch (TypeUtils.getType(parentNode).getTag()) { - case TypeTags.ARRAY_TAG: + case TypeTags.ARRAY_TAG -> { // Handle projection in array. ArrayType arrayType = (ArrayType) parentNodeType; if (arrayType.getState() == ArrayType.ArrayState.CLOSED && @@ -339,12 +335,10 @@ private State finalizeObject() { break; } ((BArray) parentNode).add(arrayIndexes.peek(), currentJsonNode); - break; - case TypeTags.TUPLE_TAG: - ((BArray) parentNode).add(arrayIndexes.peek(), currentJsonNode); - break; - default: - break; + } + case TypeTags.TUPLE_TAG -> ((BArray) parentNode).add(arrayIndexes.peek(), currentJsonNode); + default -> { + } } currentJsonNode = parentNode; @@ -480,7 +474,7 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J private static class FirstArrayElementReadyState implements State { @Override - public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + public State transition(StateMachine sm, char[] buff, int i, int count) { State state = null; char ch; for (; i < count; i++) { @@ -561,7 +555,7 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J private static class NonFirstArrayElementReadyState implements State { @Override - public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + public State transition(StateMachine sm, char[] buff, int i, int count) { State state = null; char ch; for (; i < count; i++) { @@ -629,13 +623,22 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J if (ch == sm.currentQuoteChar) { String jsonFieldName = sm.processFieldName(); if (sm.jsonFieldDepth == 0) { - sm.currentField = sm.fieldHierarchy.peek().remove(jsonFieldName); + Field currentField; + if (sm.visitedFieldHierarchy.peek().containsKey(jsonFieldName)) { + currentField = sm.visitedFieldHierarchy.peek().get(jsonFieldName); + } else { + currentField = sm.fieldHierarchy.peek().remove(jsonFieldName); + } + + sm.currentField = currentField; Type fieldType; - if (sm.currentField == null) { + if (currentField == null) { fieldType = sm.restType.peek(); } else { - jsonFieldName = sm.currentField.getFieldName(); - fieldType = sm.currentField.getFieldType(); + // Replace modified field name with actual field name. + jsonFieldName = currentField.getFieldName(); + fieldType = currentField.getFieldType(); + sm.visitedFieldHierarchy.peek().put(jsonFieldName, currentField); } sm.expectedTypes.push(fieldType); } else if (sm.expectedTypes.peek() == null) { @@ -692,7 +695,7 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J private static class FieldValueReadyState implements State { @Override - public State transition(StateMachine sm, char[] buff, int i, int count) throws JsonParserException { + public State transition(StateMachine sm, char[] buff, int i, int count) { State state = null; char ch; for (; i < count; i++) { diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java index b43cb46..7482388 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java @@ -32,6 +32,7 @@ import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BArray; +import io.ballerina.runtime.api.values.BDecimal; import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; @@ -85,7 +86,7 @@ public Object traverseJson(Object json, Type type) { switch (referredType.getTag()) { case TypeTags.RECORD_TYPE_TAG: RecordType recordType = (RecordType) referredType; - fieldHierarchy.push(new HashMap<>(recordType.getFields())); + fieldHierarchy.push(JsonCreator.getAllFieldsInRecord(recordType)); restType.push(recordType.getRestFieldType()); return traverseMapJsonOrArrayJson(json, ValueCreator.createRecordValue(type.getPackage(), type.getName()), referredType); @@ -157,7 +158,8 @@ private Object traverseMapValue(BMap map, Object currentJsonNod continue; } - fieldNames.push(currentField.getFieldName()); + String fieldName = currentField.getFieldName(); + fieldNames.push(fieldName); Type currentFieldType = TypeUtils.getReferredType(currentField.getFieldType()); int currentFieldTypeTag = currentFieldType.getTag(); Object mapValue = map.get(key); @@ -174,7 +176,7 @@ private Object traverseMapValue(BMap map, Object currentJsonNod break; default: Object nextJsonNode = traverseJson(mapValue, currentFieldType); - ((BMap) currentJsonNode).put(key, nextJsonNode); + ((BMap) currentJsonNode).put(StringUtils.fromString(fieldName), nextJsonNode); } } Map currentField = fieldHierarchy.pop(); @@ -258,8 +260,8 @@ private boolean checkTypeCompatibility(Type type, Object json) { return ((json == null && type.getTag() == TypeTags.NULL_TAG) || (json instanceof BString && type.getTag() == TypeTags.STRING_TAG) || (json instanceof Long && type.getTag() == TypeTags.INT_TAG) - || (json instanceof Double && (type.getTag() == TypeTags.FLOAT_TAG - || type.getTag() == TypeTags.DECIMAL_TAG)) + || (json instanceof Double && type.getTag() == TypeTags.FLOAT_TAG) + || ((json instanceof Double || json instanceof BDecimal) && type.getTag() == TypeTags.DECIMAL_TAG) || (Boolean.class.isInstance(json) && type.getTag() == TypeTags.BOOLEAN_TAG)); } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/Constants.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/Constants.java index 244bde9..99bb8dc 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/Constants.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/Constants.java @@ -24,6 +24,11 @@ import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.values.BString; +/** + * Constants for jsondata. + * + * @since 0.1.0 + */ public class Constants { public static final MapType JSON_MAP_TYPE = TypeCreator.createMapType(PredefinedTypes.TYPE_JSON); public static final MapType ANYDATA_MAP_TYPE = TypeCreator.createMapType(PredefinedTypes.TYPE_ANYDATA); diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticLog.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticLog.java index 46eb9d4..9c586d5 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticLog.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticLog.java @@ -27,7 +27,7 @@ import java.util.ResourceBundle; /** - * Diagnostic log for data module. + * Diagnostic log for jsondata module. * * @since 0.1.0 */ From e67084848bfac589038bb6c7e8a905a4052efea6 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:46:04 +0530 Subject: [PATCH 18/27] Refactor code and address suggestions --- .../data/jsondata/json/JsonCreator.java | 95 ++++++++----------- .../stdlib/data/jsondata/json/JsonParser.java | 92 +++++++++--------- .../data/jsondata/json/JsonTraverse.java | 88 ++++++++--------- 3 files changed, 128 insertions(+), 147 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java index e23fb5b..c676754 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java @@ -52,33 +52,24 @@ public class JsonCreator { static BMap initRootMapValue(Type expectedType) { - switch (expectedType.getTag()) { - case TypeTags.RECORD_TYPE_TAG: - return ValueCreator.createRecordValue(expectedType.getPackage(), expectedType.getName()); - case TypeTags.MAP_TAG: - return ValueCreator.createMapValue((MapType) expectedType); - case TypeTags.JSON_TAG: - return ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); - case TypeTags.ANYDATA_TAG: - return ValueCreator.createMapValue(Constants.ANYDATA_MAP_TYPE); - default: - throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, expectedType, "map type"); - } + return switch (expectedType.getTag()) { + case TypeTags.RECORD_TYPE_TAG -> + ValueCreator.createRecordValue(expectedType.getPackage(), expectedType.getName()); + case TypeTags.MAP_TAG -> ValueCreator.createMapValue((MapType) expectedType); + case TypeTags.JSON_TAG -> ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); + case TypeTags.ANYDATA_TAG -> ValueCreator.createMapValue(Constants.ANYDATA_MAP_TYPE); + default -> throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, expectedType, "map type"); + }; } static BArray initArrayValue(Type expectedType) { - switch (expectedType.getTag()) { - case TypeTags.TUPLE_TAG: - return ValueCreator.createTupleValue((TupleType) expectedType); - case TypeTags.ARRAY_TAG: - return ValueCreator.createArrayValue((ArrayType) expectedType); - case TypeTags.JSON_TAG: - return ValueCreator.createArrayValue(PredefinedTypes.TYPE_JSON_ARRAY); - case TypeTags.ANYDATA_TAG: - return ValueCreator.createArrayValue(PredefinedTypes.TYPE_ANYDATA_ARRAY); - default: - throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, expectedType, "list type"); - } + return switch (expectedType.getTag()) { + case TypeTags.TUPLE_TAG -> ValueCreator.createTupleValue((TupleType) expectedType); + case TypeTags.ARRAY_TAG -> ValueCreator.createArrayValue((ArrayType) expectedType); + case TypeTags.JSON_TAG -> ValueCreator.createArrayValue(PredefinedTypes.TYPE_JSON_ARRAY); + case TypeTags.ANYDATA_TAG -> ValueCreator.createArrayValue(PredefinedTypes.TYPE_ANYDATA_ARRAY); + default -> throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, expectedType, "list type"); + }; } static Optional> initNewMapValue(JsonParser.StateMachine sm) { @@ -95,33 +86,24 @@ static Optional> initNewMapValue(JsonParser.StateMachine s BMap nextMapValue; switch (currentType.getTag()) { - case TypeTags.RECORD_TYPE_TAG: + case TypeTags.RECORD_TYPE_TAG -> { RecordType recordType = (RecordType) currentType; nextMapValue = ValueCreator.createRecordValue(expType.getPackage(), expType.getName()); - sm.fieldHierarchy.push(new HashMap<>(recordType.getFields())); - sm.visitedFieldHierarchy.push(new HashMap<>()); - sm.restType.push(recordType.getRestFieldType()); - break; - case TypeTags.MAP_TAG: + sm.updateExpectedType(recordType.getFields(), recordType.getRestFieldType()); + } + case TypeTags.MAP_TAG -> { nextMapValue = ValueCreator.createMapValue((MapType) currentType); - sm.fieldHierarchy.push(new HashMap<>()); - sm.visitedFieldHierarchy.push(new HashMap<>()); - sm.restType.push(((MapType) currentType).getConstrainedType()); - break; - case TypeTags.JSON_TAG: + sm.updateExpectedType(new HashMap<>(), ((MapType) currentType).getConstrainedType()); + } + case TypeTags.JSON_TAG -> { nextMapValue = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); - sm.fieldHierarchy.push(new HashMap<>()); - sm.visitedFieldHierarchy.push(new HashMap<>()); - sm.restType.push(PredefinedTypes.TYPE_JSON); - break; - case TypeTags.ANYDATA_TAG: + sm.updateExpectedType(new HashMap<>(), PredefinedTypes.TYPE_JSON); + } + case TypeTags.ANYDATA_TAG -> { nextMapValue = ValueCreator.createMapValue(Constants.ANYDATA_MAP_TYPE); - sm.fieldHierarchy.push(new HashMap<>()); - sm.visitedFieldHierarchy.push(new HashMap<>()); - sm.restType.push(PredefinedTypes.TYPE_JSON); - break; - default: - throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE_FOR_FIELD, getCurrentFieldPath(sm)); + sm.updateExpectedType(new HashMap<>(), PredefinedTypes.TYPE_JSON); + } + default -> throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE_FOR_FIELD, getCurrentFieldPath(sm)); } Object currentJson = sm.currentJsonNode; @@ -181,12 +163,12 @@ static Object convertAndUpdateCurrentJsonNode(JsonParser.StateMachine sm, BStrin Type currentJsonNodeType = TypeUtils.getType(currentJson); switch (currentJsonNodeType.getTag()) { - case TypeTags.MAP_TAG: - case TypeTags.RECORD_TYPE_TAG: + case TypeTags.MAP_TAG, TypeTags.RECORD_TYPE_TAG -> { ((BMap) currentJson).put(StringUtils.fromString(sm.fieldNames.pop()), convertedValue); return currentJson; - case TypeTags.ARRAY_TAG: + } + case TypeTags.ARRAY_TAG -> { // Handle projection in array. ArrayType arrayType = (ArrayType) currentJsonNodeType; if (arrayType.getState() == ArrayType.ArrayState.CLOSED && @@ -195,11 +177,14 @@ static Object convertAndUpdateCurrentJsonNode(JsonParser.StateMachine sm, BStrin } ((BArray) currentJson).add(sm.arrayIndexes.peek(), convertedValue); return currentJson; - case TypeTags.TUPLE_TAG: + } + case TypeTags.TUPLE_TAG -> { ((BArray) currentJson).add(sm.arrayIndexes.peek(), convertedValue); return currentJson; - default: + } + default -> { return convertedValue; + } } } @@ -211,11 +196,9 @@ private static Object convertToExpectedType(BString value, Type type) { } static void updateRecordFieldValue(BString fieldName, Object parent, Object currentJson) { - switch (TypeUtils.getType(parent).getTag()) { - case TypeTags.MAP_TAG: - case TypeTags.RECORD_TYPE_TAG: - ((BMap) parent).put(fieldName, currentJson); - break; + int typeTag = TypeUtils.getType(parent).getTag(); + if (typeTag == TypeTags.MAP_TAG || typeTag == TypeTags.RECORD_TYPE_TAG) { + ((BMap) parent).put(fieldName, currentJson); } } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java index 4482fdf..02350bd 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java @@ -191,9 +191,7 @@ public Object execute(Reader reader, Type type) throws BError { case TypeTags.RECORD_TYPE_TAG -> { RecordType recordType = (RecordType) type; expectedTypes.push(recordType); - fieldHierarchy.push(JsonCreator.getAllFieldsInRecord(recordType)); - visitedFieldHierarchy.push(new HashMap<>()); - restType.push(recordType.getRestFieldType()); + updateExpectedType(JsonCreator.getAllFieldsInRecord(recordType), recordType.getRestFieldType()); } case TypeTags.ARRAY_TAG, TypeTags.TUPLE_TAG -> { expectedTypes.push(type); @@ -204,15 +202,11 @@ public Object execute(Reader reader, Type type) throws BError { expectedTypes.push(type); case TypeTags.JSON_TAG, TypeTags.ANYDATA_TAG -> { expectedTypes.push(type); - fieldHierarchy.push(new HashMap<>()); - visitedFieldHierarchy.push(new HashMap<>()); - restType.push(type); + updateExpectedType(new HashMap<>(), type); } case TypeTags.MAP_TAG -> { expectedTypes.push(type); - fieldHierarchy.push(new HashMap<>()); - visitedFieldHierarchy.push(new HashMap<>()); - restType.push(((MapType) type).getConstrainedType()); + updateExpectedType(new HashMap<>(), ((MapType) type).getConstrainedType()); } case TypeTags.UNION_TAG -> { if (isSupportedUnionType((UnionType) type)) { @@ -278,6 +272,12 @@ private void growCharBuff() { this.charBuff = newBuff; } + private State finalizeNonArrayObjectAndRemoveExpectedType() { + State state = finalizeNonArrayObject(); + expectedTypes.pop(); + return state; + } + private State finalizeNonArrayObject() { if (jsonFieldDepth > 0) { jsonFieldDepth--; @@ -345,6 +345,30 @@ private State finalizeObject() { return ARRAY_ELEMENT_END_STATE; } + private void updateIndexOfArrayElement() { + int arrayIndex = arrayIndexes.pop(); + arrayIndexes.push(arrayIndex + 1); + } + + public void updateExpectedType(Map fields, Type restType) { + this.fieldHierarchy.push(new HashMap<>(fields)); + this.visitedFieldHierarchy.push(new HashMap<>()); + this.restType.push(restType); + } + + private void updateNextArrayValue() { + arrayIndexes.push(0); + Optional nextArray = JsonCreator.initNewArrayValue(this); + nextArray.ifPresent(array -> currentJsonNode = array); + } + + private State finalizeArrayObject() { + int currentIndex = arrayIndexes.pop(); + State state = finalizeObject(); + JsonCreator.validateListSize(currentIndex, expectedTypes.pop()); + return state; + } + public enum ParserContext { MAP, ARRAY @@ -456,8 +480,7 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J state = this; continue; } else if (ch == '}') { - state = sm.finalizeNonArrayObject(); - sm.expectedTypes.pop(); + state = sm.finalizeNonArrayObjectAndRemoveExpectedType(); } else { StateMachine.throwExpected("\"", "}"); } @@ -498,13 +521,10 @@ public State transition(StateMachine sm, char[] buff, int i, int count) { // Get member type of the array and set as expected type. sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), sm.arrayIndexes.peek())); - sm.arrayIndexes.push(0); - Optional nextArray = JsonCreator.initNewArrayValue(sm); - nextArray.ifPresent(array -> sm.currentJsonNode = array); + sm.updateNextArrayValue(); state = FIRST_ARRAY_ELEMENT_READY_STATE; } else if (ch == ']') { - state = sm.finalizeObject(); - sm.expectedTypes.pop(); + state = sm.finalizeArrayObject(); } else { state = NON_STRING_ARRAY_ELEMENT_STATE; sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), @@ -577,9 +597,7 @@ public State transition(StateMachine sm, char[] buff, int i, int count) { } else if (ch == '[') { sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), sm.arrayIndexes.peek())); - sm.arrayIndexes.push(0); - Optional nextArray = JsonCreator.initNewArrayValue(sm); - nextArray.ifPresent(array -> sm.currentJsonNode = array); + sm.updateNextArrayValue(); state = FIRST_ARRAY_ELEMENT_READY_STATE; } else { sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), @@ -604,8 +622,7 @@ private String value() { } private String processFieldName() { - String value = this.value(); - return value; + return this.value(); } /** @@ -831,16 +848,13 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J state = FIRST_FIELD_READY_STATE; } else if (ch == '[') { state = FIRST_ARRAY_ELEMENT_READY_STATE; - Optional nextArray = JsonCreator.initNewArrayValue(sm); - nextArray.ifPresent(bArray -> sm.currentJsonNode = bArray); + sm.updateNextArrayValue(); } else if (ch == '}') { sm.processValue(); - state = sm.finalizeNonArrayObject(); - sm.expectedTypes.pop(); + state = sm.finalizeNonArrayObjectAndRemoveExpectedType(); } else if (ch == ']') { sm.processValue(); - state = sm.finalizeObject(); - sm.expectedTypes.pop(); + state = sm.finalizeArrayObject(); } else if (ch == ',') { sm.processValue(); state = NON_FIRST_FIELD_READY_STATE; @@ -878,20 +892,14 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J state = FIRST_FIELD_READY_STATE; } else if (ch == '[') { state = FIRST_ARRAY_ELEMENT_READY_STATE; - Optional nextArray = JsonCreator.initNewArrayValue(sm); - nextArray.ifPresent(bArray -> sm.currentJsonNode = bArray); + sm.updateNextArrayValue(); } else if (ch == ']') { sm.processValue(); - int currentIndex = sm.arrayIndexes.pop(); - state = sm.finalizeObject(); - JsonCreator.validateListSize(currentIndex, sm.expectedTypes.pop()); + state = sm.finalizeArrayObject(); } else if (ch == ',') { sm.processValue(); state = NON_FIRST_ARRAY_ELEMENT_READY_STATE; - - // Update index of the array element - int arrayIndex = sm.arrayIndexes.pop(); - sm.arrayIndexes.push(arrayIndex + 1); + sm.updateIndexOfArrayElement(); } else if (StateMachine.isWhitespace(ch)) { sm.processValue(); state = ARRAY_ELEMENT_END_STATE; @@ -996,8 +1004,7 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J } else if (ch == ',') { state = NON_FIRST_FIELD_READY_STATE; } else if (ch == '}') { - state = sm.finalizeNonArrayObject(); - sm.expectedTypes.pop(); + state = sm.finalizeNonArrayObjectAndRemoveExpectedType(); } else { StateMachine.throwExpected(",", "}"); } @@ -1026,14 +1033,9 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J continue; } else if (ch == ',') { state = NON_FIRST_ARRAY_ELEMENT_READY_STATE; - - // Update index of the array element - int arrayIndex = sm.arrayIndexes.pop(); - sm.arrayIndexes.push(arrayIndex + 1); + sm.updateIndexOfArrayElement(); } else if (ch == ']') { - int currentIndex = sm.arrayIndexes.pop(); - state = sm.finalizeObject(); - JsonCreator.validateListSize(currentIndex, sm.expectedTypes.pop()); + state = sm.finalizeArrayObject(); } else { StateMachine.throwExpected(",", "]"); } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java index 7482388..9c9d455 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java @@ -84,28 +84,28 @@ void reset() { public Object traverseJson(Object json, Type type) { Type referredType = TypeUtils.getReferredType(type); switch (referredType.getTag()) { - case TypeTags.RECORD_TYPE_TAG: + case TypeTags.RECORD_TYPE_TAG -> { RecordType recordType = (RecordType) referredType; fieldHierarchy.push(JsonCreator.getAllFieldsInRecord(recordType)); restType.push(recordType.getRestFieldType()); return traverseMapJsonOrArrayJson(json, ValueCreator.createRecordValue(type.getPackage(), type.getName()), referredType); - case TypeTags.ARRAY_TAG: + } + case TypeTags.ARRAY_TAG -> { rootArray = referredType; return traverseMapJsonOrArrayJson(json, ValueCreator.createArrayValue((ArrayType) referredType), referredType); - case TypeTags.TUPLE_TAG: + } + case TypeTags.TUPLE_TAG -> { rootArray = referredType; return traverseMapJsonOrArrayJson(json, ValueCreator.createTupleValue((TupleType) referredType), referredType); - case TypeTags.NULL_TAG: - case TypeTags.BOOLEAN_TAG: - case TypeTags.INT_TAG: - case TypeTags.FLOAT_TAG: - case TypeTags.DECIMAL_TAG: - case TypeTags.STRING_TAG: + } + case TypeTags.NULL_TAG, TypeTags.BOOLEAN_TAG, TypeTags.INT_TAG, TypeTags.FLOAT_TAG, + TypeTags.DECIMAL_TAG, TypeTags.STRING_TAG -> { return convertToBasicType(json, referredType); - case TypeTags.UNION_TAG: + } + case TypeTags.UNION_TAG -> { for (Type memberType : ((UnionType) referredType).getMemberTypes()) { try { return traverseJson(json, memberType); @@ -113,17 +113,19 @@ public Object traverseJson(Object json, Type type) { // Ignore } } - return DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, type, PredefinedTypes.TYPE_ANYDATA); - case TypeTags.JSON_TAG: - case TypeTags.ANYDATA_TAG: + throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, type, PredefinedTypes.TYPE_ANYDATA); + } + case TypeTags.JSON_TAG, TypeTags.ANYDATA_TAG -> { return json; - case TypeTags.MAP_TAG: + } + case TypeTags.MAP_TAG -> { MapType mapType = (MapType) referredType; fieldHierarchy.push(new HashMap<>()); restType.push(mapType.getConstrainedType()); return traverseMapJsonOrArrayJson(json, ValueCreator.createMapValue(mapType), referredType); - default: - return DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, type, PredefinedTypes.TYPE_ANYDATA); + } + default -> + throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, type, PredefinedTypes.TYPE_ANYDATA); } } @@ -165,18 +167,15 @@ private Object traverseMapValue(BMap map, Object currentJsonNod Object mapValue = map.get(key); switch (currentFieldTypeTag) { - case TypeTags.NULL_TAG: - case TypeTags.BOOLEAN_TAG: - case TypeTags.INT_TAG: - case TypeTags.FLOAT_TAG: - case TypeTags.DECIMAL_TAG: - case TypeTags.STRING_TAG: + case TypeTags.NULL_TAG, TypeTags.BOOLEAN_TAG, TypeTags.INT_TAG, TypeTags.FLOAT_TAG, + TypeTags.DECIMAL_TAG, TypeTags.STRING_TAG -> { Object value = convertToBasicType(mapValue, currentFieldType); ((BMap) currentJsonNode).put(StringUtils.fromString(fieldNames.pop()), value); - break; - default: - Object nextJsonNode = traverseJson(mapValue, currentFieldType); - ((BMap) currentJsonNode).put(StringUtils.fromString(fieldName), nextJsonNode); + } + default -> { + ((BMap) currentJsonNode).put(StringUtils.fromString(fieldName), + traverseJson(mapValue, currentFieldType)); + } } } Map currentField = fieldHierarchy.pop(); @@ -188,28 +187,26 @@ private Object traverseMapValue(BMap map, Object currentJsonNod private Object traverseArrayValue(Object json, Object currentJsonNode) { BArray array = (BArray) json; switch (rootArray.getTag()) { - case TypeTags.ARRAY_TAG: + case TypeTags.ARRAY_TAG -> { ArrayType arrayType = (ArrayType) rootArray; int expectedArraySize = arrayType.getSize(); if (expectedArraySize > array.getLength()) { throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); } - Type elementType = arrayType.getElementType(); if (expectedArraySize == -1) { traverseArrayMembers(array.getLength(), array, elementType, currentJsonNode); } else { traverseArrayMembers(expectedArraySize, array, elementType, currentJsonNode); } - break; - case TypeTags.TUPLE_TAG: + } + case TypeTags.TUPLE_TAG -> { TupleType tupleType = (TupleType) rootArray; Type restType = tupleType.getRestType(); int expectedTupleTypeCount = tupleType.getTupleTypes().size(); if (expectedTupleTypeCount > array.getLength()) { throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); } - for (int i = 0; i < array.getLength(); i++) { Object jsonMember = array.get(i); Object nextJsonNode; @@ -222,7 +219,7 @@ private Object traverseArrayValue(Object json, Object currentJsonNode) { } ((BArray) currentJsonNode).add(i, nextJsonNode); } - break; + } } return currentJsonNode; } @@ -236,33 +233,32 @@ private void traverseArrayMembers(long length, BArray array, Type elementType, O private void addRestField(Type restFieldType, BString key, Object jsonMember, Object currentJsonNode) { Object nextJsonValue; switch (restFieldType.getTag()) { - case TypeTags.ANYDATA_TAG: - case TypeTags.JSON_TAG: - ((BMap) currentJsonNode).put(key, jsonMember); - break; - case TypeTags.BOOLEAN_TAG: - case TypeTags.INT_TAG: - case TypeTags.FLOAT_TAG: - case TypeTags.DECIMAL_TAG: - case TypeTags.STRING_TAG: + case TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG -> + ((BMap) currentJsonNode).put(key, jsonMember); + case TypeTags.BOOLEAN_TAG, TypeTags.INT_TAG, TypeTags.FLOAT_TAG, TypeTags.DECIMAL_TAG, + TypeTags.STRING_TAG -> { if (checkTypeCompatibility(restFieldType, jsonMember)) { ((BMap) currentJsonNode).put(key, jsonMember); } - break; - default: + } + default -> { nextJsonValue = traverseJson(jsonMember, restFieldType); ((BMap) currentJsonNode).put(key, nextJsonValue); - break; + } } } private boolean checkTypeCompatibility(Type type, Object json) { return ((json == null && type.getTag() == TypeTags.NULL_TAG) || (json instanceof BString && type.getTag() == TypeTags.STRING_TAG) - || (json instanceof Long && type.getTag() == TypeTags.INT_TAG) + || (isInstanceOfBallerinaInt(json) && type.getTag() == TypeTags.INT_TAG) || (json instanceof Double && type.getTag() == TypeTags.FLOAT_TAG) || ((json instanceof Double || json instanceof BDecimal) && type.getTag() == TypeTags.DECIMAL_TAG) - || (Boolean.class.isInstance(json) && type.getTag() == TypeTags.BOOLEAN_TAG)); + || (json instanceof Boolean && type.getTag() == TypeTags.BOOLEAN_TAG)); + } + + private boolean isInstanceOfBallerinaInt(Object val) { + return val instanceof Number && !(val instanceof Double); } private void checkOptionalFieldsAndLogError(Map currentField) { From 88da152a781cef17fa23333765c6334d3648f985 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Fri, 8 Mar 2024 00:03:29 +0530 Subject: [PATCH 19/27] Handle projection in array properly --- ballerina/tests/from_json_string_test.bal | 12 ++++++++++++ ballerina/tests/from_json_test.bal | 12 ++++++++++++ .../data/jsondata/json/JsonCreator.java | 19 +++++++++++++++---- .../stdlib/data/jsondata/json/JsonParser.java | 8 +++----- .../data/jsondata/json/JsonTraverse.java | 3 +-- 5 files changed, 43 insertions(+), 11 deletions(-) diff --git a/ballerina/tests/from_json_string_test.bal b/ballerina/tests/from_json_string_test.bal index 00789bc..78c6afa 100644 --- a/ballerina/tests/from_json_string_test.bal +++ b/ballerina/tests/from_json_string_test.bal @@ -835,6 +835,10 @@ isolated function testProjectionInArrayForFromJsonStringWithType() returns error }`; record {| record {| string name; int age; |}[1] employees; |} val5 = check fromJsonStringWithType(strVal5); test:assertEquals(val5, {employees: [{name: "Prakanth", age: 26}]}); + + string strVal6 = string `["1", 2, 3, { "a" : val_a }]`; + int[3] val6 = check fromJsonStringWithType(strVal6); + test:assertEquals(val6, [1, 2, 3]); } @test:Config @@ -1058,3 +1062,11 @@ isolated function testDuplicateFieldInRecordTypeWithFromJsonStringWithType() ret test:assertTrue(x is error); test:assertEquals((x).message(), "duplicate field 'author'"); } + +@test:Config +isolated function testProjectionInArrayNegativeForFromJsonStringWithType() { + string strVal1 = string `["1", 2, 3, { "a" : val_a }]`; + int[]|error val1 = fromJsonStringWithType(strVal1); + test:assertTrue(val1 is error); + test:assertEquals((val1).message(), "invalid type 'int' expected 'map type'"); +} diff --git a/ballerina/tests/from_json_test.bal b/ballerina/tests/from_json_test.bal index 3c138aa..0c81814 100644 --- a/ballerina/tests/from_json_test.bal +++ b/ballerina/tests/from_json_test.bal @@ -825,6 +825,10 @@ isolated function testProjectionInArrayForFromJsonWithType() returns error? { }; record {| record {| string name; int age; |}[1] employees; |} val4 = check fromJsonWithType(jsonVal4); test:assertEquals(val4, {employees: [{name: "Prakanth", age: 26}]}); + + [int, int, int, record {|int a;|}] jsonVal5 = [1, 2, 3, { a : 2 }]; + int[2] val5 = check fromJsonWithType(jsonVal5); + test:assertEquals(val5, [1, 2]); } @test:Config @@ -1048,3 +1052,11 @@ isolated function testDuplicateFieldInRecordTypeWithFromJsonWithType() returns e test:assertTrue(x is error); test:assertEquals((x).message(), "duplicate field 'author'"); } + +@test:Config +isolated function testProjectionInArrayNegativeForFromJsonWithType() { + [int, int, int, record {|int a;|}] jsonVal5 = [1, 2, 3, { a : 2 }]; + int[]|error val5 = fromJsonWithType(jsonVal5); + test:assertTrue(val5 is error); + test:assertEquals((val5).message(), "incompatible expected type 'int' for value '{\"a\":2}'"); +} diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java index c676754..48b8a7b 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java @@ -73,6 +73,7 @@ static BArray initArrayValue(Type expectedType) { } static Optional> initNewMapValue(JsonParser.StateMachine sm) { + JsonParser.StateMachine.ParserContext parentContext = sm.parserContexts.peek(); sm.parserContexts.push(JsonParser.StateMachine.ParserContext.MAP); Type expType = sm.expectedTypes.peek(); if (expType == null) { @@ -97,13 +98,18 @@ static Optional> initNewMapValue(JsonParser.StateMachine s } case TypeTags.JSON_TAG -> { nextMapValue = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); - sm.updateExpectedType(new HashMap<>(), PredefinedTypes.TYPE_JSON); + sm.updateExpectedType(new HashMap<>(), currentType); } case TypeTags.ANYDATA_TAG -> { nextMapValue = ValueCreator.createMapValue(Constants.ANYDATA_MAP_TYPE); - sm.updateExpectedType(new HashMap<>(), PredefinedTypes.TYPE_JSON); + sm.updateExpectedType(new HashMap<>(), currentType); + } + default -> { + if (parentContext == JsonParser.StateMachine.ParserContext.ARRAY) { + throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, currentType, "map type"); + } + throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE_FOR_FIELD, getCurrentFieldPath(sm)); } - default -> throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE_FOR_FIELD, getCurrentFieldPath(sm)); } Object currentJson = sm.currentJsonNode; @@ -208,7 +214,12 @@ static Type getMemberType(Type expectedType, int index) { } if (expectedType.getTag() == TypeTags.ARRAY_TAG) { - return ((ArrayType) expectedType).getElementType(); + ArrayType arrayType = (ArrayType) expectedType; + if (arrayType.getState() == ArrayType.ArrayState.OPEN + || arrayType.getState() == ArrayType.ArrayState.CLOSED && index < arrayType.getSize()) { + return arrayType.getElementType(); + } + return null; } else if (expectedType.getTag() == TypeTags.TUPLE_TAG) { TupleType tupleType = (TupleType) expectedType; List tupleTypes = tupleType.getTupleTypes(); diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java index 02350bd..5682add 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java @@ -640,14 +640,12 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J if (ch == sm.currentQuoteChar) { String jsonFieldName = sm.processFieldName(); if (sm.jsonFieldDepth == 0) { - Field currentField; - if (sm.visitedFieldHierarchy.peek().containsKey(jsonFieldName)) { - currentField = sm.visitedFieldHierarchy.peek().get(jsonFieldName); - } else { + Field currentField = sm.visitedFieldHierarchy.peek().get(jsonFieldName); + if (currentField == null) { currentField = sm.fieldHierarchy.peek().remove(jsonFieldName); } - sm.currentField = currentField; + Type fieldType; if (currentField == null) { fieldType = sm.restType.peek(); diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java index 9c9d455..376a7e5 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java @@ -172,10 +172,9 @@ private Object traverseMapValue(BMap map, Object currentJsonNod Object value = convertToBasicType(mapValue, currentFieldType); ((BMap) currentJsonNode).put(StringUtils.fromString(fieldNames.pop()), value); } - default -> { + default -> ((BMap) currentJsonNode).put(StringUtils.fromString(fieldName), traverseJson(mapValue, currentFieldType)); - } } } Map currentField = fieldHierarchy.pop(); From bda32fb8916be0c4b83763939b3dac5b7a1fba42 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Tue, 12 Mar 2024 12:58:40 +0530 Subject: [PATCH 20/27] Support option to disable data projection --- ballerina/json_api.bal | 6 +- ballerina/tests/from_json_with_options.bal | 301 ++++++++++++++++++ .../data/jsondata/io/DataReaderTask.java | 9 +- .../data/jsondata/json/JsonCreator.java | 12 +- .../stdlib/data/jsondata/json/JsonParser.java | 34 +- .../data/jsondata/json/JsonTraverse.java | 31 +- .../stdlib/data/jsondata/json/Native.java | 8 +- .../stdlib/data/jsondata/utils/Constants.java | 1 + .../jsondata/utils/DiagnosticErrorCode.java | 3 +- native/src/main/resources/error.properties | 3 + 10 files changed, 375 insertions(+), 33 deletions(-) create mode 100644 ballerina/tests/from_json_with_options.bal diff --git a/ballerina/json_api.bal b/ballerina/json_api.bal index cf667ab..683c77d 100644 --- a/ballerina/json_api.bal +++ b/ballerina/json_api.bal @@ -41,11 +41,11 @@ public isolated function fromJsonStringWithType(string|byte[]|stream numericPreference = decimal; + boolean allowDataProjection = true; }; # Represents the error type of the ballerina/data.jsondata module. This error type represents any error that can occur diff --git a/ballerina/tests/from_json_with_options.bal b/ballerina/tests/from_json_with_options.bal new file mode 100644 index 0000000..01ecce1 --- /dev/null +++ b/ballerina/tests/from_json_with_options.bal @@ -0,0 +1,301 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you 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. + +import ballerina/test; + +const options = { + allowDataProjection: false +}; + +@test:Config +isolated function testDisableDataProjectionInArrayTypeForFromJsonStringWithType() { + string jsonStr1 = string `[1, 2, 3, 4]`; + int[2]|error val1 = fromJsonStringWithType(jsonStr1, options); + test:assertTrue(val1 is error); + test:assertEquals((val1).message(), "array size is not compatible with the expected size"); + + string strVal2 = string `{ + "a": [1, 2, 3, 4, 5] + }`; + record {|int[2] a;|}|error val2 = fromJsonStringWithType(strVal2, options); + test:assertTrue(val2 is error); + test:assertEquals((val2).message(), "array size is not compatible with the expected size"); + + string strVal3 = string `{ + "a": [1, 2, 3, 4, 5], + "b": [1, 2, 3, 4, 5] + }`; + record {|int[2] a; int[3] b;|}|error val3 = fromJsonStringWithType(strVal3, options); + test:assertTrue(val3 is error); + test:assertEquals((val3).message(), "array size is not compatible with the expected size"); + + string strVal4 = string `{ + "employees": [ + { "name": "Prakanth", + "age": 26 + }, + { "name": "Kevin", + "age": 25 + } + ] + }`; + record {|record {|string name; int age;|}[1] employees;|}|error val4 = fromJsonStringWithType(strVal4, options); + test:assertTrue(val4 is error); + test:assertEquals((val4).message(), "array size is not compatible with the expected size"); + + string strVal5 = string `["1", 2, 3, { "a" : val_a }]`; + int[3]|error val5 = fromJsonStringWithType(strVal5, options); + test:assertTrue(val5 is error); + test:assertEquals((val5).message(), "array size is not compatible with the expected size"); +} + +@test:Config +isolated function testDisableDataProjectionInTupleTypeForFromJsonStringWithType() { + string str1 = string `[1, 2, 3, 4, 5, 8]`; + [string, float]|error val1 = fromJsonStringWithType(str1, options); + test:assertTrue(val1 is error); + test:assertEquals((val1).message(), "array size is not compatible with the expected size"); + + string str2 = string `{ + "a": [1, 2, 3, 4, 5, 8] + }`; + record {|[string, float] a;|}|error val2 = fromJsonStringWithType(str2, options); + test:assertTrue(val2 is error); + test:assertEquals((val2).message(), "array size is not compatible with the expected size"); + + string str3 = string `[1, "4"]`; + [float]|error val3 = fromJsonStringWithType(str3, options); + test:assertTrue(val3 is error); + test:assertEquals((val3).message(), "array size is not compatible with the expected size"); + + string str4 = string `["1", {}]`; + [float]|error val4 = fromJsonStringWithType(str4, options); + test:assertTrue(val4 is error); + test:assertEquals((val4).message(), "array size is not compatible with the expected size"); + + string str5 = string `["1", [], {"name": 1}]`; + [float]|error val5 = fromJsonStringWithType(str5, options); + test:assertTrue(val5 is error); + test:assertEquals((val5).message(), "array size is not compatible with the expected size"); +} + +@test:Config +isolated function testDisableDataProjectionInRecordTypeWithFromJsonStringWithType() { + string jsonStr1 = string `{"name": "John", "age": 30, "city": "New York"}`; + record {|string name; string city;|}|error val1 = fromJsonStringWithType(jsonStr1, options); + test:assertTrue(val1 is error); + test:assertEquals((val1).message(), "undefined field 'age'"); + + string jsonStr2 = string `{"name": John, "age": "30", "city": "New York"}`; + record {|string name; string city;|}|error val2 = fromJsonStringWithType(jsonStr2, options); + test:assertTrue(val2 is error); + test:assertEquals((val2).message(), "undefined field 'age'"); + + string jsonStr3 = string `{ "name": "John", + "company": { + "name": "wso2", + "year": 2024, + "addrees": { + "street": "123", + "city": "Berkeley" + } + }, + "city": "New York" }`; + record {|string name; string city;|}|error val3 = fromJsonStringWithType(jsonStr3, options); + test:assertTrue(val3 is error); + test:assertEquals((val3).message(), "undefined field 'company'"); + + string jsonStr4 = string `{ "name": "John", + "company": [{ + "name": "wso2", + "year": 2024, + "addrees": { + "street": "123", + "city": "Berkeley" + } + }], + "city": "New York" }`; + record {|string name; string city;|}|error val4 = fromJsonStringWithType(jsonStr4, options); + test:assertTrue(val4 is error); + test:assertEquals((val4).message(), "undefined field 'company'"); + + string jsonStr5 = string `{ "name": "John", + "company1": [{ + "name": "wso2", + "year": 2024, + "addrees": { + "street": "123", + "city": "Berkeley" + } + }], + "city": "New York", + "company2": [{ + "name": "amzn", + "year": 2024, + "addrees": { + "street": "123", + "city": "Miami" + } + }] + }`; + record {|string name; string city;|}|error val5 = fromJsonStringWithType(jsonStr5, options); + test:assertTrue(val5 is error); + test:assertEquals((val5).message(), "undefined field 'company1'"); +} + +@test:Config +isolated function testDisableDataProjectionInArrayTypeForFromJsonWithType() { + json jsonVal1 = [1, 2, 3, 4]; + int[2]|error val1 = fromJsonWithType(jsonVal1, options); + test:assertTrue(val1 is error); + test:assertEquals((val1).message(), "array size is not compatible with the expected size"); + + json jsonVal2 = { + a: [1, 2, 3, 4, 5] + }; + record {|int[2] a;|}|error val2 = fromJsonWithType(jsonVal2, options); + test:assertTrue(val2 is error); + test:assertEquals((val2).message(), "array size is not compatible with the expected size"); + + json jsonVal3 = { + a: [1, 2, 3, 4, 5], + b: [1, 2, 3, 4, 5] + }; + record {|int[2] a; int[3] b;|}|error val3 = fromJsonWithType(jsonVal3, options); + test:assertTrue(val3 is error); + test:assertEquals((val3).message(), "array size is not compatible with the expected size"); + + json jsonVal4 = { + employees: [ + { + name: "Prakanth", + age: 26 + }, + { + name: "Kevin", + age: 25 + } + ] + }; + record {|record {|string name; int age;|}[1] employees;|}|error val4 = fromJsonWithType(jsonVal4, options); + test:assertTrue(val4 is error); + test:assertEquals((val4).message(), "array size is not compatible with the expected size"); + + json jsonVal5 = ["1", 2, 3, {a: "val_a"}]; + int[3]|error val5 = fromJsonWithType(jsonVal5, options); + test:assertTrue(val5 is error); + test:assertEquals((val5).message(), "array size is not compatible with the expected size"); +} + +@test:Config +isolated function testDisableDataProjectionInTupleTypeForFromJsonWithType() { + json jsonVal1 = [1, 2, 3, 4, 5, 8]; + [int, int]|error val1 = fromJsonWithType(jsonVal1, options); + test:assertTrue(val1 is error); + test:assertEquals((val1).message(), "array size is not compatible with the expected size"); + + json jsonVal2 = { + a: [1, 2, 3, 4, 5, 8] + }; + record {|[int, int] a;|}|error val2 = fromJsonWithType(jsonVal2, options); + test:assertTrue(val2 is error); + test:assertEquals((val2).message(), "array size is not compatible with the expected size"); + + json jsonVal3 = [1, "4"]; + [int]|error val3 = fromJsonWithType(jsonVal3, options); + test:assertTrue(val3 is error); + test:assertEquals((val3).message(), "array size is not compatible with the expected size"); + + json jsonVal4 = ["1", {}]; + [string]|error val4 = fromJsonWithType(jsonVal4, options); + test:assertTrue(val4 is error); + test:assertEquals((val4).message(), "array size is not compatible with the expected size"); + + json jsonVal5 = ["1", [], {"name": 1}]; + [string]|error val5 = fromJsonWithType(jsonVal5, options); + test:assertTrue(val5 is error); + test:assertEquals((val5).message(), "array size is not compatible with the expected size"); +} + +@test:Config +isolated function testDisableDataProjectionInRecordTypeWithFromJsonWithType() { + json jsonVal1 = {"name": "John", "age": 30, "city": "New York"}; + record {|string name; string city;|}|error val1 = fromJsonWithType(jsonVal1, options); + test:assertTrue(val1 is error); + test:assertEquals((val1).message(), "undefined field 'age'"); + + json jsonVal2 = { + "name": "John", + "company": { + "name": "wso2", + "year": 2024, + "addrees": { + "street": "123", + "city": "Berkeley" + } + }, + "city": "New York" + }; + record {|string name; string city;|}|error val2 = fromJsonWithType(jsonVal2, options); + test:assertTrue(val2 is error); + test:assertEquals((val2).message(), "undefined field 'company'"); + + json jsonVal3 = { + "name": "John", + "company": [ + { + "name": "wso2", + "year": 2024, + "addrees": { + "street": "123", + "city": "Berkeley" + } + } + ], + "city": "New York" + }; + record {|string name; string city;|}|error val3 = fromJsonWithType(jsonVal3, options); + test:assertTrue(val3 is error); + test:assertEquals((val3).message(), "undefined field 'company'"); + + json jsonVal4 = { + "name": "John", + "company1": [ + { + "name": "wso2", + "year": 2024, + "addrees": { + "street": "123", + "city": "Berkeley" + } + } + ], + "city": "New York", + "company2": [ + { + "name": "amzn", + "year": 2024, + "addrees": { + "street": "123", + "city": "Miami" + } + } + ] + }; + record {|string name; string city;|}|error val4 = fromJsonWithType(jsonVal4, options); + test:assertTrue(val4 is error); + test:assertEquals((val4).message(), "undefined field 'company1'"); +} diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderTask.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderTask.java index 159c980..361d147 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderTask.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderTask.java @@ -22,7 +22,9 @@ import io.ballerina.runtime.api.types.MethodType; import io.ballerina.runtime.api.types.ObjectType; import io.ballerina.runtime.api.utils.TypeUtils; +import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BObject; +import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.api.values.BTypedesc; import io.ballerina.stdlib.data.jsondata.json.JsonParser; import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; @@ -44,12 +46,15 @@ public class DataReaderTask implements Runnable { private final BObject iteratorObj; private final Future future; private final BTypedesc typed; + private final BMap options; - public DataReaderTask(Environment env, BObject iteratorObj, Future future, BTypedesc typed) { + public DataReaderTask(Environment env, BObject iteratorObj, Future future, BTypedesc typed, BMap options) { this.env = env; this.iteratorObj = iteratorObj; this.future = future; this.typed = typed; + this.options = options; } static MethodType resolveNextMethod(BObject iterator) { @@ -81,7 +86,7 @@ public void run() { ResultConsumer resultConsumer = new ResultConsumer<>(future); try (var byteBlockSteam = new BallerinaByteBlockInputStream(env, iteratorObj, resolveNextMethod(iteratorObj), resolveCloseMethod(iteratorObj), resultConsumer)) { - Object result = JsonParser.parse(new InputStreamReader(byteBlockSteam), typed.getDescribingType()); + Object result = JsonParser.parse(new InputStreamReader(byteBlockSteam), options, typed.getDescribingType()); future.complete(result); } catch (Exception e) { future.complete(DiagnosticLog.getJsonError("Error occurred while reading the stream: " + e.getMessage())); diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java index 48b8a7b..9643a0b 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java @@ -208,7 +208,7 @@ static void updateRecordFieldValue(BString fieldName, Object parent, Object curr } } - static Type getMemberType(Type expectedType, int index) { + static Type getMemberType(Type expectedType, int index, boolean allowDataProjection) { if (expectedType == null) { return null; } @@ -219,12 +219,20 @@ static Type getMemberType(Type expectedType, int index) { || arrayType.getState() == ArrayType.ArrayState.CLOSED && index < arrayType.getSize()) { return arrayType.getElementType(); } + + if (!allowDataProjection) { + throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); + } return null; } else if (expectedType.getTag() == TypeTags.TUPLE_TAG) { TupleType tupleType = (TupleType) expectedType; List tupleTypes = tupleType.getTupleTypes(); if (tupleTypes.size() < index + 1) { - return tupleType.getRestType(); + Type restType = tupleType.getRestType(); + if (restType == null && !allowDataProjection) { + throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); + } + return restType; } return tupleTypes.get(index); } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java index 5682add..87bf641 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java @@ -31,7 +31,9 @@ import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BError; +import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; +import io.ballerina.stdlib.data.jsondata.utils.Constants; import io.ballerina.stdlib.data.jsondata.utils.DiagnosticErrorCode; import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; import org.apache.commons.lang3.StringEscapeUtils; @@ -57,15 +59,16 @@ public class JsonParser { /** * Parses the contents in the given {@link Reader} and returns a json. * - * @param reader reader which contains the JSON content + * @param reader reader which contains the JSON content + * @param options represent the options that can be used to modify the behaviour of conversion * @return JSON structure * @throws BError for any parsing error */ - public static Object parse(Reader reader, Type type) + public static Object parse(Reader reader, BMap options, Type type) throws BError { StateMachine sm = tlStateMachine.get(); try { - return sm.execute(reader, TypeUtils.getReferredType(type)); + return sm.execute(reader, options, TypeUtils.getReferredType(type)); } finally { // Need to reset the state machine before leaving. Otherwise, references to the created // JSON values will be maintained and the java GC will not happen properly. @@ -140,6 +143,7 @@ static class StateMachine { private int line; private int column; private char currentQuoteChar; + boolean allowDataProjection; Field currentField; Stack> fieldHierarchy = new Stack<>(); Stack> visitedFieldHierarchy = new Stack<>(); @@ -185,7 +189,7 @@ private void processLocation(char ch) { } } - public Object execute(Reader reader, Type type) throws BError { + public Object execute(Reader reader, BMap options, Type type) throws BError { switch (type.getTag()) { // TODO: Handle readonly and singleton type as expType. case TypeTags.RECORD_TYPE_TAG -> { @@ -218,6 +222,8 @@ public Object execute(Reader reader, Type type) throws BError { default -> throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE, type); } + allowDataProjection = (boolean) options.get(Constants.ALLOW_DATA_PROJECTION); + State currentState = DOC_START_STATE; try { char[] buff = new char[1024]; @@ -510,17 +516,17 @@ public State transition(StateMachine sm, char[] buff, int i, int count) { state = STRING_ARRAY_ELEMENT_STATE; sm.currentQuoteChar = ch; sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), - sm.arrayIndexes.peek())); + sm.arrayIndexes.peek(), sm.allowDataProjection)); } else if (ch == '{') { // Get member type of the array and set as expected type. sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), - sm.arrayIndexes.peek())); + sm.arrayIndexes.peek(), sm.allowDataProjection)); JsonCreator.updateNextMapValue(sm); state = FIRST_FIELD_READY_STATE; } else if (ch == '[') { // Get member type of the array and set as expected type. sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), - sm.arrayIndexes.peek())); + sm.arrayIndexes.peek(), sm.allowDataProjection)); sm.updateNextArrayValue(); state = FIRST_ARRAY_ELEMENT_READY_STATE; } else if (ch == ']') { @@ -528,7 +534,7 @@ public State transition(StateMachine sm, char[] buff, int i, int count) { } else { state = NON_STRING_ARRAY_ELEMENT_STATE; sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), - sm.arrayIndexes.peek())); + sm.arrayIndexes.peek(), sm.allowDataProjection)); } break; } @@ -588,20 +594,20 @@ public State transition(StateMachine sm, char[] buff, int i, int count) { state = STRING_ARRAY_ELEMENT_STATE; sm.currentQuoteChar = ch; sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), - sm.arrayIndexes.peek())); + sm.arrayIndexes.peek(), sm.allowDataProjection)); } else if (ch == '{') { sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), - sm.arrayIndexes.peek())); + sm.arrayIndexes.peek(), sm.allowDataProjection)); JsonCreator.updateNextMapValue(sm); state = FIRST_FIELD_READY_STATE; } else if (ch == '[') { sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), - sm.arrayIndexes.peek())); + sm.arrayIndexes.peek(), sm.allowDataProjection)); sm.updateNextArrayValue(); state = FIRST_ARRAY_ELEMENT_READY_STATE; } else { sm.expectedTypes.push(JsonCreator.getMemberType(sm.expectedTypes.peek(), - sm.arrayIndexes.peek())); + sm.arrayIndexes.peek(), sm.allowDataProjection)); state = NON_STRING_ARRAY_ELEMENT_STATE; } break; @@ -656,6 +662,10 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J sm.visitedFieldHierarchy.peek().put(jsonFieldName, currentField); } sm.expectedTypes.push(fieldType); + + if (!sm.allowDataProjection && fieldType == null) { + throw DiagnosticLog.error(DiagnosticErrorCode.UNDEFINED_FIELD, jsonFieldName); + } } else if (sm.expectedTypes.peek() == null) { sm.expectedTypes.push(null); } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java index 376a7e5..1be8c7e 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java @@ -36,6 +36,7 @@ import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; +import io.ballerina.stdlib.data.jsondata.utils.Constants; import io.ballerina.stdlib.data.jsondata.utils.DiagnosticErrorCode; import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; @@ -55,9 +56,10 @@ public class JsonTraverse { private static final ThreadLocal tlJsonTree = ThreadLocal.withInitial(JsonTree::new); - public static Object traverse(Object json, Type type) { + public static Object traverse(Object json, BMap options, Type type) { JsonTree jsonTree = tlJsonTree.get(); try { + jsonTree.allowDataProjection = (boolean) options.get(Constants.ALLOW_DATA_PROJECTION); return jsonTree.traverseJson(json, type); } catch (BError e) { return e; @@ -72,6 +74,7 @@ static class JsonTree { Stack restType = new Stack<>(); Deque fieldNames = new ArrayDeque<>(); Type rootArray; + boolean allowDataProjection; void reset() { currentField = null; @@ -130,10 +133,10 @@ public Object traverseJson(Object json, Type type) { } private Object traverseMapJsonOrArrayJson(Object json, Object currentJsonNode, Type type) { - if (json instanceof BMap map) { - return traverseMapValue(map, currentJsonNode); - } else if (json instanceof BArray) { - return traverseArrayValue(json, currentJsonNode); + if (json instanceof BMap bMap) { + return traverseMapValue(bMap, currentJsonNode); + } else if (json instanceof BArray bArray) { + return traverseArrayValue(bArray, currentJsonNode); } else { // JSON value not compatible with map or array. if (type.getTag() == TypeTags.RECORD_TYPE_TAG) { @@ -157,7 +160,10 @@ private Object traverseMapValue(BMap map, Object currentJsonNod Type restFieldType = TypeUtils.getReferredType(restType.peek()); addRestField(restFieldType, key, map.get(key), currentJsonNode); } - continue; + if (allowDataProjection) { + continue; + } + throw DiagnosticLog.error(DiagnosticErrorCode.UNDEFINED_FIELD, key); } String fieldName = currentField.getFieldName(); @@ -183,15 +189,20 @@ private Object traverseMapValue(BMap map, Object currentJsonNod return currentJsonNode; } - private Object traverseArrayValue(Object json, Object currentJsonNode) { - BArray array = (BArray) json; + private Object traverseArrayValue(BArray array, Object currentJsonNode) { switch (rootArray.getTag()) { case TypeTags.ARRAY_TAG -> { ArrayType arrayType = (ArrayType) rootArray; int expectedArraySize = arrayType.getSize(); - if (expectedArraySize > array.getLength()) { + long sourceArraySize = array.getLength(); + if (expectedArraySize > sourceArraySize) { throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); } + + if (!allowDataProjection && expectedArraySize < sourceArraySize) { + throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); + } + Type elementType = arrayType.getElementType(); if (expectedArraySize == -1) { traverseArrayMembers(array.getLength(), array, elementType, currentJsonNode); @@ -213,6 +224,8 @@ private Object traverseArrayValue(Object json, Object currentJsonNode) { nextJsonNode = traverseJson(jsonMember, tupleType.getTupleTypes().get(i)); } else if (restType != null) { nextJsonNode = traverseJson(jsonMember, restType); + } else if (!allowDataProjection) { + throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); } else { continue; } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java index d2ccfd7..774fea1 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java @@ -47,7 +47,7 @@ public class Native { public static Object fromJsonWithType(Object json, BMap options, BTypedesc typed) { try { - return JsonTraverse.traverse(json, typed.getDescribingType()); + return JsonTraverse.traverse(json, options, typed.getDescribingType()); } catch (BError e) { return e; } @@ -58,15 +58,15 @@ public static Object fromJsonStringWithType(Environment env, Object json, BMap Date: Tue, 12 Mar 2024 17:03:01 +0530 Subject: [PATCH 21/27] Use module prefix when validating annotations --- ballerina/json_api.bal | 2 +- .../data/jsondata/compiler/Constants.java | 2 +- .../compiler/JsondataTypeValidator.java | 17 ++++++++++++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/ballerina/json_api.bal b/ballerina/json_api.bal index 683c77d..b41c743 100644 --- a/ballerina/json_api.bal +++ b/ballerina/json_api.bal @@ -54,7 +54,7 @@ public type Error distinct error; # Defines the name of the JSON Object key. # -# + value - The name of the JSON Object key. +# + value - The name of the JSON Object key public type NameConfig record {| string value; |}; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java index 6338faa..276c91c 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java @@ -25,6 +25,6 @@ */ public class Constants { static final String FROM_JSON_STRING_WITH_TYPE = "fromJsonStringWithType"; - static final String FROM_JSON_WITH_TYPE = "fromJsonWithType"; public static final String NAME = "Name"; + public static final String JSONDATA = "jsondata"; } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java index 3852588..d79e7a4 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java @@ -20,7 +20,9 @@ import io.ballerina.compiler.api.SemanticModel; import io.ballerina.compiler.api.symbols.AnnotationAttachmentSymbol; +import io.ballerina.compiler.api.symbols.AnnotationSymbol; import io.ballerina.compiler.api.symbols.ArrayTypeSymbol; +import io.ballerina.compiler.api.symbols.ModuleSymbol; import io.ballerina.compiler.api.symbols.RecordFieldSymbol; import io.ballerina.compiler.api.symbols.RecordTypeSymbol; import io.ballerina.compiler.api.symbols.Symbol; @@ -261,7 +263,11 @@ private void detectDuplicateFields(RecordTypeSymbol recordTypeSymbol, SyntaxNode private String getNameFromAnnotation(String fieldName, List annotationAttachments) { for (AnnotationAttachmentSymbol annotAttSymbol : annotationAttachments) { - Optional nameAnnot = annotAttSymbol.typeDescriptor().getName(); + AnnotationSymbol annotation = annotAttSymbol.typeDescriptor(); + if (!getAnnotModuleName(annotation).contains(Constants.JSONDATA)) { + continue; + } + Optional nameAnnot = annotation.getName(); if (nameAnnot.isEmpty()) { continue; } @@ -273,4 +279,13 @@ private String getNameFromAnnotation(String fieldName, } return fieldName; } + + private String getAnnotModuleName(AnnotationSymbol annotation) { + Optional moduleSymbol = annotation.getModule(); + if (moduleSymbol.isEmpty()) { + return ""; + } + Optional moduleName = moduleSymbol.get().getName(); + return moduleName.orElse(""); + } } From bd954d2637982fd112e1a4826dc0e067be249e8e Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Thu, 14 Mar 2024 22:36:22 +0530 Subject: [PATCH 22/27] Support sub type of int as expected type --- ballerina/tests/from_json_string_test.bal | 381 +++++++++++++++++- ballerina/tests/from_json_test.bal | 138 ++++++- .../stdlib/data/jsondata/FromString.java | 112 +++++ .../data/jsondata/json/JsonCreator.java | 2 + .../stdlib/data/jsondata/json/JsonParser.java | 6 +- .../data/jsondata/json/JsonTraverse.java | 29 +- native/src/main/resources/error.properties | 2 +- 7 files changed, 647 insertions(+), 23 deletions(-) diff --git a/ballerina/tests/from_json_string_test.bal b/ballerina/tests/from_json_string_test.bal index 78c6afa..5b3bff3 100644 --- a/ballerina/tests/from_json_string_test.bal +++ b/ballerina/tests/from_json_string_test.bal @@ -37,6 +37,9 @@ isolated function testJsonStringToBasicTypes() returns error? { () val6 = check fromJsonStringWithType("null"); test:assertEquals(val6, null); + + string val7 = check fromJsonStringWithType(""); + test:assertEquals(val7, ""); } @test:Config @@ -969,6 +972,276 @@ function testNameAnnotationWithFromJsonStringWithType() returns error? { test:assertEquals(book.author, "J.K. Rowling"); } +@test:Config +isolated function testByteAsExpectedTypeForFromJsonStringWithType() returns error? { + byte val1 = check fromJsonStringWithType("1"); + test:assertEquals(val1, 1); + + [byte, int] val2 = check fromJsonStringWithType("[255, 2000]"); + test:assertEquals(val2, [255, 2000]); + + string str4 = string `{ + "id": 1, + "name": "Anne", + "address": { + "street": "Main", + "city": "94", + "id": 2 + } + }`; + + record { + byte id; + string name; + record { + string street; + string city; + byte id; + } address; + } val4 = check fromJsonStringWithType(str4); + test:assertEquals(val4.length(), 3); + test:assertEquals(val4.id, 1); + test:assertEquals(val4.name, "Anne"); + test:assertEquals(val4.address.length(), 3); + test:assertEquals(val4.address.street, "Main"); + test:assertEquals(val4.address.city, "94"); + test:assertEquals(val4.address.id, 2); +} + +@test:Config +isolated function testSignedInt8AsExpectedTypeForFromJsonStringWithType() returns error? { + int:Signed8 val1 = check fromJsonStringWithType("-128"); + test:assertEquals(val1, -128); + + int:Signed8 val2 = check fromJsonStringWithType("127"); + test:assertEquals(val2, 127); + + [int:Signed8, int] val3 = check fromJsonStringWithType("[127, 2000]"); + test:assertEquals(val3, [127, 2000]); + + string str4 = string `{ + "id": 100, + "name": "Anne", + "address": { + "street": "Main", + "city": "94", + "id": -2 + } + }`; + + record { + int:Signed8 id; + string name; + record { + string street; + string city; + int:Signed8 id; + } address; + } val4 = check fromJsonStringWithType(str4); + test:assertEquals(val4.length(), 3); + test:assertEquals(val4.id, 100); + test:assertEquals(val4.name, "Anne"); + test:assertEquals(val4.address.length(), 3); + test:assertEquals(val4.address.street, "Main"); + test:assertEquals(val4.address.city, "94"); + test:assertEquals(val4.address.id, -2); +} + +@test:Config +isolated function testSignedInt16AsExpectedTypeForFromJsonStringWithType() returns error? { + int:Signed16 val1 = check fromJsonStringWithType("-32768"); + test:assertEquals(val1, -32768); + + int:Signed16 val2 = check fromJsonStringWithType("32767"); + test:assertEquals(val2, 32767); + + [int:Signed16, int] val3 = check fromJsonStringWithType("[32767, -324234]"); + test:assertEquals(val3, [32767, -324234]); + + string str4 = string `{ + "id": 100, + "name": "Anne", + "address": { + "street": "Main", + "city": "94", + "id": -2 + } + }`; + + record { + int:Signed16 id; + string name; + record { + string street; + string city; + int:Signed16 id; + } address; + } val4 = check fromJsonStringWithType(str4); + test:assertEquals(val4.length(), 3); + test:assertEquals(val4.id, 100); + test:assertEquals(val4.name, "Anne"); + test:assertEquals(val4.address.length(), 3); + test:assertEquals(val4.address.street, "Main"); + test:assertEquals(val4.address.city, "94"); + test:assertEquals(val4.address.id, -2); +} + +@test:Config +isolated function testSignedInt32AsExpectedTypeForFromJsonStringWithType() returns error? { + int:Signed32 val1 = check fromJsonStringWithType("-2147483648"); + test:assertEquals(val1, -2147483648); + + int:Signed32 val2 = check fromJsonStringWithType("2147483647"); + test:assertEquals(val2, 2147483647); + + int:Signed32[] val3 = check fromJsonStringWithType("[2147483647, -2147483648]"); + test:assertEquals(val3, [2147483647, -2147483648]); + + string str4 = string `{ + "id": 2147483647, + "name": "Anne", + "address": { + "street": "Main", + "city": "94", + "id": -2147483648 + } + }`; + + record { + int:Signed32 id; + string name; + record { + string street; + string city; + int:Signed32 id; + } address; + } val4 = check fromJsonStringWithType(str4); + test:assertEquals(val4.length(), 3); + test:assertEquals(val4.id, 2147483647); + test:assertEquals(val4.name, "Anne"); + test:assertEquals(val4.address.length(), 3); + test:assertEquals(val4.address.street, "Main"); + test:assertEquals(val4.address.city, "94"); + test:assertEquals(val4.address.id, -2147483648); +} + +@test:Config +isolated function testUnSignedInt8AsExpectedTypeForFromJsonStringWithType() returns error? { + int:Unsigned8 val1 = check fromJsonStringWithType("255"); + test:assertEquals(val1, 255); + + int:Unsigned8 val2 = check fromJsonStringWithType("0"); + test:assertEquals(val2, 0); + + int:Unsigned8[] val3 = check fromJsonStringWithType("[0, 255]"); + test:assertEquals(val3, [0, 255]); + + string str4 = string `{ + "id": 0, + "name": "Anne", + "address": { + "street": "Main", + "city": "94", + "id": 255 + } + }`; + + record { + int:Unsigned8 id; + string name; + record { + string street; + string city; + int:Unsigned8 id; + } address; + } val4 = check fromJsonStringWithType(str4); + test:assertEquals(val4.length(), 3); + test:assertEquals(val4.id, 0); + test:assertEquals(val4.name, "Anne"); + test:assertEquals(val4.address.length(), 3); + test:assertEquals(val4.address.street, "Main"); + test:assertEquals(val4.address.city, "94"); + test:assertEquals(val4.address.id, 255); +} + +@test:Config +isolated function testUnSignedInt16AsExpectedTypeForFromJsonStringWithType() returns error? { + int:Unsigned16 val1 = check fromJsonStringWithType("65535"); + test:assertEquals(val1, 65535); + + int:Unsigned16 val2 = check fromJsonStringWithType("0"); + test:assertEquals(val2, 0); + + int:Unsigned16[] val3 = check fromJsonStringWithType("[0, 65535]"); + test:assertEquals(val3, [0, 65535]); + + string str4 = string `{ + "id": 0, + "name": "Anne", + "address": { + "street": "Main", + "city": "94", + "id": 65535 + } + }`; + + record { + int:Unsigned16 id; + string name; + record { + string street; + string city; + int:Unsigned16 id; + } address; + } val4 = check fromJsonStringWithType(str4); + test:assertEquals(val4.length(), 3); + test:assertEquals(val4.id, 0); + test:assertEquals(val4.name, "Anne"); + test:assertEquals(val4.address.length(), 3); + test:assertEquals(val4.address.street, "Main"); + test:assertEquals(val4.address.city, "94"); + test:assertEquals(val4.address.id, 65535); +} + +@test:Config +isolated function testUnSignedInt32AsExpectedTypeForFromJsonStringWithType() returns error? { + int:Unsigned32 val1 = check fromJsonStringWithType("4294967295"); + test:assertEquals(val1, 4294967295); + + int:Unsigned32 val2 = check fromJsonStringWithType("0"); + test:assertEquals(val2, 0); + + int:Unsigned32[] val3 = check fromJsonStringWithType("[0, 4294967295]"); + test:assertEquals(val3, [0, 4294967295]); + + string str4 = string `{ + "id": 0, + "name": "Anne", + "address": { + "street": "Main", + "city": "94", + "id": 4294967295 + } + }`; + + record { + int:Unsigned32 id; + string name; + record { + string street; + string city; + int:Unsigned32 id; + } address; + } val4 = check fromJsonStringWithType(str4); + test:assertEquals(val4.length(), 3); + test:assertEquals(val4.id, 0); + test:assertEquals(val4.name, "Anne"); + test:assertEquals(val4.address.length(), 3); + test:assertEquals(val4.address.street, "Main"); + test:assertEquals(val4.address.city, "94"); + test:assertEquals(val4.address.id, 4294967295); +} + // Negative tests for fromJsonStringWithType() function. @test:Config @@ -1027,7 +1300,7 @@ isolated function testFromJsonStringWithTypeNegative4() returns error? { Union|Error y = fromJsonStringWithType(str); test:assertTrue(y is error); - test:assertEquals((y).message(), "invalid type 'ballerina/data.jsondata:0:Union' expected 'map type'"); + test:assertEquals((y).message(), "unsupported type 'ballerina/data.jsondata:0:Union'"); table|Error z = fromJsonStringWithType(str); test:assertTrue(z is error); @@ -1070,3 +1343,109 @@ isolated function testProjectionInArrayNegativeForFromJsonStringWithType() { test:assertTrue(val1 is error); test:assertEquals((val1).message(), "invalid type 'int' expected 'map type'"); } + +@test:Config +isolated function testUnionTypeAsExpTypeForFromJsonStringWithTypeNegative() { + string str1 = string `[ + 123, + "Lakshan", + { + "city": "Colombo", + "street": "123", + "zip": 123 + }, + { + "code": 123, + "subject": "Bio" + } + ]`; + (map|int|float)[]|error err1 = fromJsonStringWithType(str1); + test:assertTrue(err1 is error); + test:assertEquals(( err1).message(), "incompatible expected type '(map|int|float)' for value 'Lakshan'"); + + string str2 = string `[ + { + "city": "Colombo", + "street": "123", + "zip": 123 + }, + { + "code": 123, + "subject": "Bio" + } + ]`; + (map|int|float)[]|error err2 = fromJsonStringWithType(str2); + test:assertTrue(err2 is error); + test:assertEquals(( err2).message(), "unsupported type '(map|int|float)'"); + + string str3 = string `{ + "a": "hello", + "b": 1, + "c": { + "d": "world", + "e": 2 + } + }`; + (map|int|float)|error err3 = fromJsonStringWithType(str3); + test:assertTrue(err3 is error); + test:assertEquals(( err3).message(), "unsupported type '(map|int|float)'"); +} + +@test:Config +function testSubTypeOfIntAsExptypeNegative() { + byte|error err1 = fromJsonStringWithType("256"); + test:assertTrue(err1 is error); + test:assertEquals(( err1).message(), "incompatible expected type 'byte' for value '256'"); + + byte|error err2 = fromJsonStringWithType("-1"); + test:assertTrue(err2 is error); + test:assertEquals(( err2).message(), "incompatible expected type 'byte' for value '-1'"); + + int:Signed8|error err3 = fromJsonStringWithType("128"); + test:assertTrue(err3 is error); + test:assertEquals(( err3).message(), "incompatible expected type 'lang.int:Signed8' for value '128'"); + + int:Signed8|error err4 = fromJsonStringWithType("-129"); + test:assertTrue(err4 is error); + test:assertEquals(( err4).message(), "incompatible expected type 'lang.int:Signed8' for value '-129'"); + + int:Unsigned8|error err5 = fromJsonStringWithType("256"); + test:assertTrue(err5 is error); + test:assertEquals(( err5).message(), "incompatible expected type 'lang.int:Unsigned8' for value '256'"); + + int:Unsigned8|error err6 = fromJsonStringWithType("-1"); + test:assertTrue(err6 is error); + test:assertEquals(( err6).message(), "incompatible expected type 'lang.int:Unsigned8' for value '-1'"); + + int:Signed16|error err7 = fromJsonStringWithType("32768"); + test:assertTrue(err7 is error); + test:assertEquals(( err7).message(), "incompatible expected type 'lang.int:Signed16' for value '32768'"); + + int:Signed16|error err8 = fromJsonStringWithType("-32769"); + test:assertTrue(err8 is error); + test:assertEquals(( err8).message(), "incompatible expected type 'lang.int:Signed16' for value '-32769'"); + + int:Unsigned16|error err9 = fromJsonStringWithType("65536"); + test:assertTrue(err9 is error); + test:assertEquals(( err9).message(), "incompatible expected type 'lang.int:Unsigned16' for value '65536'"); + + int:Unsigned16|error err10 = fromJsonStringWithType("-1"); + test:assertTrue(err10 is error); + test:assertEquals(( err10).message(), "incompatible expected type 'lang.int:Unsigned16' for value '-1'"); + + int:Signed32|error err11 = fromJsonStringWithType("2147483648"); + test:assertTrue(err11 is error); + test:assertEquals(( err11).message(), "incompatible expected type 'lang.int:Signed32' for value '2147483648'"); + + int:Signed32|error err12 = fromJsonStringWithType("-2147483649"); + test:assertTrue(err12 is error); + test:assertEquals(( err12).message(), "incompatible expected type 'lang.int:Signed32' for value '-2147483649'"); + + int:Unsigned32|error err13 = fromJsonStringWithType("4294967296"); + test:assertTrue(err13 is error); + test:assertEquals(( err13).message(), "incompatible expected type 'lang.int:Unsigned32' for value '4294967296'"); + + int:Unsigned32|error err14 = fromJsonStringWithType("-1"); + test:assertTrue(err14 is error); + test:assertEquals(( err14).message(), "incompatible expected type 'lang.int:Unsigned32' for value '-1'"); +} diff --git a/ballerina/tests/from_json_test.bal b/ballerina/tests/from_json_test.bal index 0c81814..deca97f 100644 --- a/ballerina/tests/from_json_test.bal +++ b/ballerina/tests/from_json_test.bal @@ -553,7 +553,7 @@ isolated function testFromJsonWithType20() returns error? { @test:Config isolated function testUnionTypeAsExpTypeForFromJsonWithType() returns error? { decimal|float val1 = check fromJsonWithType(1.0); - test:assertEquals(val1, 1.0); + test:assertEquals(val1, 1.0d); json jsonVal2 = { "a": "hello", @@ -564,7 +564,7 @@ isolated function testUnionTypeAsExpTypeForFromJsonWithType() returns error? { decimal|float b; |} val2 = check fromJsonWithType(jsonVal2); test:assertEquals(val2.length(), 1); - test:assertEquals(val2.b, 1.0); + test:assertEquals(val2.b, 1.0d); json jsonVal3 = { "a": { @@ -577,14 +577,14 @@ isolated function testUnionTypeAsExpTypeForFromJsonWithType() returns error? { }; record {| - record {| decimal|int b; record {| string|boolean e; |} d; |} a; + record {| int|decimal b; record {| string|boolean e; |} d; |} a; decimal|float c; |} val3 = check fromJsonWithType(jsonVal3); test:assertEquals(val3.length(), 2); test:assertEquals(val3.a.length(), 2); test:assertEquals(val3.a.b, 1); test:assertEquals(val3.a.d.e, false); - test:assertEquals(val3.c, 2.0); + test:assertEquals(val3.c, 2.0d); } @test:Config @@ -942,6 +942,77 @@ function testNameAnnotationWithFromJsonWithType() returns error? { test:assertEquals(book.author, "J.K. Rowling"); } +@test:Config +function testSubTypeOfIntAsExpectedTypeWithFromJsonWithType() returns error? { + byte val1 = check fromJsonWithType(255); + test:assertEquals(val1, 255); + + int:Unsigned8 val2 = check fromJsonWithType(255); + test:assertEquals(val2, 255); + + int jsonVal2 = 0; + byte val3 = check fromJsonWithType(jsonVal2); + test:assertEquals(val3, 0); + + int:Unsigned8 val4 = check fromJsonWithType(jsonVal2); + test:assertEquals(val4, 0); + + int:Signed8 val5 = check fromJsonWithType(127); + test:assertEquals(val5, 127); + + int:Signed8 val6 = check fromJsonWithType(-128); + test:assertEquals(val6, -128); + + int:Unsigned16 val7 = check fromJsonWithType(65535); + test:assertEquals(val7, 65535); + + int:Unsigned16 val8 = check fromJsonWithType(0); + test:assertEquals(val8, 0); + + int:Signed16 val9 = check fromJsonWithType(32767); + test:assertEquals(val9, 32767); + + int:Signed16 val10 = check fromJsonWithType(-32768); + test:assertEquals(val10, -32768); + + int:Unsigned32 val11 = check fromJsonWithType(4294967295); + test:assertEquals(val11, 4294967295); + + int:Unsigned32 val12 = check fromJsonWithType(0); + test:assertEquals(val12, 0); + + int:Signed32 val13 = check fromJsonWithType(2147483647); + test:assertEquals(val13, 2147483647); + + int:Signed32 val14 = check fromJsonWithType(-2147483648); + test:assertEquals(val14, -2147483648); + + int[] jsonVal3 = [255, 127, 32767, 2147483647, 255, 32767, 2147483647]; + [byte, int:Signed8, int:Signed16, int:Signed32, int:Unsigned8, int:Unsigned16, int:Unsigned32] val15 = + check fromJsonWithType(jsonVal3); + test:assertEquals(val15, [255, 127, 32767, 2147483647, 255, 32767, 2147483647]); + + json jsonVal4 = { + "a": 1, + "b": 127, + "c": 32767, + "d": 2147483647, + "e": 255, + "f": 32767, + "g": 2147483647 + }; + record {| + byte a; + int:Signed8 b; + int:Signed16 c; + int:Signed32 d; + int:Unsigned8 e; + int:Unsigned16 f; + int:Unsigned32 g; + |} val16 = check fromJsonWithType(jsonVal4); + test:assertEquals(val16, {a: 1, b: 127, c: 32767, d: 2147483647, e: 255, f: 32767, g: 2147483647}); +} + // Negative tests for fromJsonWithType() function. @test:Config @@ -1060,3 +1131,62 @@ isolated function testProjectionInArrayNegativeForFromJsonWithType() { test:assertTrue(val5 is error); test:assertEquals((val5).message(), "incompatible expected type 'int' for value '{\"a\":2}'"); } + +@test:Config +function testSubTypeOfIntAsExptypeWithFromJsonWithTypeNegative() { + byte|error err1 = fromJsonWithType(256); + test:assertTrue(err1 is error); + test:assertEquals(( err1).message(), "incompatible expected type 'byte' for value '256'"); + + byte|error err2 = fromJsonWithType(-1); + test:assertTrue(err2 is error); + test:assertEquals(( err2).message(), "incompatible expected type 'byte' for value '-1'"); + + int:Signed8|error err3 = fromJsonWithType(128); + test:assertTrue(err3 is error); + test:assertEquals(( err3).message(), "incompatible expected type 'lang.int:Signed8' for value '128'"); + + int:Signed8|error err4 = fromJsonWithType(-129); + test:assertTrue(err4 is error); + test:assertEquals(( err4).message(), "incompatible expected type 'lang.int:Signed8' for value '-129'"); + + int:Unsigned8|error err5 = fromJsonWithType(256); + test:assertTrue(err5 is error); + test:assertEquals(( err5).message(), "incompatible expected type 'lang.int:Unsigned8' for value '256'"); + + int:Unsigned8|error err6 = fromJsonWithType(-1); + test:assertTrue(err6 is error); + test:assertEquals(( err6).message(), "incompatible expected type 'lang.int:Unsigned8' for value '-1'"); + + int:Signed16|error err7 = fromJsonWithType(32768); + test:assertTrue(err7 is error); + test:assertEquals(( err7).message(), "incompatible expected type 'lang.int:Signed16' for value '32768'"); + + int:Signed16|error err8 = fromJsonWithType(-32769); + test:assertTrue(err8 is error); + test:assertEquals(( err8).message(), "incompatible expected type 'lang.int:Signed16' for value '-32769'"); + + int:Unsigned16|error err9 = fromJsonWithType(65536); + test:assertTrue(err9 is error); + test:assertEquals(( err9).message(), "incompatible expected type 'lang.int:Unsigned16' for value '65536'"); + + int:Unsigned16|error err10 = fromJsonWithType(-1); + test:assertTrue(err10 is error); + test:assertEquals(( err10).message(), "incompatible expected type 'lang.int:Unsigned16' for value '-1'"); + + int:Signed32|error err11 = fromJsonWithType(2147483648); + test:assertTrue(err11 is error); + test:assertEquals(( err11).message(), "incompatible expected type 'lang.int:Signed32' for value '2147483648'"); + + int:Signed32|error err12 = fromJsonWithType(-2147483649); + test:assertTrue(err12 is error); + test:assertEquals(( err12).message(), "incompatible expected type 'lang.int:Signed32' for value '-2147483649'"); + + int:Unsigned32|error err13 = fromJsonWithType(4294967296); + test:assertTrue(err13 is error); + test:assertEquals(( err13).message(), "incompatible expected type 'lang.int:Unsigned32' for value '4294967296'"); + + int:Unsigned32|error err14 = fromJsonWithType(-1); + test:assertTrue(err14 is error); + test:assertEquals(( err14).message(), "incompatible expected type 'lang.int:Unsigned32' for value '-1'"); +} diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java index b8c16d8..fa18e70 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java @@ -63,6 +63,17 @@ public class FromString { PredefinedTypes.TYPE_STRING ); private static final UnionType JSON_TYPE_WITH_BASIC_TYPES = TypeCreator.createUnionType(BASIC_JSON_MEMBER_TYPES); + public static final Integer BBYTE_MIN_VALUE = 0; + public static final Integer BBYTE_MAX_VALUE = 255; + public static final Integer SIGNED32_MAX_VALUE = 2147483647; + public static final Integer SIGNED32_MIN_VALUE = -2147483648; + public static final Integer SIGNED16_MAX_VALUE = 32767; + public static final Integer SIGNED16_MIN_VALUE = -32768; + public static final Integer SIGNED8_MAX_VALUE = 127; + public static final Integer SIGNED8_MIN_VALUE = -128; + public static final Long UNSIGNED32_MAX_VALUE = 4294967295L; + public static final Integer UNSIGNED16_MAX_VALUE = 65535; + public static final Integer UNSIGNED8_MAX_VALUE = 255; public static Object fromStringWithType(BString string, BTypedesc typed) { Type expType = typed.getDescribingType(); @@ -80,6 +91,20 @@ public static Object fromStringWithType(BString string, Type expType) { switch (expType.getTag()) { case TypeTags.INT_TAG: return stringToInt(value); + case TypeTags.BYTE_TAG: + return stringToByte(value); + case TypeTags.SIGNED8_INT_TAG: + return stringToSigned8Int(value); + case TypeTags.SIGNED16_INT_TAG: + return stringToSigned16Int(value); + case TypeTags.SIGNED32_INT_TAG: + return stringToSigned32Int(value); + case TypeTags.UNSIGNED8_INT_TAG: + return stringToUnsigned8Int(value); + case TypeTags.UNSIGNED16_INT_TAG: + return stringToUnsigned16Int(value); + case TypeTags.UNSIGNED32_INT_TAG: + return stringToUnsigned32Int(value); case TypeTags.FLOAT_TAG: return stringToFloat(value); case TypeTags.DECIMAL_TAG: @@ -108,6 +133,65 @@ private static Long stringToInt(String value) throws NumberFormatException { return Long.parseLong(value); } + private static int stringToByte(String value) throws NumberFormatException { + int intValue = Integer.parseInt(value); + if (!isByteLiteral(intValue)) { + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_TYPE, PredefinedTypes.TYPE_BYTE, value); + } + return intValue; + } + + private static long stringToSigned8Int(String value) throws NumberFormatException { + long intValue = Long.parseLong(value); + if (!isSigned8LiteralValue(intValue)) { + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_TYPE, PredefinedTypes.TYPE_INT_SIGNED_8, value); + } + return intValue; + } + + private static long stringToSigned16Int(String value) throws NumberFormatException { + long intValue = Long.parseLong(value); + if (!isSigned16LiteralValue(intValue)) { + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_TYPE, PredefinedTypes.TYPE_INT_SIGNED_16, value); + } + return intValue; + } + + private static long stringToSigned32Int(String value) throws NumberFormatException { + long intValue = Long.parseLong(value); + if (!isSigned32LiteralValue(intValue)) { + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_TYPE, PredefinedTypes.TYPE_INT_SIGNED_32, value); + } + return intValue; + } + + private static long stringToUnsigned8Int(String value) throws NumberFormatException { + long intValue = Long.parseLong(value); + if (!isUnsigned8LiteralValue(intValue)) { + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_TYPE, + PredefinedTypes.TYPE_INT_UNSIGNED_8, value); + } + return intValue; + } + + private static long stringToUnsigned16Int(String value) throws NumberFormatException { + long intValue = Long.parseLong(value); + if (!isUnsigned16LiteralValue(intValue)) { + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_TYPE, + PredefinedTypes.TYPE_INT_UNSIGNED_16, value); + } + return intValue; + } + + private static long stringToUnsigned32Int(String value) throws NumberFormatException { + long intValue = Long.parseLong(value); + if (!isUnsigned32LiteralValue(intValue)) { + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_TYPE, + PredefinedTypes.TYPE_INT_UNSIGNED_32, value); + } + return intValue; + } + private static Double stringToFloat(String value) throws NumberFormatException { if (hasFloatOrDecimalLiteralSuffix(value)) { throw new NumberFormatException(); @@ -174,6 +258,34 @@ private static boolean hasFloatOrDecimalLiteralSuffix(String value) { } } + public static boolean isByteLiteral(long longValue) { + return (longValue >= BBYTE_MIN_VALUE && longValue <= BBYTE_MAX_VALUE); + } + + public static boolean isSigned32LiteralValue(Long longObject) { + return (longObject >= SIGNED32_MIN_VALUE && longObject <= SIGNED32_MAX_VALUE); + } + + public static boolean isSigned16LiteralValue(Long longObject) { + return (longObject.intValue() >= SIGNED16_MIN_VALUE && longObject.intValue() <= SIGNED16_MAX_VALUE); + } + + public static boolean isSigned8LiteralValue(Long longObject) { + return (longObject.intValue() >= SIGNED8_MIN_VALUE && longObject.intValue() <= SIGNED8_MAX_VALUE); + } + + public static boolean isUnsigned32LiteralValue(Long longObject) { + return (longObject >= 0 && longObject <= UNSIGNED32_MAX_VALUE); + } + + public static boolean isUnsigned16LiteralValue(Long longObject) { + return (longObject.intValue() >= 0 && longObject.intValue() <= UNSIGNED16_MAX_VALUE); + } + + public static boolean isUnsigned8LiteralValue(Long longObject) { + return (longObject.intValue() >= 0 && longObject.intValue() <= UNSIGNED8_MAX_VALUE); + } + private static BError returnError(String string, String expType) { return DiagnosticLog.error(DiagnosticErrorCode.CANNOT_CONVERT_TO_EXPECTED_TYPE, PredefinedTypes.TYPE_STRING.getName(), string, expType); diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java index 9643a0b..0e8b667 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java @@ -58,6 +58,7 @@ static BMap initRootMapValue(Type expectedType) { case TypeTags.MAP_TAG -> ValueCreator.createMapValue((MapType) expectedType); case TypeTags.JSON_TAG -> ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); case TypeTags.ANYDATA_TAG -> ValueCreator.createMapValue(Constants.ANYDATA_MAP_TYPE); + case TypeTags.UNION_TAG -> throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE, expectedType); default -> throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, expectedType, "map type"); }; } @@ -104,6 +105,7 @@ static Optional> initNewMapValue(JsonParser.StateMachine s nextMapValue = ValueCreator.createMapValue(Constants.ANYDATA_MAP_TYPE); sm.updateExpectedType(new HashMap<>(), currentType); } + case TypeTags.UNION_TAG -> throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE, currentType); default -> { if (parentContext == JsonParser.StateMachine.ParserContext.ARRAY) { throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, currentType, "map type"); diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java index 87bf641..db281e4 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java @@ -201,8 +201,10 @@ public Object execute(Reader reader, BMap options, Type type) t expectedTypes.push(type); arrayIndexes.push(0); } - case TypeTags.NULL_TAG, TypeTags.BOOLEAN_TAG, TypeTags.INT_TAG, TypeTags.FLOAT_TAG, - TypeTags.DECIMAL_TAG, TypeTags.STRING_TAG -> + case TypeTags.NULL_TAG, TypeTags.BOOLEAN_TAG, TypeTags.INT_TAG, TypeTags.BYTE_TAG, + TypeTags.SIGNED8_INT_TAG, TypeTags.SIGNED16_INT_TAG, TypeTags.SIGNED32_INT_TAG, + TypeTags.UNSIGNED8_INT_TAG, TypeTags.UNSIGNED16_INT_TAG, TypeTags.UNSIGNED32_INT_TAG, + TypeTags.FLOAT_TAG, TypeTags.DECIMAL_TAG, TypeTags.STRING_TAG -> expectedTypes.push(type); case TypeTags.JSON_TAG, TypeTags.ANYDATA_TAG -> { expectedTypes.push(type); diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java index 1be8c7e..6966c36 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java @@ -31,6 +31,7 @@ import io.ballerina.runtime.api.types.UnionType; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; +import io.ballerina.runtime.api.utils.ValueUtils; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BDecimal; import io.ballerina.runtime.api.values.BError; @@ -105,7 +106,9 @@ public Object traverseJson(Object json, Type type) { referredType); } case TypeTags.NULL_TAG, TypeTags.BOOLEAN_TAG, TypeTags.INT_TAG, TypeTags.FLOAT_TAG, - TypeTags.DECIMAL_TAG, TypeTags.STRING_TAG -> { + TypeTags.DECIMAL_TAG, TypeTags.STRING_TAG, TypeTags.BYTE_TAG, TypeTags.SIGNED8_INT_TAG, + TypeTags.SIGNED16_INT_TAG, TypeTags.SIGNED32_INT_TAG, TypeTags.UNSIGNED8_INT_TAG, + TypeTags.UNSIGNED16_INT_TAG, TypeTags.UNSIGNED32_INT_TAG -> { return convertToBasicType(json, referredType); } case TypeTags.UNION_TAG -> { @@ -263,13 +266,13 @@ private void addRestField(Type restFieldType, BString key, Object jsonMember, Ob private boolean checkTypeCompatibility(Type type, Object json) { return ((json == null && type.getTag() == TypeTags.NULL_TAG) || (json instanceof BString && type.getTag() == TypeTags.STRING_TAG) - || (isInstanceOfBallerinaInt(json) && type.getTag() == TypeTags.INT_TAG) + || (isBallerinaInt(json) && type.getTag() == TypeTags.INT_TAG) || (json instanceof Double && type.getTag() == TypeTags.FLOAT_TAG) || ((json instanceof Double || json instanceof BDecimal) && type.getTag() == TypeTags.DECIMAL_TAG) || (json instanceof Boolean && type.getTag() == TypeTags.BOOLEAN_TAG)); } - private boolean isInstanceOfBallerinaInt(Object val) { + private boolean isBallerinaInt(Object val) { return val instanceof Number && !(val instanceof Double); } @@ -282,19 +285,15 @@ private void checkOptionalFieldsAndLogError(Map currentField) { } private Object convertToBasicType(Object json, Type targetType) { - if (targetType.getTag() == TypeTags.READONLY_TAG) { - return json; - } - - if (checkTypeCompatibility(targetType, json)) { - return json; - } - - if (fieldNames.isEmpty()) { - throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_TYPE, targetType, json); + try { + return ValueUtils.convert(json, targetType); + } catch (BError e) { + if (fieldNames.isEmpty()) { + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_TYPE, targetType, json.toString()); + } + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_VALUE_FOR_FIELD, json.toString(), targetType, + getCurrentFieldPath()); } - throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_VALUE_FOR_FIELD, json, targetType, - getCurrentFieldPath()); } private String getCurrentFieldPath() { diff --git a/native/src/main/resources/error.properties b/native/src/main/resources/error.properties index 7b6fc9e..3db6b8b 100644 --- a/native/src/main/resources/error.properties +++ b/native/src/main/resources/error.properties @@ -24,7 +24,7 @@ error.unsupported.type=\ unsupported type ''{0}'' error.json.reader.failure=\ - error reading JSON: ''{0}'' + error reading while JSON: ''{0}'' error.json.parser.exception=\ ''{0}'' at line: ''{1}'' column: ''{2}'' From 56aa81543e69dc1c51b776f8788826946cdca285 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:25:14 +0530 Subject: [PATCH 23/27] Support readonly intersection --- ...adonly_intersection_expected_type_test.bal | 318 ++++++++++++++++++ ballerina/tests/types.bal | 56 +++ .../stdlib/data/jsondata/FromString.java | 32 +- .../data/jsondata/json/JsonCreator.java | 77 +++-- .../stdlib/data/jsondata/json/JsonParser.java | 50 ++- .../data/jsondata/json/JsonTraverse.java | 29 +- 6 files changed, 496 insertions(+), 66 deletions(-) create mode 100644 ballerina/tests/readonly_intersection_expected_type_test.bal diff --git a/ballerina/tests/readonly_intersection_expected_type_test.bal b/ballerina/tests/readonly_intersection_expected_type_test.bal new file mode 100644 index 0000000..c797fac --- /dev/null +++ b/ballerina/tests/readonly_intersection_expected_type_test.bal @@ -0,0 +1,318 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you 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. + +import ballerina/test; + +type intArrayReadonly int[] & readonly; + +type intArray2dReadonly int[][] & readonly; + +type booleanArrayReadonly boolean[] & readonly; + +type type1Readonly [int, boolean, decimal, string] & readonly; + +type type2Readonly map & readonly; + +type type3Readonly map & readonly; + +type type4Readonly map> & readonly; + +type type5Readonly map[] & readonly; + +type mapIntArrayReadonly map & readonly; + +type jsonTypeReadonly json & readonly; + +type int2ArrayReadonly int[2] & readonly; + +type char2ArrayReadonly string:Char[2] & readonly; + +type char2DFixedArrayReadonly string:Char[3][4] & readonly; + +type int2DFixedArrayReadonly int[2][1] & readonly; + +type intTupleReadonly [[int], [int]] & readonly; + +type intTupleRestReadonly [[int], [int]...] & readonly; + +type intStringTupleReadonly [[int], [string]] & readonly; + +type intStringTupleRestReadonly [[int], [string]...] & readonly; + +type NilTypeReadonly () & readonly; + +type BooleanTypeReadonly boolean & readonly; + +type intTypeReadonly int & readonly; + +type floatTypeReadonly float & readonly; + +type decimalTypeReadonly decimal & readonly; + +type stringTypeReadonly string & readonly; + +type charTypeReadonly string:Char & readonly; + +type ByteTypeReadonly byte & readonly; + +type intUnsigned8Readonly int:Unsigned8 & readonly; + +type intSigned8Readonly int:Signed8 & readonly; + +type intUnsigned16Readonly int:Unsigned16 & readonly; + +type intSigned16Readonly int:Signed16 & readonly; + +type intUnsigned32Readonly int:Unsigned32 & readonly; + +type intSigned32Readonly int:Signed32 & readonly; + +type strinttupleReadonly [int, int] & readonly; + +type stringArrReadonly string[] & readonly; + +type tuple1Readonly [[int, string], [boolean, float]] & readonly; + +type tuple2Readonly [[float, string], [boolean, decimal]...] & readonly; + +type stringArrayTypeReadonly string[] & readonly; + +type Rec1ReadOnly Rec1 & readonly; + +type Rec2ReadOnly Rec2 & readonly; + +type Rec3ReadOnly Rec3 & readonly; + +type Rec1 record {| + string name; + int age; + boolean isMarried = true; + float...; +|}; + +type Rec2 record {| + Rec1 student; + string address; + int count; + float weight = 18.3; + boolean...; +|}; + +type Rec3 record {| + Rec1 student; +|}; + +type Rec4 record {| + readonly string department; + intTypeReadonly studentCount; + Rec1ReadOnly[] student; +|}; + +type Rec5 record {| + readonly & int id; + Rec2 & readonly health; +|}; + +type ExpectedTuple [ + intArrayReadonly, + type1Readonly, + intArrayReadonly, + intArray2dReadonly, + type3Readonly, + type4Readonly, + type5Readonly, + mapIntArrayReadonly, + int2ArrayReadonly, + int2DFixedArrayReadonly, + intTupleReadonly, + intTupleRestReadonly, + intTupleRestReadonly, + intStringTupleRestReadonly, + intStringTupleRestReadonly, + intTupleReadonly, + int2DFixedArrayReadonly, + BooleanTypeReadonly, + BooleanTypeReadonly, + intTypeReadonly, + floatTypeReadonly, + decimalTypeReadonly, + stringTypeReadonly, + charTypeReadonly, + ByteTypeReadonly, + intUnsigned8Readonly, + intSigned8Readonly, + intUnsigned16Readonly, + intSigned16Readonly, + intUnsigned32Readonly, + intSigned32Readonly, + NilTypeReadonly, + Rec1ReadOnly, + Rec3ReadOnly, + Rec2ReadOnly, + Rec4, + Rec5 +]; + +ExpectedTuple expectedResults = [ + [1, 2, 3], + [12, true, 123.4, "hello"], + [12, 13], + [[12], [13]], + {id: false, age: true}, + {key1: {id: 12, age: 24}, key2: {id: 12, age: 24}}, + [{id: 12, age: 24}, {id: 12, age: 24}], + {key1: [12, 13], key2: [132, 133]}, + [12], + [[1], [2]], + [[1], [2]], + [[1], [2], [3]], + [[1]], + [[1], ["2"], ["3"]], + [[1]], + [[1], [2]], + [[1], [2]], + true, + false, + 12, + 12.3, + 12.3, + "hello", + "h", + 12, + 13, + 14, + 15, + 16, + 17, + 18, + null, + {name: "John", age: 30, "height": 1.8}, + {student: {name: "John", age: 30, "height": 1.8}}, + {"isSingle": true, address: "this is address", count: 14, student: {name: "John", age: 30, "height": 1.8}}, + {department: "CSE", studentCount: 3, student: [{name: "John", age: 30, "height": 1.8}]}, + {id: 12, health: {student: {name: "John", age: 30, "height": 1.8}, address: "this is address", count: 14}} +]; + +@test:Config { + dataProvider: readonlyIntersectionTestDataForFromJsonStringWithType +} +isolated function testReadOnlyIntersectionTypeAsExpTypForFromJsonStringWithType(string sourceData, + typedesc expType, anydata expectedData) returns error? { + anydata result = check fromJsonStringWithType(sourceData, {}, expType); + test:assertEquals(result, expectedData); +} + +function readonlyIntersectionTestDataForFromJsonStringWithType() returns [string, typedesc, anydata][] { + return [ + [string `[1, 2, 3]`, intArrayReadonly, expectedResults[0]], + ["[12, true, 123.4, \"hello\"]", type1Readonly, expectedResults[1]], + ["[12, 13]", intArrayReadonly, expectedResults[2]], + ["[[12], [13]]", intArray2dReadonly, expectedResults[3]], + ["{\"id\": false, \"age\": true}", type3Readonly, expectedResults[4]], + ["{\"key1\": {\"id\": 12, \"age\": 24}, \"key2\": {\"id\": 12, \"age\": 24}}", type4Readonly, expectedResults[5]], + ["[{\"id\": 12, \"age\": 24}, {\"id\": 12, \"age\": 24}]", type5Readonly, expectedResults[6]], + ["{\"key1\": [12, 13], \"key2\": [132, 133]}", mapIntArrayReadonly, expectedResults[7]], + ["[12]", int2ArrayReadonly, expectedResults[8]], + ["[[1],[2]]", int2DFixedArrayReadonly, expectedResults[9]], + ["[[1],[2]]", intTupleReadonly, expectedResults[10]], + ["[[1],[2],[3]]", intTupleRestReadonly, expectedResults[11]], + ["[[1]]", intTupleRestReadonly, expectedResults[12]], + ["[[1],[\"2\"],[\"3\"]]", intStringTupleRestReadonly, expectedResults[13]], + ["[[1]]", intStringTupleRestReadonly, expectedResults[14]], + ["[[1],[2]]", intTupleReadonly, expectedResults[15]], + ["[[1],[2]]", int2DFixedArrayReadonly, expectedResults[16]], + ["true", BooleanTypeReadonly, expectedResults[17]], + ["false", BooleanTypeReadonly, expectedResults[18]], + ["12", intTypeReadonly, expectedResults[19]], + ["12.3", floatTypeReadonly, expectedResults[20]], + ["12.3", decimalTypeReadonly, expectedResults[21]], + ["\"hello\"", stringTypeReadonly, expectedResults[22]], + ["\"h\"", charTypeReadonly, expectedResults[23]], + ["12", ByteTypeReadonly, expectedResults[24]], + ["13", intUnsigned8Readonly, expectedResults[25]], + ["14", intSigned8Readonly, expectedResults[26]], + ["15", intUnsigned16Readonly, expectedResults[27]], + ["16", intSigned16Readonly, expectedResults[28]], + ["17", intUnsigned32Readonly, expectedResults[29]], + ["18", intSigned32Readonly, expectedResults[30]], + ["null", NilTypeReadonly, expectedResults[31]], + [string `{"name": "John", "age": 30, "height": 1.8}`, Rec1ReadOnly, expectedResults[32]], + [string `{"student": {"name": "John", "age": 30, "height": 1.8}}`, Rec3ReadOnly, expectedResults[33]], + [string `{"isSingle": true, "address": "this is address", "count": 14,"student": {"name": "John", "age": 30, "height": 1.8}}`, Rec2ReadOnly, expectedResults[34]], + [string `{"department": "CSE", "studentCount": 3, "student": [{"name": "John", "age": 30, "height": 1.8}]}`, Rec4, expectedResults[35]], + [string `{"id": 12, "health": {"student": {"name": "John", "age": 30, "height": 1.8}, "address": "this is address", "count": 14}}`, Rec5, expectedResults[36]] + ]; +} + +@test:Config { + dataProvider: readonlyIntersectionTestDataForFromJsonWithType +} +isolated function testReadOnlyIntersectionTypeAsExpTypForFromJsonWithType(json sourceData, + typedesc expType, anydata expectedData) returns error? { + anydata result = check fromJsonWithType(sourceData, {}, expType); + test:assertEquals(result, expectedData); +} + +function readonlyIntersectionTestDataForFromJsonWithType() returns [json, typedesc, anydata][] { + return [ + [[1, 2, 3], intArrayReadonly, expectedResults[0]], + [[12, true, 123.4, "hello"], type1Readonly, expectedResults[1]], + [[12, 13], intArrayReadonly, expectedResults[2]], + [[[12], [13]], intArray2dReadonly, expectedResults[3]], + [{id: false, age: true}, type3Readonly, expectedResults[4]], + [{key1: {id: 12, age: 24}, key2: {id: 12, age: 24}}, type4Readonly, expectedResults[5]], + [[{id: 12, age: 24}, {id: 12, age: 24}], type5Readonly, expectedResults[6]], + [{key1: [12, 13], key2: [132, 133]}, mapIntArrayReadonly, expectedResults[7]], + [[12], int2ArrayReadonly, expectedResults[8]], + [[[1], [2]], int2DFixedArrayReadonly, expectedResults[9]], + [[[1], [2]], intTupleReadonly, expectedResults[10]], + [[[1], [2], [3]], intTupleRestReadonly, expectedResults[11]], + [[[1]], intTupleRestReadonly, expectedResults[12]], + [[[1], ["2"], ["3"]], intStringTupleRestReadonly, expectedResults[13]], + [[[1]], intStringTupleRestReadonly, expectedResults[14]], + [[[1], [2]], intTupleReadonly, expectedResults[15]], + [[[1], [2]], int2DFixedArrayReadonly, expectedResults[16]], + [true, BooleanTypeReadonly, expectedResults[17]], + [false, BooleanTypeReadonly, expectedResults[18]], + [12, intTypeReadonly, expectedResults[19]], + [12.3, floatTypeReadonly, expectedResults[20]], + [12.3, decimalTypeReadonly, expectedResults[21]], + ["hello", stringTypeReadonly, expectedResults[22]], + ["h", charTypeReadonly, expectedResults[23]], + [12, ByteTypeReadonly, expectedResults[24]], + [13, intUnsigned8Readonly, expectedResults[25]], + [14, intSigned8Readonly, expectedResults[26]], + [15, intUnsigned16Readonly, expectedResults[27]], + [16, intSigned16Readonly, expectedResults[28]], + [17, intUnsigned32Readonly, expectedResults[29]], + [18, intSigned32Readonly, expectedResults[30]], + [null, NilTypeReadonly, expectedResults[31]], + [{name: "John", "age": 30, "height": 1.8}, Rec1ReadOnly, expectedResults[32]], + [{"student": {"name": "John", "age": 30, "height": 1.8}}, Rec3ReadOnly, expectedResults[33]], + [ + { + "isSingle": true, + "address": "this is address", + "count": 14, + "student": {"name": "John", "age": 30, "height": 1.8} + }, + Rec2ReadOnly, + expectedResults[34] + ], + [{"department": "CSE", "studentCount": 3, "student": [{"name": "John", "age": 30, "height": 1.8}]}, Rec4, expectedResults[35]], + [{"id": 12, "health": {"student": {"name": "John", "age": 30, "height": 1.8}, "address": "this is address", "count": 14}}, Rec5, expectedResults[36]] + ]; +} diff --git a/ballerina/tests/types.bal b/ballerina/tests/types.bal index d0cc4d5..f527f42 100644 --- a/ballerina/tests/types.bal +++ b/ballerina/tests/types.bal @@ -14,6 +14,62 @@ // specific language governing permissions and limitations // under the License. +type OpenRecord record {}; + +type SimpleRec1 record {| + string a; + int b; +|}; + +type SimpleRec2 record { + string a; + int b; +}; + +type NestedRecord1 record {| + string a; + int b; + record {| + string d; + int e; + |} c; +|}; + +type NestedRecord2 record { + string a; + int b; + record {| + string d; + int e; + |} c; +}; + +type RestRecord1 record {| + string a; + anydata...; +|}; + +type RestRecord2 record {| + string a; + int...; +|}; + +type RestRecord3 record {| + string a; + int b; + record {| + int...; + |} c; +|}; + +type RestRecord4 record {| + string a; + int b; + record {| + decimal|float...; + |}...; +|}; + type Address record { string street; string city; diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java index fa18e70..fa3b42a 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java @@ -22,9 +22,11 @@ import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.creators.TypeCreator; import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.types.IntersectionType; import io.ballerina.runtime.api.types.ReferenceType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.UnionType; +import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BDecimal; import io.ballerina.runtime.api.values.BError; @@ -109,6 +111,8 @@ public static Object fromStringWithType(BString string, Type expType) { return stringToFloat(value); case TypeTags.DECIMAL_TAG: return stringToDecimal(value); + case TypeTags.CHAR_STRING_TAG: + return stringToChar(value); case TypeTags.STRING_TAG: return string; case TypeTags.BOOLEAN_TAG: @@ -121,6 +125,8 @@ public static Object fromStringWithType(BString string, Type expType) { return stringToUnion(string, JSON_TYPE_WITH_BASIC_TYPES); case TypeTags.TYPE_REFERENCED_TYPE_TAG: return fromStringWithType(string, ((ReferenceType) expType).getReferredType()); + case TypeTags.INTERSECTION_TAG: + return fromStringWithType(string, ((IntersectionType) expType).getEffectiveType()); default: return returnError(value, expType.toString()); } @@ -192,6 +198,14 @@ private static long stringToUnsigned32Int(String value) throws NumberFormatExcep return intValue; } + private static BString stringToChar(String value) throws NumberFormatException { + if (!isCharLiteralValue(value)) { + throw DiagnosticLog.error(DiagnosticErrorCode.INCOMPATIBLE_TYPE, + PredefinedTypes.TYPE_STRING_CHAR, value); + } + return StringUtils.fromString(value); + } + private static Double stringToFloat(String value) throws NumberFormatException { if (hasFloatOrDecimalLiteralSuffix(value)) { throw new NumberFormatException(); @@ -258,34 +272,38 @@ private static boolean hasFloatOrDecimalLiteralSuffix(String value) { } } - public static boolean isByteLiteral(long longValue) { + private static boolean isByteLiteral(long longValue) { return (longValue >= BBYTE_MIN_VALUE && longValue <= BBYTE_MAX_VALUE); } - public static boolean isSigned32LiteralValue(Long longObject) { + private static boolean isSigned32LiteralValue(Long longObject) { return (longObject >= SIGNED32_MIN_VALUE && longObject <= SIGNED32_MAX_VALUE); } - public static boolean isSigned16LiteralValue(Long longObject) { + private static boolean isSigned16LiteralValue(Long longObject) { return (longObject.intValue() >= SIGNED16_MIN_VALUE && longObject.intValue() <= SIGNED16_MAX_VALUE); } - public static boolean isSigned8LiteralValue(Long longObject) { + private static boolean isSigned8LiteralValue(Long longObject) { return (longObject.intValue() >= SIGNED8_MIN_VALUE && longObject.intValue() <= SIGNED8_MAX_VALUE); } - public static boolean isUnsigned32LiteralValue(Long longObject) { + private static boolean isUnsigned32LiteralValue(Long longObject) { return (longObject >= 0 && longObject <= UNSIGNED32_MAX_VALUE); } - public static boolean isUnsigned16LiteralValue(Long longObject) { + private static boolean isUnsigned16LiteralValue(Long longObject) { return (longObject.intValue() >= 0 && longObject.intValue() <= UNSIGNED16_MAX_VALUE); } - public static boolean isUnsigned8LiteralValue(Long longObject) { + private static boolean isUnsigned8LiteralValue(Long longObject) { return (longObject.intValue() >= 0 && longObject.intValue() <= UNSIGNED8_MAX_VALUE); } + private static boolean isCharLiteralValue(String value) { + return value.codePoints().count() == 1; + } + private static BError returnError(String string, String expType) { return DiagnosticLog.error(DiagnosticErrorCode.CANNOT_CONVERT_TO_EXPECTED_TYPE, PredefinedTypes.TYPE_STRING.getName(), string, expType); diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java index 0e8b667..566b64a 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java @@ -23,6 +23,7 @@ import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.types.ArrayType; import io.ballerina.runtime.api.types.Field; +import io.ballerina.runtime.api.types.IntersectionType; import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.TupleType; @@ -37,12 +38,14 @@ import io.ballerina.stdlib.data.jsondata.utils.Constants; import io.ballerina.stdlib.data.jsondata.utils.DiagnosticErrorCode; import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; +import org.ballerinalang.langlib.value.CloneReadOnly; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Stack; /** * Create objects for partially parsed json. @@ -78,14 +81,20 @@ static Optional> initNewMapValue(JsonParser.StateMachine s sm.parserContexts.push(JsonParser.StateMachine.ParserContext.MAP); Type expType = sm.expectedTypes.peek(); if (expType == null) { + sm.fieldNameHierarchy.push(new Stack<>()); return Optional.empty(); } - Type currentType = TypeUtils.getReferredType(expType); if (sm.currentJsonNode != null) { sm.nodesStack.push(sm.currentJsonNode); } + BMap nextMapValue = checkTypeAndCreateMappingValue(sm, expType, parentContext); + return Optional.of(nextMapValue); + } + static BMap checkTypeAndCreateMappingValue(JsonParser.StateMachine sm, Type expType, + JsonParser.StateMachine.ParserContext parentContext) { + Type currentType = TypeUtils.getReferredType(expType); BMap nextMapValue; switch (currentType.getTag()) { case TypeTags.RECORD_TYPE_TAG -> { @@ -105,6 +114,13 @@ static Optional> initNewMapValue(JsonParser.StateMachine s nextMapValue = ValueCreator.createMapValue(Constants.ANYDATA_MAP_TYPE); sm.updateExpectedType(new HashMap<>(), currentType); } + case TypeTags.INTERSECTION_TAG -> { + Optional mutableType = getMutableType((IntersectionType) currentType); + if (mutableType.isEmpty()) { + throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, currentType, "map type"); + } + return checkTypeAndCreateMappingValue(sm, mutableType.get(), parentContext); + } case TypeTags.UNION_TAG -> throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE, currentType); default -> { if (parentContext == JsonParser.StateMachine.ParserContext.ARRAY) { @@ -113,13 +129,7 @@ static Optional> initNewMapValue(JsonParser.StateMachine s throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE_FOR_FIELD, getCurrentFieldPath(sm)); } } - - Object currentJson = sm.currentJsonNode; - int valueTypeTag = TypeUtils.getType(currentJson).getTag(); - if (valueTypeTag == TypeTags.MAP_TAG || valueTypeTag == TypeTags.RECORD_TYPE_TAG) { - ((BMap) currentJson).put(StringUtils.fromString(sm.fieldNames.peek()), nextMapValue); - } - return Optional.of(nextMapValue); + return nextMapValue; } static void updateNextMapValue(JsonParser.StateMachine sm) { @@ -139,7 +149,15 @@ static Optional initNewArrayValue(JsonParser.StateMachine sm) { } Object currentJsonNode = sm.currentJsonNode; - BArray nextArrValue = initArrayValue(sm.expectedTypes.peek()); + Type expType = TypeUtils.getReferredType(sm.expectedTypes.peek()); + if (expType.getTag() == TypeTags.INTERSECTION_TAG) { + Optional type = getMutableType((IntersectionType) expType); + if (type.isEmpty()) { + throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, expType, "array type"); + } + expType = type.get(); + } + BArray nextArrValue = initArrayValue(expType); if (currentJsonNode == null) { return Optional.ofNullable(nextArrValue); } @@ -148,12 +166,21 @@ static Optional initNewArrayValue(JsonParser.StateMachine sm) { return Optional.ofNullable(nextArrValue); } - private static String getCurrentFieldPath(JsonParser.StateMachine sm) { - Iterator itr = sm.fieldNames.descendingIterator(); + static Optional getMutableType(IntersectionType intersectionType) { + for (Type constituentType : intersectionType.getConstituentTypes()) { + if (constituentType.getTag() == TypeTags.READONLY_TAG) { + continue; + } + return Optional.of(constituentType); + } + return Optional.empty(); + } - StringBuilder result = new StringBuilder(itr.hasNext() ? itr.next() : ""); + private static String getCurrentFieldPath(JsonParser.StateMachine sm) { + Iterator> itr = sm.fieldNameHierarchy.iterator(); + StringBuilder result = new StringBuilder(itr.hasNext() ? itr.next().peek() : ""); while (itr.hasNext()) { - result.append(".").append(itr.next()); + result.append(".").append(itr.next().peek()); } return result.toString(); } @@ -172,7 +199,7 @@ static Object convertAndUpdateCurrentJsonNode(JsonParser.StateMachine sm, BStrin Type currentJsonNodeType = TypeUtils.getType(currentJson); switch (currentJsonNodeType.getTag()) { case TypeTags.MAP_TAG, TypeTags.RECORD_TYPE_TAG -> { - ((BMap) currentJson).put(StringUtils.fromString(sm.fieldNames.pop()), + ((BMap) currentJson).put(StringUtils.fromString(sm.fieldNameHierarchy.peek().pop()), convertedValue); return currentJson; } @@ -241,24 +268,6 @@ static Type getMemberType(Type expectedType, int index, boolean allowDataProject return expectedType; } - static void validateListSize(int currentIndex, Type expType) { - int expLength = 0; - if (expType == null) { - return; - } - - if (expType.getTag() == TypeTags.ARRAY_TAG) { - expLength = ((ArrayType) expType).getSize(); - } else if (expType.getTag() == TypeTags.TUPLE_TAG) { - TupleType tupleType = (TupleType) expType; - expLength = tupleType.getTupleTypes().size(); - } - - if (expLength >= 0 && expLength > currentIndex + 1) { - throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); - } - } - static Map getAllFieldsInRecord(RecordType recordType) { BMap annotations = recordType.getAnnotations(); Map modifiedNames = new HashMap<>(); @@ -292,4 +301,8 @@ static String getModifiedName(Map fieldAnnotation, String field } return fieldName; } + + static Object constructReadOnlyValue(Object value) { + return CloneReadOnly.cloneReadOnly(value); + } } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java index db281e4..614bc78 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java @@ -23,6 +23,7 @@ import io.ballerina.runtime.api.flags.SymbolFlags; import io.ballerina.runtime.api.types.ArrayType; import io.ballerina.runtime.api.types.Field; +import io.ballerina.runtime.api.types.IntersectionType; import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.Type; @@ -37,6 +38,7 @@ import io.ballerina.stdlib.data.jsondata.utils.DiagnosticErrorCode; import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; import org.apache.commons.lang3.StringEscapeUtils; +import org.ballerinalang.langlib.value.CloneReadOnly; import java.io.IOException; import java.io.Reader; @@ -133,8 +135,6 @@ static class StateMachine { Object currentJsonNode; Deque nodesStack; - Deque fieldNames; - private StringBuilder hexBuilder = new StringBuilder(4); private char[] charBuff = new char[1024]; private int charBuffIndex; @@ -149,6 +149,7 @@ static class StateMachine { Stack> visitedFieldHierarchy = new Stack<>(); Stack restType = new Stack<>(); Stack expectedTypes = new Stack<>(); + Stack> fieldNameHierarchy = new Stack<>(); int jsonFieldDepth = 0; Stack arrayIndexes = new Stack<>(); Stack parserContexts = new Stack<>(); @@ -163,7 +164,7 @@ public void reset() { line = 1; column = 0; nodesStack = new ArrayDeque<>(); - fieldNames = new ArrayDeque<>(); + fieldNameHierarchy.clear(); fieldHierarchy.clear(); currentField = null; restType.clear(); @@ -204,7 +205,7 @@ public Object execute(Reader reader, BMap options, Type type) t case TypeTags.NULL_TAG, TypeTags.BOOLEAN_TAG, TypeTags.INT_TAG, TypeTags.BYTE_TAG, TypeTags.SIGNED8_INT_TAG, TypeTags.SIGNED16_INT_TAG, TypeTags.SIGNED32_INT_TAG, TypeTags.UNSIGNED8_INT_TAG, TypeTags.UNSIGNED16_INT_TAG, TypeTags.UNSIGNED32_INT_TAG, - TypeTags.FLOAT_TAG, TypeTags.DECIMAL_TAG, TypeTags.STRING_TAG -> + TypeTags.FLOAT_TAG, TypeTags.DECIMAL_TAG, TypeTags.CHAR_STRING_TAG, TypeTags.STRING_TAG -> expectedTypes.push(type); case TypeTags.JSON_TAG, TypeTags.ANYDATA_TAG -> { expectedTypes.push(type); @@ -221,6 +222,22 @@ public Object execute(Reader reader, BMap options, Type type) t } throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE, type); } + case TypeTags.INTERSECTION_TAG -> { + Type effectiveType = ((IntersectionType) type).getEffectiveType(); + if (!SymbolFlags.isFlagOn(SymbolFlags.READONLY, effectiveType.getFlags())) { + throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE, type); + } + + Object jsonValue = null; + for (Type constituentType : ((IntersectionType) type).getConstituentTypes()) { + if (constituentType.getTag() == TypeTags.READONLY_TAG) { + continue; + } + jsonValue = execute(reader, options, TypeUtils.getReferredType(constituentType)); + break; + } + return JsonCreator.constructReadOnlyValue(jsonValue); + } default -> throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE, type); } @@ -294,6 +311,7 @@ private State finalizeNonArrayObject() { if (!expectedTypes.isEmpty() && expectedTypes.peek() == null) { // Skip the value and continue to next state. parserContexts.pop(); + fieldNameHierarchy.pop(); if (parserContexts.peek() == ParserContext.MAP) { return FIELD_END_STATE; } @@ -302,6 +320,7 @@ private State finalizeNonArrayObject() { Map remainingFields = fieldHierarchy.pop(); visitedFieldHierarchy.pop(); + fieldNameHierarchy.pop(); restType.pop(); for (Field field : remainingFields.values()) { if (SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.REQUIRED)) { @@ -326,10 +345,16 @@ private State finalizeObject() { return DOC_END_STATE; } + if (expectedTypes.peek().isReadOnly()) { + currentJsonNode = CloneReadOnly.cloneReadOnly(currentJsonNode); + } + Object parentNode = nodesStack.pop(); Type parentNodeType = TypeUtils.getType(parentNode); int parentNodeTypeTag = TypeUtils.getReferredType(parentNodeType).getTag(); if (parentNodeTypeTag == TypeTags.RECORD_TYPE_TAG || parentNodeTypeTag == TypeTags.MAP_TAG) { + ((BMap) parentNode).put(StringUtils.fromString(fieldNameHierarchy.peek().pop()), + currentJsonNode); currentJsonNode = parentNode; return FIELD_END_STATE; } @@ -362,6 +387,7 @@ public void updateExpectedType(Map fields, Type restType) { this.fieldHierarchy.push(new HashMap<>(fields)); this.visitedFieldHierarchy.push(new HashMap<>()); this.restType.push(restType); + this.fieldNameHierarchy.push(new Stack<>()); } private void updateNextArrayValue() { @@ -371,9 +397,9 @@ private void updateNextArrayValue() { } private State finalizeArrayObject() { - int currentIndex = arrayIndexes.pop(); + arrayIndexes.pop(); State state = finalizeObject(); - JsonCreator.validateListSize(currentIndex, expectedTypes.pop()); + expectedTypes.pop(); return state; } @@ -671,7 +697,7 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J } else if (sm.expectedTypes.peek() == null) { sm.expectedTypes.push(null); } - sm.fieldNames.push(jsonFieldName); + sm.fieldNameHierarchy.peek().push(jsonFieldName); state = END_FIELD_NAME_STATE; } else if (ch == REV_SOL) { state = FIELD_NAME_ESC_CHAR_PROCESSING_STATE; @@ -742,8 +768,6 @@ public State transition(StateMachine sm, char[] buff, int i, int count) { Optional nextArray = JsonCreator.initNewArrayValue(sm); if (nextArray.isPresent()) { sm.currentJsonNode = nextArray.get(); - JsonCreator.updateRecordFieldValue(StringUtils.fromString(sm.fieldNames.peek()), - sm.nodesStack.peek(), sm.currentJsonNode); } state = FIRST_ARRAY_ELEMENT_READY_STATE; } else { @@ -783,15 +807,9 @@ public State transition(StateMachine sm, char[] buff, int i, int count) throws J if (sm.jsonFieldDepth > 0) { sm.currentJsonNode = JsonCreator.convertAndUpdateCurrentJsonNode(sm, StringUtils.fromString(s), expType); - } else if (sm.currentField != null) { + } else if (sm.currentField != null || sm.restType.peek() != null) { sm.currentJsonNode = JsonCreator.convertAndUpdateCurrentJsonNode(sm, StringUtils.fromString(s), expType); - } else if (sm.restType.peek() != null) { - try { - sm.currentJsonNode = JsonCreator.convertAndUpdateCurrentJsonNode(sm, - StringUtils.fromString(s), expType); - // this element will be ignored in projection - } catch (BError ignored) { } } state = FIELD_END_STATE; } else if (ch == REV_SOL) { diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java index 6966c36..5619108 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java @@ -24,6 +24,7 @@ import io.ballerina.runtime.api.flags.SymbolFlags; import io.ballerina.runtime.api.types.ArrayType; import io.ballerina.runtime.api.types.Field; +import io.ballerina.runtime.api.types.IntersectionType; import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.TupleType; @@ -106,9 +107,9 @@ public Object traverseJson(Object json, Type type) { referredType); } case TypeTags.NULL_TAG, TypeTags.BOOLEAN_TAG, TypeTags.INT_TAG, TypeTags.FLOAT_TAG, - TypeTags.DECIMAL_TAG, TypeTags.STRING_TAG, TypeTags.BYTE_TAG, TypeTags.SIGNED8_INT_TAG, - TypeTags.SIGNED16_INT_TAG, TypeTags.SIGNED32_INT_TAG, TypeTags.UNSIGNED8_INT_TAG, - TypeTags.UNSIGNED16_INT_TAG, TypeTags.UNSIGNED32_INT_TAG -> { + TypeTags.DECIMAL_TAG, TypeTags.STRING_TAG, TypeTags.CHAR_STRING_TAG , TypeTags.BYTE_TAG, + TypeTags.SIGNED8_INT_TAG, TypeTags.SIGNED16_INT_TAG, TypeTags.SIGNED32_INT_TAG, + TypeTags.UNSIGNED8_INT_TAG, TypeTags.UNSIGNED16_INT_TAG, TypeTags.UNSIGNED32_INT_TAG -> { return convertToBasicType(json, referredType); } case TypeTags.UNION_TAG -> { @@ -130,6 +131,19 @@ public Object traverseJson(Object json, Type type) { restType.push(mapType.getConstrainedType()); return traverseMapJsonOrArrayJson(json, ValueCreator.createMapValue(mapType), referredType); } + case TypeTags.INTERSECTION_TAG -> { + Type effectiveType = ((IntersectionType) referredType).getEffectiveType(); + if (!SymbolFlags.isFlagOn(SymbolFlags.READONLY, effectiveType.getFlags())) { + throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE, type); + } + for (Type constituentType : ((IntersectionType) referredType).getConstituentTypes()) { + if (constituentType.getTag() == TypeTags.READONLY_TAG) { + continue; + } + return JsonCreator.constructReadOnlyValue(traverseJson(json, constituentType)); + } + throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE, type); + } default -> throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, type, PredefinedTypes.TYPE_ANYDATA); } @@ -198,16 +212,12 @@ private Object traverseArrayValue(BArray array, Object currentJsonNode) { ArrayType arrayType = (ArrayType) rootArray; int expectedArraySize = arrayType.getSize(); long sourceArraySize = array.getLength(); - if (expectedArraySize > sourceArraySize) { - throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); - } - if (!allowDataProjection && expectedArraySize < sourceArraySize) { throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); } Type elementType = arrayType.getElementType(); - if (expectedArraySize == -1) { + if (expectedArraySize == -1 || expectedArraySize > sourceArraySize) { traverseArrayMembers(array.getLength(), array, elementType, currentJsonNode); } else { traverseArrayMembers(expectedArraySize, array, elementType, currentJsonNode); @@ -217,9 +227,6 @@ private Object traverseArrayValue(BArray array, Object currentJsonNode) { TupleType tupleType = (TupleType) rootArray; Type restType = tupleType.getRestType(); int expectedTupleTypeCount = tupleType.getTupleTypes().size(); - if (expectedTupleTypeCount > array.getLength()) { - throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); - } for (int i = 0; i < array.getLength(); i++) { Object jsonMember = array.get(i); Object nextJsonNode; From 4503830ad162443a2838d3f4ba1d3353f1b47bd4 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Mon, 18 Mar 2024 22:42:26 +0530 Subject: [PATCH 24/27] Support singleton as expected type --- ballerina/json_api.bal | 4 +- ballerina/tests/from_json_string_test.bal | 94 +++++++++++++++---- ballerina/tests/from_json_test.bal | 91 ++++++++++++++---- ballerina/tests/types.bal | 9 ++ .../compiler/JsondataTypeValidator.java | 2 +- .../stdlib/data/jsondata/FromString.java | 18 ++++ .../stdlib/data/jsondata/json/JsonParser.java | 3 +- .../data/jsondata/json/JsonTraverse.java | 3 +- 8 files changed, 182 insertions(+), 42 deletions(-) diff --git a/ballerina/json_api.bal b/ballerina/json_api.bal index b41c743..bc3b599 100644 --- a/ballerina/json_api.bal +++ b/ballerina/json_api.bal @@ -44,9 +44,9 @@ public isolated function toJson(anydata v) # Represent the options that can be used to modify the behaviour of conversion in `fromJsonStringWithType` and `fromJsonWithType`. # # + allowDataProjection - enable or disable projection -public type Options record { +public type Options record {| boolean allowDataProjection = true; -}; +|}; # Represents the error type of the ballerina/data.jsondata module. This error type represents any error that can occur # during the execution of jsondata APIs. diff --git a/ballerina/tests/from_json_string_test.bal b/ballerina/tests/from_json_string_test.bal index 5b3bff3..ec1d245 100644 --- a/ballerina/tests/from_json_string_test.bal +++ b/ballerina/tests/from_json_string_test.bal @@ -37,18 +37,23 @@ isolated function testJsonStringToBasicTypes() returns error? { () val6 = check fromJsonStringWithType("null"); test:assertEquals(val6, null); - - string val7 = check fromJsonStringWithType(""); - test:assertEquals(val7, ""); } @test:Config isolated function testSimpleJsonStringToRecord() returns error? { string j = string `{"a": "hello", "b": 1}`; - record {|string a; int b;|} recA = check fromJsonStringWithType(j); + SimpleRec1 recA = check fromJsonStringWithType(j); test:assertEquals(recA.a, "hello"); test:assertEquals(recA.b, 1); + + SimpleRec2 recB = check fromJsonStringWithType(j); + test:assertEquals(recB.a, "hello"); + test:assertEquals(recB.b, 1); + + OpenRecord recC = check fromJsonStringWithType(j); + test:assertEquals(recC.get("a"), "hello"); + test:assertEquals(recC.get("b"), 1); } @test:Config @@ -72,13 +77,26 @@ isolated function testNestedJsonStringToRecord() returns error? { } }`; - record {|string a; int b; record {|string d; int e;|} c;|} recA = check fromJsonStringWithType(str); + NestedRecord1 recA = check fromJsonStringWithType(str); test:assertEquals(recA.length(), 3); test:assertEquals(recA.a, "hello"); test:assertEquals(recA.b, 1); test:assertEquals(recA.c.length(), 2); test:assertEquals(recA.c.d, "world"); test:assertEquals(recA.c.e, 2); + + NestedRecord2 recB = check fromJsonStringWithType(str); + test:assertEquals(recB.length(), 3); + test:assertEquals(recB.a, "hello"); + test:assertEquals(recB.b, 1); + test:assertEquals(recB.c.length(), 2); + test:assertEquals(recB.c.d, "world"); + test:assertEquals(recB.c.e, 2); + + OpenRecord recC = check fromJsonStringWithType(str); + test:assertEquals(recC.get("a"), "hello"); + test:assertEquals(recC.get("b"), 1); + test:assertEquals(recC.get("c"), {d: "world", e: 2}); } @test:Config @@ -941,6 +959,44 @@ isolated function testArrayOrTupleCaseForFromJsonStringWithType() returns error? test:assertEquals(val6, [{val: [[1, 2], [2, 3]]}]); } +@test:Config +isolated function testListFillerValuesWithFromJsonStringWithType() returns error? { + int[2] jsonVal1 = check fromJsonStringWithType("[1]"); + test:assertEquals(jsonVal1, [1, 0]); + + [int, float, string, boolean] jsonVal2 = check fromJsonStringWithType("[1]"); + test:assertEquals(jsonVal2, [1, 0.0, "", false]); + + record {| + float[3] A; + [int, decimal, float, boolean] B; + |} jsonVal3 = check fromJsonStringWithType(string `{"A": [1], "B": [1]}`); + test:assertEquals(jsonVal3, {A: [1.0, 0.0, 0.0], B: [1, 0d, 0.0, false]}); +} + +@test:Config +isolated function testSingletonAsExpectedTypeForFromJsonStringWithType() returns error? { + "1" val1 = check fromJsonStringWithType("1"); + test:assertEquals(val1, "1"); + + Singleton1 val2 = check fromJsonStringWithType("1"); + test:assertEquals(val2, 1); + + SingletonUnion val3 = check fromJsonStringWithType("1"); + test:assertEquals(val3, 1); + + () val4 = check fromJsonStringWithType("null"); + test:assertEquals(val4, ()); + + string str5 = string `{ + "value": "1", + "id": "3" + }`; + SingletonInRecord val5 = check fromJsonStringWithType(str5); + test:assertEquals(val5.id, "3"); + test:assertEquals(val5.value, 1); +} + @test:Config function testDuplicateKeyInTheStringSource() returns error? { string str = string `{ @@ -1242,6 +1298,12 @@ isolated function testUnSignedInt32AsExpectedTypeForFromJsonStringWithType() ret test:assertEquals(val4.address.id, 4294967295); } +// TODO: Fix the behaviour and add tests +@test:Config +isolated function testRecordWithRestAsExpectedTypeForFromJsonStringWithType() returns error? { + +} + // Negative tests for fromJsonStringWithType() function. @test:Config @@ -1311,19 +1373,6 @@ isolated function testFromJsonStringWithTypeNegative4() returns error? { test:assertEquals((a).message(), "incompatible expected type 'data.jsondata:RN2' for value '1'"); } -@test:Config -isolated function testFromJsonStringWithTypeNegative5() returns error? { - string str = string `[1, 2]`; - - INTARR|Error x = fromJsonStringWithType(str); - test:assertTrue(x is error); - test:assertEquals((x).message(), "array size is not compatible with the expected size"); - - INTTUPLE|Error y = fromJsonStringWithType(str); - test:assertTrue(y is error); - test:assertEquals((y).message(), "array size is not compatible with the expected size"); -} - @test:Config isolated function testDuplicateFieldInRecordTypeWithFromJsonStringWithType() returns error? { string str = string `{ @@ -1392,7 +1441,7 @@ isolated function testUnionTypeAsExpTypeForFromJsonStringWithTypeNegative() { } @test:Config -function testSubTypeOfIntAsExptypeNegative() { +isolated function testSubTypeOfIntAsExptypeNegative() { byte|error err1 = fromJsonStringWithType("256"); test:assertTrue(err1 is error); test:assertEquals(( err1).message(), "incompatible expected type 'byte' for value '256'"); @@ -1449,3 +1498,10 @@ function testSubTypeOfIntAsExptypeNegative() { test:assertTrue(err14 is error); test:assertEquals(( err14).message(), "incompatible expected type 'lang.int:Unsigned32' for value '-1'"); } + +@test:Config +isolated function testEmptyJsonDocumentNegative() { + string|error err = fromJsonStringWithType(""); + test:assertTrue(err is error); + test:assertEquals(( err).message(), "'empty JSON document' at line: '1' column: '1'"); +} diff --git a/ballerina/tests/from_json_test.bal b/ballerina/tests/from_json_test.bal index deca97f..b54bfb0 100644 --- a/ballerina/tests/from_json_test.bal +++ b/ballerina/tests/from_json_test.bal @@ -41,15 +41,34 @@ isolated function testJsonToBasicTypes() returns error? { decimal dVal = 1.5; decimal val7 = check fromJsonWithType(dVal); test:assertEquals(val7, 1.5d); + + string val8 = check fromJsonWithType(""); + test:assertEquals(val8, ""); + + float f = 1.5; + decimal val9 = check fromJsonWithType(f); + test:assertEquals(val9, 1.5d); + + decimal d = 1.5; + float val10 = check fromJsonWithType(d); + test:assertEquals(val10, 1.5f); } @test:Config isolated function testSimpleJsonToRecord() returns error? { json j = {"a": "hello", "b": 1}; - record {|string a; int b;|} recA = check fromJsonWithType(j); + SimpleRec1 recA = check fromJsonWithType(j); test:assertEquals(recA.a, "hello"); test:assertEquals(recA.b, 1); + + SimpleRec2 recB = check fromJsonWithType(j); + test:assertEquals(recB.a, "hello"); + test:assertEquals(recB.b, 1); + + OpenRecord recC = check fromJsonWithType(j); + test:assertEquals(recC.get("a"), "hello"); + test:assertEquals(recC.get("b"), 1); } @test:Config @@ -72,11 +91,22 @@ isolated function testNestedJsonToRecord() returns error? { } }; - record {|string a; int b; record {|string d; int e;|} c;|} recA = check fromJsonWithType(j); + NestedRecord1 recA = check fromJsonWithType(j); test:assertEquals(recA.a, "hello"); test:assertEquals(recA.b, 1); test:assertEquals(recA.c.d, "world"); test:assertEquals(recA.c.e, 2); + + NestedRecord2 recB = check fromJsonWithType(j); + test:assertEquals(recB.a, "hello"); + test:assertEquals(recB.b, 1); + test:assertEquals(recB.c.d, "world"); + test:assertEquals(recB.c.e, 2); + + OpenRecord recC = check fromJsonWithType(j); + test:assertEquals(recC.get("a"), "hello"); + test:assertEquals(recC.get("b"), 1); + test:assertEquals(recC.get("c"), {d: "world", e: 2}); } @test:Config @@ -929,7 +959,22 @@ isolated function testArrayOrTupleCaseForFromJsonWithType() returns error? { } @test:Config -function testNameAnnotationWithFromJsonWithType() returns error? { +isolated function testListFillerValuesWithFromJsonWithType() returns error? { + int[2] jsonVal1 = check fromJsonWithType([1]); + test:assertEquals(jsonVal1, [1, 0]); + + [int, float, string, boolean] jsonVal2 = check fromJsonWithType([1]); + test:assertEquals(jsonVal2, [1, 0.0, "", false]); + + record {| + float[3] A; + [int, decimal, float, boolean] B; + |} jsonVal3 = check fromJsonWithType({A: [1], B: [1]}); + test:assertEquals(jsonVal3, {A: [1.0, 0.0, 0.0], B: [1, 0d, 0.0, false]}); +} + +@test:Config +isolated function testNameAnnotationWithFromJsonWithType() returns error? { json jsonContent = { "id": 1, "title-name": "Harry Potter", @@ -943,7 +988,7 @@ function testNameAnnotationWithFromJsonWithType() returns error? { } @test:Config -function testSubTypeOfIntAsExpectedTypeWithFromJsonWithType() returns error? { +isolated function testSubTypeOfIntAsExpectedTypeWithFromJsonWithType() returns error? { byte val1 = check fromJsonWithType(255); test:assertEquals(val1, 255); @@ -1013,6 +1058,29 @@ function testSubTypeOfIntAsExpectedTypeWithFromJsonWithType() returns error? { test:assertEquals(val16, {a: 1, b: 127, c: 32767, d: 2147483647, e: 255, f: 32767, g: 2147483647}); } +@test:Config +isolated function testSingletonAsExpectedTypeForFromJsonWithType() returns error? { + "1" val1 = check fromJsonWithType("1"); + test:assertEquals(val1, "1"); + + Singleton1 val2 = check fromJsonWithType(1); + test:assertEquals(val2, 1); + + SingletonUnion val3 = check fromJsonWithType(2); + test:assertEquals(val3, 2); + + () val4 = check fromJsonWithType(null); + test:assertEquals(val4, ()); + + json jsonContent = { + value: 1, + id: "3" + }; + SingletonInRecord val5 = check fromJsonWithType(jsonContent); + test:assertEquals(val5.id, "3"); + test:assertEquals(val5.value, 1); +} + // Negative tests for fromJsonWithType() function. @test:Config @@ -1086,19 +1154,6 @@ isolated function testFromJsonWithTypeNegative4() returns error? { test:assertEquals((b).message(), "incompatible expected type 'string' for value '1'"); } -@test:Config -isolated function testFromJsonWithTypeNegative5() returns error? { - json jsonContent = [1, 2]; - - INTARR|Error x = fromJsonWithType(jsonContent); - test:assertTrue(x is error); - test:assertEquals((x).message(), "array size is not compatible with the expected size"); - - INTTUPLE|Error y = fromJsonWithType(jsonContent); - test:assertTrue(y is error); - test:assertEquals((y).message(), "array size is not compatible with the expected size"); -} - @test:Config isolated function testFromJsonWithTypeNegative6() { json jsonContent = { @@ -1133,7 +1188,7 @@ isolated function testProjectionInArrayNegativeForFromJsonWithType() { } @test:Config -function testSubTypeOfIntAsExptypeWithFromJsonWithTypeNegative() { +isolated function testSubTypeOfIntAsExptypeWithFromJsonWithTypeNegative() { byte|error err1 = fromJsonWithType(256); test:assertTrue(err1 is error); test:assertEquals(( err1).message(), "incompatible expected type 'byte' for value '256'"); diff --git a/ballerina/tests/types.bal b/ballerina/tests/types.bal index f527f42..9c6992b 100644 --- a/ballerina/tests/types.bal +++ b/ballerina/tests/types.bal @@ -224,6 +224,15 @@ type Library record { BookA[2] books; }; +type Singleton1 1; + +type SingletonUnion Singleton1|2|"3"; + +type SingletonInRecord record {| + Singleton1 value; + SingletonUnion id; +|}; + //////// Types used for Negative cases ///////// type AddressN record { diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java index d79e7a4..fa5767f 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java @@ -195,7 +195,7 @@ private boolean isSupportedUnionMemberType(TypeSymbol typeSymbol) { } switch (kind) { - case INT, FLOAT, DECIMAL, STRING, BOOLEAN, BYTE, NIL, ERROR -> { + case INT, FLOAT, DECIMAL, STRING, BOOLEAN, BYTE, NIL, SINGLETON, ERROR -> { return true; } default -> { diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java index fa3b42a..45262ca 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java @@ -22,6 +22,7 @@ import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.creators.TypeCreator; import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.types.FiniteType; import io.ballerina.runtime.api.types.IntersectionType; import io.ballerina.runtime.api.types.ReferenceType; import io.ballerina.runtime.api.types.Type; @@ -119,6 +120,8 @@ public static Object fromStringWithType(BString string, Type expType) { return stringToBoolean(value); case TypeTags.NULL_TAG: return stringToNull(value); + case TypeTags.FINITE_TYPE_TAG: + return stringToFiniteType(value, (FiniteType) expType); case TypeTags.UNION_TAG: return stringToUnion(string, (UnionType) expType); case TypeTags.JSON_TAG: @@ -135,6 +138,21 @@ public static Object fromStringWithType(BString string, Type expType) { } } + private static Object stringToFiniteType(String value, FiniteType finiteType) { + return finiteType.getValueSpace().stream() + .filter(finiteValue -> !(convertToSingletonValue(value, finiteValue) instanceof BError)) + .findFirst() + .orElseGet(() -> returnError(value, finiteType.toString())); + } + + private static Object convertToSingletonValue(String str, Object singletonValue) { + if (str.equals(singletonValue.toString())) { + return fromStringWithType(StringUtils.fromString(str), TypeUtils.getType(singletonValue)); + } else { + return returnError(str, singletonValue.toString()); + } + } + private static Long stringToInt(String value) throws NumberFormatException { return Long.parseLong(value); } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java index 614bc78..3c56c5e 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java @@ -205,7 +205,8 @@ public Object execute(Reader reader, BMap options, Type type) t case TypeTags.NULL_TAG, TypeTags.BOOLEAN_TAG, TypeTags.INT_TAG, TypeTags.BYTE_TAG, TypeTags.SIGNED8_INT_TAG, TypeTags.SIGNED16_INT_TAG, TypeTags.SIGNED32_INT_TAG, TypeTags.UNSIGNED8_INT_TAG, TypeTags.UNSIGNED16_INT_TAG, TypeTags.UNSIGNED32_INT_TAG, - TypeTags.FLOAT_TAG, TypeTags.DECIMAL_TAG, TypeTags.CHAR_STRING_TAG, TypeTags.STRING_TAG -> + TypeTags.FLOAT_TAG, TypeTags.DECIMAL_TAG, TypeTags.CHAR_STRING_TAG, TypeTags.STRING_TAG, + TypeTags.FINITE_TYPE_TAG -> expectedTypes.push(type); case TypeTags.JSON_TAG, TypeTags.ANYDATA_TAG -> { expectedTypes.push(type); diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java index 5619108..e2ef433 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java @@ -109,7 +109,8 @@ public Object traverseJson(Object json, Type type) { case TypeTags.NULL_TAG, TypeTags.BOOLEAN_TAG, TypeTags.INT_TAG, TypeTags.FLOAT_TAG, TypeTags.DECIMAL_TAG, TypeTags.STRING_TAG, TypeTags.CHAR_STRING_TAG , TypeTags.BYTE_TAG, TypeTags.SIGNED8_INT_TAG, TypeTags.SIGNED16_INT_TAG, TypeTags.SIGNED32_INT_TAG, - TypeTags.UNSIGNED8_INT_TAG, TypeTags.UNSIGNED16_INT_TAG, TypeTags.UNSIGNED32_INT_TAG -> { + TypeTags.UNSIGNED8_INT_TAG, TypeTags.UNSIGNED16_INT_TAG, TypeTags.UNSIGNED32_INT_TAG, + TypeTags.FINITE_TYPE_TAG -> { return convertToBasicType(json, referredType); } case TypeTags.UNION_TAG -> { From 21c91c462ae3a2e6093dbbbf89efbd3d078816ec Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Tue, 19 Mar 2024 19:06:09 +0530 Subject: [PATCH 25/27] Address review suggestions and add tests --- README.md | 23 +- ballerina/json_api.bal | 4 +- ballerina/tests/from_json_string_test.bal | 431 ++++++++++-------- ballerina/tests/from_json_test.bal | 391 ++++++++-------- ballerina/tests/types.bal | 13 +- .../jsondata/compiler/CompilerPluginTest.java | 14 + .../sample_package_7/Ballerina.toml | 4 + .../sample_package_7/sample.bal | 31 ++ .../src/test/resources/testng.xml | 2 +- .../data/jsondata/compiler/Constants.java | 4 +- .../compiler/JsondataTypeValidator.java | 2 +- .../data/jsondata/json/JsonTraverse.java | 18 +- 12 files changed, 509 insertions(+), 428 deletions(-) create mode 100644 compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/Ballerina.toml create mode 100644 compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/sample.bal diff --git a/README.md b/README.md index 1a3638f..89f508e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The Ballerina JSON Data Library is a comprehensive toolkit designed to facilitat ## Features -- **Versatile JSON Data Input**: Accept JSON data as a json, a string, byte array, or a stream and convert it into a subtype of anydata value. +- **Versatile JSON Data Input**: Accept JSON data as a ballerina JSON value, a string, byte array, or a stream and convert it into a subtype of anydata. - **JSON to anydata Value Conversion**: Transform JSON data into expected type which is subtype of anydata. - **Projection Support**: Perform selective conversion of JSON data subsets into anydata values through projection. @@ -12,7 +12,7 @@ The Ballerina JSON Data Library is a comprehensive toolkit designed to facilitat ### Converting JSON Document value to a record value -To convert an JSON document value to a Record value, you can utilize the `fromJsonWithType` function provided by the library. The example below showcases the transformation of an JSON document value into a Record value. +To convert an JSON document value to a record value, you can utilize the `fromJsonWithType` function provided by the library. The example below showcases the transformation of an JSON document value into a record value. ```ballerina import ballerina/data.jsondata; @@ -32,13 +32,13 @@ public function main() returns error? { }; Book book = check jsondata:fromJsonWithType(jsonContent); - io:println(b); + io:println(book); } ``` ### Converting external JSON document to a record value -For transforming JSON content from an external source into a Record value, the `fromJsonStringWithType` function can be used. This external source can be in the form of a string or a byte array/byte stream that houses the JSON data. This is commonly extracted from files or network sockets. The example below demonstrates the conversion of an JSON value from an external source into a Record value. +For transforming JSON content from an external source into a record value, the `fromJsonStringWithType` function can be used. This external source can be in the form of a string or a byte array/byte stream that houses the JSON data. This is commonly extracted from files or network sockets. The example below demonstrates the conversion of an JSON value from an external source into a record value. ```ballerina import ballerina/data.jsondata; @@ -65,9 +65,10 @@ The conversion of JSON data to subtype of anydata representation is a fundamenta ### JSON Object -The JSON Object can be represented as a record value in Ballerina which facilitates a structured and type-safe approach to handling JSON data. +The JSON Object can be represented as a value of type record/map in Ballerina which facilitates a structured and type-safe approach to handling JSON data. Take for instance the following JSON Object snippet: + ```json { "author": "Robert C. Martin", @@ -85,6 +86,7 @@ Take for instance the following JSON Object snippet: ``` This JSON Object can be represented as a record value in Ballerina as follows: + ```ballerina type Author record { string author; @@ -134,6 +136,7 @@ The JSON Array can be represented as an array/tuple values in Ballerina. ``` This JSON Array can be converted as an array/tuple in Ballerina as follows: + ```ballerina type Book record { string name; @@ -162,7 +165,7 @@ public function main() returns error? { ### Controlling the JSON to record conversion -The library allows for selective conversion of JSON into records through the use of fields. This is beneficial when the JSON data contains elements that are not necessary to be transformed into record fields. +The library allows for selective conversion of JSON into closed records. This is beneficial when the JSON data contains members that are not necessary to be transformed into record fields. ```json { @@ -194,7 +197,7 @@ public function main() returns error? { } ``` -However, if the rest field is utilized (or if the record type is defined as an open record), all elements in the JSON data will be transformed into record fields: +However, if the rest field is utilized (or if the record type is defined as an open record), all members in the JSON data will be transformed into record fields: ```ballerina type Book record { @@ -203,15 +206,15 @@ type Book record { } ``` -In this instance, all other elements in the JSON data, such as `year` and `publisher` will be transformed into `string` type fields with the corresponding json object member as the key. +In this instance, all other members in the JSON data, such as `year` and `publisher` will be transformed into `anydata-typed` fields with the corresponding JSON object member as the key-value pair. This behavior extends to arrays as well. -The process of projecting JSON data into a record supports various use cases, including the filtering out of unnecessary elements. This functionality is anticipated to be enhanced in the future to accommodate more complex scenarios, such as filtering values based on regular expressions, among others. +The process of projecting JSON data into a record supports various use cases, including the filtering out of unnecessary members. This functionality is anticipated to be enhanced in the future to accommodate more complex scenarios, such as filtering values based on regular expressions, among others. ## Issues and projects -Issues and Projects tabs are disabled for this repository as this is part of the Ballerina standard library. To report bugs, request new features, start new discussions, view project boards, etc. please visit Ballerina standard library [parent repository](https://github.com/ballerina-platform/ballerina-standard-library). +Issues and Projects tabs are disabled for this repository as this is part of the Ballerina library. To report bugs, request new features, start new discussions, view project boards, etc. please visit Ballerina library [parent repository](https://github.com/ballerina-platform/ballerina-library). This repository only contains the source code for the package. diff --git a/ballerina/json_api.bal b/ballerina/json_api.bal index bc3b599..d5ae7ef 100644 --- a/ballerina/json_api.bal +++ b/ballerina/json_api.bal @@ -21,7 +21,7 @@ import ballerina/jballerina.java; # + v - Source JSON value # + options - Options to be used for filtering in the projection # + t - Target type -# + return - On success, returns the given target type value, else returns an `jsondata:Error` +# + return - On success, returns value belonging to the given target type, else returns an `jsondata:Error` value. public isolated function fromJsonWithType(json v, Options options = {}, typedesc t = <>) returns t|Error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; @@ -30,7 +30,7 @@ public isolated function fromJsonWithType(json v, Options options = {}, typedesc # + s - Source JSON string value or byte[] or byte-block-stream # + options - Options to be used for filtering in the projection # + t - Target type -# + return - On success, returns the given target type value, else returns an `jsondata:Error` +# + return - On success, value belonging to the given target type, else returns an `jsondata:Error` value. public isolated function fromJsonStringWithType(string|byte[]|stream s, Options options = {}, typedesc t = <>) returns t|Error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; diff --git a/ballerina/tests/from_json_string_test.bal b/ballerina/tests/from_json_string_test.bal index ec1d245..2e32730 100644 --- a/ballerina/tests/from_json_string_test.bal +++ b/ballerina/tests/from_json_string_test.bal @@ -16,31 +16,33 @@ import ballerina/test; -// Possitive tests for fromJsonStringWithType() function. - -@test:Config -isolated function testJsonStringToBasicTypes() returns error? { - int val1 = check fromJsonStringWithType("5"); - test:assertEquals(val1, 5); - - float val2 = check fromJsonStringWithType("5.5"); - test:assertEquals(val2, 5.5); - - decimal val3 = check fromJsonStringWithType("5.5"); - test:assertEquals(val3, 5.5d); - - string val4 = check fromJsonStringWithType("hello"); - test:assertEquals(val4, "hello"); +@test:Config { + dataProvider: basicTypeDataProviderForFromJsonStringWithType +} +isolated function testJsonStringToBasicTypes(string sourceData, typedesc expType, + anydata expectedData) returns Error? { + anydata val1 = check fromJsonStringWithType(sourceData, {}, expType); + test:assertEquals(val1, expectedData); +} - boolean val5 = check fromJsonStringWithType("true"); - test:assertEquals(val5, true); +function basicTypeDataProviderForFromJsonStringWithType() returns [string, typedesc, anydata][] { + return [ + ["5", int, 5], + ["5.5", float, 5.5], + ["5.5", decimal, 5.5d], + ["hello", string, "hello"], + ["true", boolean, true] + ]; +} - () val6 = check fromJsonStringWithType("null"); - test:assertEquals(val6, null); +@test:Config +isolated function testNilAsExpectedTypeWithFromJsonStringWithType() returns error? { + () val = check fromJsonStringWithType("null"); + test:assertEquals(val, null); } @test:Config -isolated function testSimpleJsonStringToRecord() returns error? { +isolated function testSimpleJsonStringToRecord() returns Error? { string j = string `{"a": "hello", "b": 1}`; SimpleRec1 recA = check fromJsonStringWithType(j); @@ -57,7 +59,7 @@ isolated function testSimpleJsonStringToRecord() returns error? { } @test:Config -isolated function testSimpleJsonStringToRecordWithProjection() returns error? { +isolated function testSimpleJsonStringToRecordWithProjection() returns Error? { string str = string `{"a": "hello", "b": 1}`; record {|string a;|} recA = check fromJsonStringWithType(str); @@ -67,7 +69,7 @@ isolated function testSimpleJsonStringToRecordWithProjection() returns error? { } @test:Config -isolated function testNestedJsonStringToRecord() returns error? { +isolated function testNestedJsonStringToRecord() returns Error? { string str = string `{ "a": "hello", "b": 1, @@ -100,7 +102,7 @@ isolated function testNestedJsonStringToRecord() returns error? { } @test:Config -isolated function testNestedJsonStringToRecordWithProjection() returns error? { +isolated function testNestedJsonStringToRecordWithProjection() returns Error? { string str = string `{ "a": "hello", "b": 1, @@ -117,7 +119,7 @@ isolated function testNestedJsonStringToRecordWithProjection() returns error? { } @test:Config -isolated function testJsonStringToRecordWithOptionalFields() returns error? { +isolated function testJsonStringToRecordWithOptionalFields() returns Error? { string str = string `{"a": "hello"}`; record {|string a; int b?;|} recA = check fromJsonStringWithType(str); @@ -127,7 +129,7 @@ isolated function testJsonStringToRecordWithOptionalFields() returns error? { } @test:Config -isolated function testJsonStringToRecordWithOptionalFieldsWithProjection() returns error? { +isolated function testJsonStringToRecordWithOptionalFieldsWithProjection() returns Error? { string str = string `{ "a": "hello", "b": 1, @@ -144,7 +146,7 @@ isolated function testJsonStringToRecordWithOptionalFieldsWithProjection() retur } @test:Config -isolated function testFromJsonStringWithType1() returns error? { +isolated function testFromJsonStringWithType1() returns Error? { string str = string `{ "id": 2, "name": "Anne", @@ -162,7 +164,7 @@ isolated function testFromJsonStringWithType1() returns error? { } @test:Config -isolated function testMapTypeAsFieldTypeInRecordForJsonString() returns error? { +isolated function testMapTypeAsFieldTypeInRecordForJsonString() returns Error? { string str = string `{ "employees": { "John": "Manager", @@ -176,7 +178,7 @@ isolated function testMapTypeAsFieldTypeInRecordForJsonString() returns error? { } @test:Config -isolated function testFromJsonStringWithType2() returns error? { +isolated function testFromJsonStringWithType2() returns Error? { string str = string `{ "name": "John", "age": 30, @@ -203,7 +205,7 @@ isolated function testFromJsonStringWithType2() returns error? { } @test:Config -isolated function testFromJsonStringWithType3() returns error? { +isolated function testFromJsonStringWithType3() returns Error? { string str = string `{ "title": "To Kill a Mockingbird", "author": { @@ -234,7 +236,7 @@ isolated function testFromJsonStringWithType3() returns error? { } @test:Config -isolated function testFromJsonStringWithType4() returns error? { +isolated function testFromJsonStringWithType4() returns Error? { string str = string `{ "name": "School Twelve", "city": 23, @@ -254,7 +256,7 @@ isolated function testFromJsonStringWithType4() returns error? { } @test:Config -isolated function testFromJsonStringWithType5() returns error? { +isolated function testFromJsonStringWithType5() returns Error? { string str = string `{ "intValue": 10, "floatValue": 10.5, @@ -273,7 +275,7 @@ isolated function testFromJsonStringWithType5() returns error? { } @test:Config -isolated function testFromJsonStringWithType6() returns error? { +isolated function testFromJsonStringWithType6() returns Error? { string str = string `{ "id": 1, "name": "Class A", @@ -314,7 +316,7 @@ isolated function testFromJsonStringWithType6() returns error? { } @test:Config -isolated function testFromJsonStringWithType7() returns error? { +isolated function testFromJsonStringWithType7() returns Error? { string nestedJsonStr = string `{ "intValue": 5, "floatValue": 2.5, @@ -335,7 +337,7 @@ isolated function testFromJsonStringWithType7() returns error? { } @test:Config -isolated function testFromJsonStringWithType8() returns error? { +isolated function testFromJsonStringWithType8() returns Error? { string str = string `{ "street": "Main", "city": "Mahar", @@ -348,7 +350,7 @@ isolated function testFromJsonStringWithType8() returns error? { } @test:Config -isolated function testFromJsonStringWithType9() returns error? { +isolated function testFromJsonStringWithType9() returns Error? { string str = string `{ "street": "Main", "city": "Mahar", @@ -363,7 +365,7 @@ isolated function testFromJsonStringWithType9() returns error? { } @test:Config -isolated function testFromJsonStringWithType10() returns error? { +isolated function testFromJsonStringWithType10() returns Error? { string str = string `{ "street": "Main", "city": 11, @@ -378,7 +380,7 @@ isolated function testFromJsonStringWithType10() returns error? { } @test:Config -isolated function testFromJsonStringWithType11() returns error? { +isolated function testFromJsonStringWithType11() returns Error? { string str = string `{ "street": "Main", "city": "Mahar", @@ -393,7 +395,7 @@ isolated function testFromJsonStringWithType11() returns error? { } @test:Config -isolated function testFromJsonStringWithType12() returns error? { +isolated function testFromJsonStringWithType12() returns Error? { string str = string `{ "street": "Main", "city": { @@ -410,7 +412,7 @@ isolated function testFromJsonStringWithType12() returns error? { } @test:Config -isolated function testFromJsonStringWithType13() returns error? { +isolated function testFromJsonStringWithType13() returns Error? { string str = string `{ "street": "Main", "city": "Mahar", @@ -425,7 +427,7 @@ isolated function testFromJsonStringWithType13() returns error? { } @test:Config -isolated function testFromJsoStringWithType14() returns error? { +isolated function testFromJsonStringWithType14() returns Error? { string str = string `{ "id": 12, "name": "Anne", @@ -447,7 +449,7 @@ isolated function testFromJsoStringWithType14() returns error? { } @test:Config -isolated function testFromJsonStringWithType15() returns error? { +isolated function testFromJsonStringWithType15() returns Error? { string str = string `[1, 2, 3]`; IntArr x = check fromJsonStringWithType(str); @@ -455,15 +457,15 @@ isolated function testFromJsonStringWithType15() returns error? { } @test:Config -isolated function testFromJsonStringWithType16() returns error? { +isolated function testFromJsonStringWithType16() returns Error? { string str = string `[1, "abc", [3, 4.0]]`; - TUPLE x = check fromJsonStringWithType(str); + Tuple x = check fromJsonStringWithType(str); test:assertEquals(x, [1, "abc", [3, 4.0]]); } @test:Config -isolated function testFromJsonStringWithType17() returns error? { +isolated function testFromJsonStringWithType17() returns Error? { string str = string `{ "street": "Main", "city": { @@ -484,7 +486,7 @@ isolated function testFromJsonStringWithType17() returns error? { } @test:Config -isolated function testFromJsonStringWithType18() returns error? { +isolated function testFromJsonStringWithType18() returns Error? { string str = string `{ "books": [ { @@ -519,7 +521,7 @@ type LibraryC record {| |}; @test:Config -isolated function testFromJsonStringWithType19() returns error? { +isolated function testFromJsonStringWithType19() returns Error? { string str = string `{ "books": [ { @@ -555,7 +557,7 @@ isolated function testFromJsonStringWithType19() returns error? { } @test:Config -isolated function testFromJsonStringWithType20() returns error? { +isolated function testFromJsonStringWithType20() returns Error? { string str1 = string `{ "a": { "c": "world", @@ -587,7 +589,7 @@ isolated function testFromJsonStringWithType20() returns error? { test:assertEquals(val2["a"]["d"], "2"); test:assertEquals(val2["b"]["c"], "world"); test:assertEquals(val2["b"]["d"], "2"); - + string str3 = string `{ "a": [{ "c": "world", @@ -606,18 +608,22 @@ isolated function testFromJsonStringWithType20() returns error? { |}[]...; |} val3 = check fromJsonStringWithType(str3); test:assertEquals(val3.length(), 2); - test:assertEquals(val3["a"], [{ + test:assertEquals(val3["a"], [ + { "c": "world", "d": "2" - }]); - test:assertEquals(val3["b"], [{ + } + ]); + test:assertEquals(val3["b"], [ + { "c": "world", "d": "2" - }]); + } + ]); } @test:Config -isolated function testUnionTypeAsExpTypeForFromJsonStringWithType() returns error? { +isolated function testUnionTypeAsExpTypeForFromJsonStringWithType() returns Error? { decimal|float val1 = check fromJsonStringWithType("1.0"); test:assertEquals(val1, 1.0); @@ -643,7 +649,7 @@ isolated function testUnionTypeAsExpTypeForFromJsonStringWithType() returns erro }`; record {| - record {| decimal|int b; record {| string|boolean e; |} d; |} a; + record {|decimal|int b; record {|string|boolean e;|} d;|} a; decimal|float c; |} val3 = check fromJsonStringWithType(str3); test:assertEquals(val3.length(), 2); @@ -654,7 +660,7 @@ isolated function testUnionTypeAsExpTypeForFromJsonStringWithType() returns erro } @test:Config -isolated function testAnydataAsExpTypeForFromJsonStringWithType() returns error? { +isolated function testAnydataAsExpTypeForFromJsonStringWithType() returns Error? { string jsonStr1 = string `1`; anydata val1 = check fromJsonStringWithType(jsonStr1); test:assertEquals(val1, 1); @@ -699,7 +705,7 @@ isolated function testAnydataAsExpTypeForFromJsonStringWithType() returns error? } @test:Config -isolated function testJsonAsExpTypeForFromJsonStringWithType() returns error? { +isolated function testJsonAsExpTypeForFromJsonStringWithType() returns Error? { string jsonStr1 = string `1`; json val1 = check fromJsonStringWithType(jsonStr1); test:assertEquals(val1, 1); @@ -744,7 +750,7 @@ isolated function testJsonAsExpTypeForFromJsonStringWithType() returns error? { } @test:Config -isolated function testMapAsExpTypeForFromJsonStringWithType() returns error? { +isolated function testMapAsExpTypeForFromJsonStringWithType() returns Error? { string jsonStr1 = string `{ "a": "hello", "b": 1 @@ -794,10 +800,30 @@ isolated function testMapAsExpTypeForFromJsonStringWithType() returns error? { int d; |}> val5 = check fromJsonStringWithType(jsonStr3); test:assertEquals(val5, {"a": {"c": "world", "d": 2}, "b": {"c": "world", "d": 2}}); + + string jsonStr6 = string `{ + "a": Kanth, + "b": { + "g": { + "c": "hello", + "d": 1 + }, + "h": { + "c": "world", + "d": 2 + } + } + }`; + record {| + string a; + map> b; + |} val6 = check fromJsonStringWithType(jsonStr6); + test:assertEquals(val6.a, "Kanth"); + test:assertEquals(val6.b, {"g": {"c": "hello", "d": "1"}, "h": {"c": "world", "d": "2"}}); } @test:Config -isolated function testProjectionInTupleForFromJsonStringWithType() returns error? { +isolated function testProjectionInTupleForFromJsonStringWithType() returns Error? { string str1 = string `[1, 2, 3, 4, 5, 8]`; [string, float] val1 = check fromJsonStringWithType(str1); test:assertEquals(val1, ["1", 2.0]); @@ -805,24 +831,24 @@ isolated function testProjectionInTupleForFromJsonStringWithType() returns error string str2 = string `{ "a": [1, 2, 3, 4, 5, 8] }`; - record {| [string, float] a; |} val2 = check fromJsonStringWithType(str2); + record {|[string, float] a;|} val2 = check fromJsonStringWithType(str2); test:assertEquals(val2.a, ["1", 2.0]); string str3 = string `[1, "4"]`; - [float] val3 = check fromJsonStringWithType(str3); + [float] val3 = check fromJsonStringWithType(str3); test:assertEquals(val3, [1.0]); string str4 = string `["1", {}]`; - [float] val4 = check fromJsonStringWithType(str4); + [float] val4 = check fromJsonStringWithType(str4); test:assertEquals(val4, [1.0]); string str5 = string `["1", [], {"name": 1}]`; - [float] val5 = check fromJsonStringWithType(str5); + [float] val5 = check fromJsonStringWithType(str5); test:assertEquals(val5, [1.0]); } @test:Config -isolated function testProjectionInArrayForFromJsonStringWithType() returns error? { +isolated function testProjectionInArrayForFromJsonStringWithType() returns Error? { string strVal = string `[1, 2, 3, 4, 5]`; int[] val = check fromJsonStringWithType(strVal); test:assertEquals(val, [1, 2, 3, 4, 5]); @@ -834,14 +860,14 @@ isolated function testProjectionInArrayForFromJsonStringWithType() returns error string strVal3 = string `{ "a": [1, 2, 3, 4, 5] }`; - record {| int[2] a; |} val3 = check fromJsonStringWithType(strVal3); + record {|int[2] a;|} val3 = check fromJsonStringWithType(strVal3); test:assertEquals(val3, {a: [1, 2]}); string strVal4 = string `{ "a": [1, 2, 3, 4, 5], "b": [1, 2, 3, 4, 5] }`; - record {| int[2] a; int[3] b; |} val4 = check fromJsonStringWithType(strVal4); + record {|int[2] a; int[3] b;|} val4 = check fromJsonStringWithType(strVal4); test:assertEquals(val4, {a: [1, 2], b: [1, 2, 3]}); string strVal5 = string `{ @@ -854,7 +880,7 @@ isolated function testProjectionInArrayForFromJsonStringWithType() returns error } ] }`; - record {| record {| string name; int age; |}[1] employees; |} val5 = check fromJsonStringWithType(strVal5); + record {|record {|string name; int age;|}[1] employees;|} val5 = check fromJsonStringWithType(strVal5); test:assertEquals(val5, {employees: [{name: "Prakanth", age: 26}]}); string strVal6 = string `["1", 2, 3, { "a" : val_a }]`; @@ -863,13 +889,13 @@ isolated function testProjectionInArrayForFromJsonStringWithType() returns error } @test:Config -isolated function testProjectionInRecordForFromJsonStringWithType() returns error? { +isolated function testProjectionInRecordForFromJsonStringWithType() returns Error? { string jsonStr1 = string `{"name": "John", "age": 30, "city": "New York"}`; - record {| string name; string city; |} val1 = check fromJsonStringWithType(jsonStr1); + record {|string name; string city;|} val1 = check fromJsonStringWithType(jsonStr1); test:assertEquals(val1, {name: "John", city: "New York"}); string jsonStr2 = string `{"name": John, "age": "30", "city": "New York"}`; - record {| string name; string city; |} val2 = check fromJsonStringWithType(jsonStr2); + record {|string name; string city;|} val2 = check fromJsonStringWithType(jsonStr2); test:assertEquals(val2, {name: "John", city: "New York"}); string jsonStr3 = string `{ "name": "John", @@ -882,9 +908,9 @@ isolated function testProjectionInRecordForFromJsonStringWithType() returns erro } }, "city": "New York" }`; - record {| string name; string city; |} val3 = check fromJsonStringWithType(jsonStr3); + record {|string name; string city;|} val3 = check fromJsonStringWithType(jsonStr3); test:assertEquals(val3, {name: "John", city: "New York"}); - + string jsonStr4 = string `{ "name": "John", "company": [{ "name": "wso2", @@ -895,7 +921,7 @@ isolated function testProjectionInRecordForFromJsonStringWithType() returns erro } }], "city": "New York" }`; - record {| string name; string city; |} val4 = check fromJsonStringWithType(jsonStr4); + record {|string name; string city;|} val4 = check fromJsonStringWithType(jsonStr4); test:assertEquals(val4, {name: "John", city: "New York"}); string jsonStr5 = string `{ "name": "John", @@ -917,12 +943,12 @@ isolated function testProjectionInRecordForFromJsonStringWithType() returns erro } }] }`; - record {| string name; string city; |} val5 = check fromJsonStringWithType(jsonStr5); + record {|string name; string city;|} val5 = check fromJsonStringWithType(jsonStr5); test:assertEquals(val5, {name: "John", city: "New York"}); } @test:Config -isolated function testArrayOrTupleCaseForFromJsonStringWithType() returns error? { +isolated function testArrayOrTupleCaseForFromJsonStringWithType() returns Error? { string jsonStr1 = string `[["1"], 2.0]`; [[int], float] val1 = check fromJsonStringWithType(jsonStr1); test:assertEquals(val1, [[1], 2.0]); @@ -930,7 +956,7 @@ isolated function testArrayOrTupleCaseForFromJsonStringWithType() returns error? string jsonStr2 = string `[["1", 2], 2.0]`; [[int, float], string] val2 = check fromJsonStringWithType(jsonStr2); test:assertEquals(val2, [[1, 2.0], "2.0"]); - + string jsonStr3 = string `[["1", 2], [2, "3"]]`; int[][] val3 = check fromJsonStringWithType(jsonStr3); test:assertEquals(val3, [[1, 2], [2, 3]]); @@ -960,10 +986,10 @@ isolated function testArrayOrTupleCaseForFromJsonStringWithType() returns error? } @test:Config -isolated function testListFillerValuesWithFromJsonStringWithType() returns error? { +isolated function testListFillerValuesWithFromJsonStringWithType() returns Error? { int[2] jsonVal1 = check fromJsonStringWithType("[1]"); test:assertEquals(jsonVal1, [1, 0]); - + [int, float, string, boolean] jsonVal2 = check fromJsonStringWithType("[1]"); test:assertEquals(jsonVal2, [1, 0.0, "", false]); @@ -975,7 +1001,7 @@ isolated function testListFillerValuesWithFromJsonStringWithType() returns error } @test:Config -isolated function testSingletonAsExpectedTypeForFromJsonStringWithType() returns error? { +isolated function testSingletonAsExpectedTypeForFromJsonStringWithType() returns Error? { "1" val1 = check fromJsonStringWithType("1"); test:assertEquals(val1, "1"); @@ -998,7 +1024,7 @@ isolated function testSingletonAsExpectedTypeForFromJsonStringWithType() returns } @test:Config -function testDuplicateKeyInTheStringSource() returns error? { +function testDuplicateKeyInTheStringSource() returns Error? { string str = string `{ "id": 1, "name": "Anne", @@ -1015,7 +1041,7 @@ function testDuplicateKeyInTheStringSource() returns error? { } @test:Config -function testNameAnnotationWithFromJsonStringWithType() returns error? { +function testNameAnnotationWithFromJsonStringWithType() returns Error? { string jsonStr = string `{ "id": 1, "title-name": "Harry Potter", @@ -1029,13 +1055,13 @@ function testNameAnnotationWithFromJsonStringWithType() returns error? { } @test:Config -isolated function testByteAsExpectedTypeForFromJsonStringWithType() returns error? { +isolated function testByteAsExpectedTypeForFromJsonStringWithType() returns Error? { byte val1 = check fromJsonStringWithType("1"); test:assertEquals(val1, 1); [byte, int] val2 = check fromJsonStringWithType("[255, 2000]"); test:assertEquals(val2, [255, 2000]); - + string str4 = string `{ "id": 1, "name": "Anne", @@ -1065,7 +1091,7 @@ isolated function testByteAsExpectedTypeForFromJsonStringWithType() returns erro } @test:Config -isolated function testSignedInt8AsExpectedTypeForFromJsonStringWithType() returns error? { +isolated function testSignedInt8AsExpectedTypeForFromJsonStringWithType() returns Error? { int:Signed8 val1 = check fromJsonStringWithType("-128"); test:assertEquals(val1, -128); @@ -1074,7 +1100,7 @@ isolated function testSignedInt8AsExpectedTypeForFromJsonStringWithType() return [int:Signed8, int] val3 = check fromJsonStringWithType("[127, 2000]"); test:assertEquals(val3, [127, 2000]); - + string str4 = string `{ "id": 100, "name": "Anne", @@ -1104,7 +1130,7 @@ isolated function testSignedInt8AsExpectedTypeForFromJsonStringWithType() return } @test:Config -isolated function testSignedInt16AsExpectedTypeForFromJsonStringWithType() returns error? { +isolated function testSignedInt16AsExpectedTypeForFromJsonStringWithType() returns Error? { int:Signed16 val1 = check fromJsonStringWithType("-32768"); test:assertEquals(val1, -32768); @@ -1113,7 +1139,7 @@ isolated function testSignedInt16AsExpectedTypeForFromJsonStringWithType() retur [int:Signed16, int] val3 = check fromJsonStringWithType("[32767, -324234]"); test:assertEquals(val3, [32767, -324234]); - + string str4 = string `{ "id": 100, "name": "Anne", @@ -1143,7 +1169,7 @@ isolated function testSignedInt16AsExpectedTypeForFromJsonStringWithType() retur } @test:Config -isolated function testSignedInt32AsExpectedTypeForFromJsonStringWithType() returns error? { +isolated function testSignedInt32AsExpectedTypeForFromJsonStringWithType() returns Error? { int:Signed32 val1 = check fromJsonStringWithType("-2147483648"); test:assertEquals(val1, -2147483648); @@ -1152,7 +1178,7 @@ isolated function testSignedInt32AsExpectedTypeForFromJsonStringWithType() retur int:Signed32[] val3 = check fromJsonStringWithType("[2147483647, -2147483648]"); test:assertEquals(val3, [2147483647, -2147483648]); - + string str4 = string `{ "id": 2147483647, "name": "Anne", @@ -1182,7 +1208,7 @@ isolated function testSignedInt32AsExpectedTypeForFromJsonStringWithType() retur } @test:Config -isolated function testUnSignedInt8AsExpectedTypeForFromJsonStringWithType() returns error? { +isolated function testUnSignedInt8AsExpectedTypeForFromJsonStringWithType() returns Error? { int:Unsigned8 val1 = check fromJsonStringWithType("255"); test:assertEquals(val1, 255); @@ -1191,7 +1217,7 @@ isolated function testUnSignedInt8AsExpectedTypeForFromJsonStringWithType() retu int:Unsigned8[] val3 = check fromJsonStringWithType("[0, 255]"); test:assertEquals(val3, [0, 255]); - + string str4 = string `{ "id": 0, "name": "Anne", @@ -1221,7 +1247,7 @@ isolated function testUnSignedInt8AsExpectedTypeForFromJsonStringWithType() retu } @test:Config -isolated function testUnSignedInt16AsExpectedTypeForFromJsonStringWithType() returns error? { +isolated function testUnSignedInt16AsExpectedTypeForFromJsonStringWithType() returns Error? { int:Unsigned16 val1 = check fromJsonStringWithType("65535"); test:assertEquals(val1, 65535); @@ -1230,7 +1256,7 @@ isolated function testUnSignedInt16AsExpectedTypeForFromJsonStringWithType() ret int:Unsigned16[] val3 = check fromJsonStringWithType("[0, 65535]"); test:assertEquals(val3, [0, 65535]); - + string str4 = string `{ "id": 0, "name": "Anne", @@ -1260,7 +1286,7 @@ isolated function testUnSignedInt16AsExpectedTypeForFromJsonStringWithType() ret } @test:Config -isolated function testUnSignedInt32AsExpectedTypeForFromJsonStringWithType() returns error? { +isolated function testUnSignedInt32AsExpectedTypeForFromJsonStringWithType() returns Error? { int:Unsigned32 val1 = check fromJsonStringWithType("4294967295"); test:assertEquals(val1, 4294967295); @@ -1269,7 +1295,7 @@ isolated function testUnSignedInt32AsExpectedTypeForFromJsonStringWithType() ret int:Unsigned32[] val3 = check fromJsonStringWithType("[0, 4294967295]"); test:assertEquals(val3, [0, 4294967295]); - + string str4 = string `{ "id": 0, "name": "Anne", @@ -1298,16 +1324,27 @@ isolated function testUnSignedInt32AsExpectedTypeForFromJsonStringWithType() ret test:assertEquals(val4.address.id, 4294967295); } -// TODO: Fix the behaviour and add tests @test:Config -isolated function testRecordWithRestAsExpectedTypeForFromJsonStringWithType() returns error? { - +isolated function testUnalignedJsonContent() returns error? { + string jsonStr = string ` +{ + "a" + : + "h +ello", + "b": + 1 + }`; + record {| + string a; + int b; + |} val = check fromJsonStringWithType(jsonStr); + test:assertEquals(val.a, "h\nello"); + test:assertEquals(val.b, 1); } -// Negative tests for fromJsonStringWithType() function. - @test:Config -isolated function testFromJsonStringWithTypeNegative1() returns error? { +isolated function testFromJsonStringWithTypeNegative1() returns Error? { string str = string `{ "id": 12, "name": "Anne", @@ -1319,23 +1356,23 @@ isolated function testFromJsonStringWithTypeNegative1() returns error? { }`; RN|Error x = fromJsonStringWithType(str); - test:assertTrue(x is error); - test:assertEquals((x).message(), "incompatible value 'true' for type 'int' in field 'address.id'"); + test:assertTrue(x is Error); + test:assertEquals((x).message(), "incompatible value 'true' for type 'int' in field 'address.id'"); } @test:Config -isolated function testFromJsonStringWithTypeNegative2() returns error? { +isolated function testFromJsonStringWithTypeNegative2() returns Error? { string str = string `{ "id": 12 }`; RN2|Error x = fromJsonStringWithType(str); - test:assertTrue(x is error); - test:assertEquals((x).message(), "required field 'name' not present in JSON"); + test:assertTrue(x is Error); + test:assertEquals((x).message(), "required field 'name' not present in JSON"); } @test:Config -isolated function testFromJsonStringWithTypeNegative3() returns error? { +isolated function testFromJsonStringWithTypeNegative3() returns Error? { string str = string `{ "id": 12, "name": "Anne", @@ -1346,51 +1383,51 @@ isolated function testFromJsonStringWithTypeNegative3() returns error? { }`; RN|Error x = fromJsonStringWithType(str); - test:assertTrue(x is error); - test:assertEquals((x).message(), "required field 'id' not present in JSON"); + test:assertTrue(x is Error); + test:assertEquals((x).message(), "required field 'id' not present in JSON"); } @test:Config -isolated function testFromJsonStringWithTypeNegative4() returns error? { +isolated function testFromJsonStringWithTypeNegative4() returns Error? { string str = string `{ name: "John" }`; int|Error x = fromJsonStringWithType(str); - test:assertTrue(x is error); - test:assertEquals((x).message(), "invalid type 'int' expected 'map type'"); + test:assertTrue(x is Error); + test:assertEquals((x).message(), "invalid type 'int' expected 'map type'"); Union|Error y = fromJsonStringWithType(str); - test:assertTrue(y is error); - test:assertEquals((y).message(), "unsupported type 'ballerina/data.jsondata:0:Union'"); + test:assertTrue(y is Error); + test:assertEquals((y).message(), "unsupported type 'ballerina/data.jsondata:0:Union'"); table|Error z = fromJsonStringWithType(str); - test:assertTrue(z is error); - test:assertEquals((z).message(), "unsupported type 'table'"); + test:assertTrue(z is Error); + test:assertEquals((z).message(), "unsupported type 'table'"); RN2|Error a = fromJsonStringWithType("1"); - test:assertTrue(a is error); - test:assertEquals((a).message(), "incompatible expected type 'data.jsondata:RN2' for value '1'"); + test:assertTrue(a is Error); + test:assertEquals((a).message(), "incompatible expected type 'data.jsondata:RN2' for value '1'"); } @test:Config -isolated function testDuplicateFieldInRecordTypeWithFromJsonStringWithType() returns error? { +isolated function testDuplicateFieldInRecordTypeWithFromJsonStringWithType() returns Error? { string str = string `{ "title": "Clean Code", "author": "Robert C. Martin", `; BookN|Error x = fromJsonStringWithType(str); - test:assertTrue(x is error); - test:assertEquals((x).message(), "duplicate field 'author'"); + test:assertTrue(x is Error); + test:assertEquals((x).message(), "duplicate field 'author'"); } @test:Config isolated function testProjectionInArrayNegativeForFromJsonStringWithType() { string strVal1 = string `["1", 2, 3, { "a" : val_a }]`; - int[]|error val1 = fromJsonStringWithType(strVal1); - test:assertTrue(val1 is error); - test:assertEquals((val1).message(), "invalid type 'int' expected 'map type'"); + int[]|Error val1 = fromJsonStringWithType(strVal1); + test:assertTrue(val1 is Error); + test:assertEquals((val1).message(), "invalid type 'int' expected 'map type'"); } @test:Config @@ -1408,9 +1445,9 @@ isolated function testUnionTypeAsExpTypeForFromJsonStringWithTypeNegative() { "subject": "Bio" } ]`; - (map|int|float)[]|error err1 = fromJsonStringWithType(str1); - test:assertTrue(err1 is error); - test:assertEquals(( err1).message(), "incompatible expected type '(map|int|float)' for value 'Lakshan'"); + (map|int|float)[]|Error err1 = fromJsonStringWithType(str1); + test:assertTrue(err1 is Error); + test:assertEquals((err1).message(), "incompatible expected type '(map|int|float)' for value 'Lakshan'"); string str2 = string `[ { @@ -1423,9 +1460,9 @@ isolated function testUnionTypeAsExpTypeForFromJsonStringWithTypeNegative() { "subject": "Bio" } ]`; - (map|int|float)[]|error err2 = fromJsonStringWithType(str2); - test:assertTrue(err2 is error); - test:assertEquals(( err2).message(), "unsupported type '(map|int|float)'"); + (map|int|float)[]|Error err2 = fromJsonStringWithType(str2); + test:assertTrue(err2 is Error); + test:assertEquals((err2).message(), "unsupported type '(map|int|float)'"); string str3 = string `{ "a": "hello", @@ -1435,73 +1472,89 @@ isolated function testUnionTypeAsExpTypeForFromJsonStringWithTypeNegative() { "e": 2 } }`; - (map|int|float)|error err3 = fromJsonStringWithType(str3); - test:assertTrue(err3 is error); - test:assertEquals(( err3).message(), "unsupported type '(map|int|float)'"); + (map|int|float)|Error err3 = fromJsonStringWithType(str3); + test:assertTrue(err3 is Error); + test:assertEquals((err3).message(), "unsupported type '(map|int|float)'"); } -@test:Config -isolated function testSubTypeOfIntAsExptypeNegative() { - byte|error err1 = fromJsonStringWithType("256"); - test:assertTrue(err1 is error); - test:assertEquals(( err1).message(), "incompatible expected type 'byte' for value '256'"); - - byte|error err2 = fromJsonStringWithType("-1"); - test:assertTrue(err2 is error); - test:assertEquals(( err2).message(), "incompatible expected type 'byte' for value '-1'"); - - int:Signed8|error err3 = fromJsonStringWithType("128"); - test:assertTrue(err3 is error); - test:assertEquals(( err3).message(), "incompatible expected type 'lang.int:Signed8' for value '128'"); - - int:Signed8|error err4 = fromJsonStringWithType("-129"); - test:assertTrue(err4 is error); - test:assertEquals(( err4).message(), "incompatible expected type 'lang.int:Signed8' for value '-129'"); - - int:Unsigned8|error err5 = fromJsonStringWithType("256"); - test:assertTrue(err5 is error); - test:assertEquals(( err5).message(), "incompatible expected type 'lang.int:Unsigned8' for value '256'"); - - int:Unsigned8|error err6 = fromJsonStringWithType("-1"); - test:assertTrue(err6 is error); - test:assertEquals(( err6).message(), "incompatible expected type 'lang.int:Unsigned8' for value '-1'"); - - int:Signed16|error err7 = fromJsonStringWithType("32768"); - test:assertTrue(err7 is error); - test:assertEquals(( err7).message(), "incompatible expected type 'lang.int:Signed16' for value '32768'"); - - int:Signed16|error err8 = fromJsonStringWithType("-32769"); - test:assertTrue(err8 is error); - test:assertEquals(( err8).message(), "incompatible expected type 'lang.int:Signed16' for value '-32769'"); - - int:Unsigned16|error err9 = fromJsonStringWithType("65536"); - test:assertTrue(err9 is error); - test:assertEquals(( err9).message(), "incompatible expected type 'lang.int:Unsigned16' for value '65536'"); - - int:Unsigned16|error err10 = fromJsonStringWithType("-1"); - test:assertTrue(err10 is error); - test:assertEquals(( err10).message(), "incompatible expected type 'lang.int:Unsigned16' for value '-1'"); +@test:Config { + dataProvider: dataProviderForSubTypeOfIntNegativeTestForFromJsonStringWithType +} +isolated function testSubTypeOfIntAsExptypeNegative(string sourceData, typedesc expType, string expectedError) { + anydata|Error err = fromJsonStringWithType(sourceData, {}, expType); + test:assertTrue(err is Error); + test:assertEquals((err).message(), expectedError); +} - int:Signed32|error err11 = fromJsonStringWithType("2147483648"); - test:assertTrue(err11 is error); - test:assertEquals(( err11).message(), "incompatible expected type 'lang.int:Signed32' for value '2147483648'"); +function dataProviderForSubTypeOfIntNegativeTestForFromJsonStringWithType() returns [string, typedesc, string][] { + string incompatibleStr = "incompatible expected type "; + return [ + ["256", byte, incompatibleStr + "'byte' for value '256'"], + ["-1", byte, incompatibleStr + "'byte' for value '-1'"], + ["128", int:Signed8, incompatibleStr + "'lang.int:Signed8' for value '128'"], + ["-129", int:Signed8, incompatibleStr + "'lang.int:Signed8' for value '-129'"], + ["256", int:Unsigned8, incompatibleStr + "'lang.int:Unsigned8' for value '256'"], + ["-1", int:Unsigned8, incompatibleStr + "'lang.int:Unsigned8' for value '-1'"], + ["32768", int:Signed16, incompatibleStr + "'lang.int:Signed16' for value '32768'"], + ["-32769", int:Signed16, incompatibleStr + "'lang.int:Signed16' for value '-32769'"], + ["65536", int:Unsigned16, incompatibleStr + "'lang.int:Unsigned16' for value '65536'"], + ["-1", int:Unsigned16, incompatibleStr + "'lang.int:Unsigned16' for value '-1'"], + ["2147483648", int:Signed32, incompatibleStr + "'lang.int:Signed32' for value '2147483648'"], + ["-2147483649", int:Signed32, incompatibleStr + "'lang.int:Signed32' for value '-2147483649'"], + ["4294967296", int:Unsigned32, incompatibleStr + "'lang.int:Unsigned32' for value '4294967296'"], + ["-1", int:Unsigned32, incompatibleStr + "'lang.int:Unsigned32' for value '-1'"] + ]; +} - int:Signed32|error err12 = fromJsonStringWithType("-2147483649"); - test:assertTrue(err12 is error); - test:assertEquals(( err12).message(), "incompatible expected type 'lang.int:Signed32' for value '-2147483649'"); +@test:Config +isolated function testEmptyJsonDocumentNegative() { + string|Error err = fromJsonStringWithType(""); + test:assertTrue(err is Error); + test:assertEquals((err).message(), "'empty JSON document' at line: '1' column: '1'"); +} - int:Unsigned32|error err13 = fromJsonStringWithType("4294967296"); - test:assertTrue(err13 is error); - test:assertEquals(( err13).message(), "incompatible expected type 'lang.int:Unsigned32' for value '4294967296'"); +@test:Config +isolated function testRecordWithRestAsExpectedTypeForFromJsonStringWithTypeNegative() { + string personStr = string ` + { + "id": 1, + "name": "Anne", + "measurements": { + "height": 5.5, + "weight": 60 + } + }`; - int:Unsigned32|error err14 = fromJsonStringWithType("-1"); - test:assertTrue(err14 is error); - test:assertEquals(( err14).message(), "incompatible expected type 'lang.int:Unsigned32' for value '-1'"); + PersonA|error val = fromJsonStringWithType(personStr); + test:assertTrue(val is error); + test:assertEquals((val).message(), "incompatible expected type 'int' for value '5.5'"); } @test:Config -isolated function testEmptyJsonDocumentNegative() { - string|error err = fromJsonStringWithType(""); - test:assertTrue(err is error); - test:assertEquals(( err).message(), "'empty JSON document' at line: '1' column: '1'"); +function testComplexTypeAsUnionMemberAsExpTypeNegative() { + string str1 = string `[ + { + "p1":"v1", + "p2":1 + }, + { + "p1":"v2", + "p2":true + } + ]`; + T1|error t1 = fromJsonStringWithType(str1); + test:assertTrue(t1 is error); + test:assertEquals((t1).message(), "unsupported type '(map|int|boolean)'"); + + string str2 = string ` + { + "p1":"v1", + "p2": { + "a": 1, + "b": 2 + } + }`; + T2|error t2 = fromJsonStringWithType(str2); + test:assertTrue(t2 is error); + test:assertEquals((t2).message(), "unsupported type '(map|int)'"); } diff --git a/ballerina/tests/from_json_test.bal b/ballerina/tests/from_json_test.bal index b54bfb0..53b4e92 100644 --- a/ballerina/tests/from_json_test.bal +++ b/ballerina/tests/from_json_test.bal @@ -16,46 +16,42 @@ import ballerina/test; -// Possitive tests for fromJsonWithType() function. - -@test:Config -isolated function testJsonToBasicTypes() returns error? { - int val1 = check fromJsonWithType(5); - test:assertEquals(val1, 5); - - float val2 = check fromJsonWithType(5.5); - test:assertEquals(val2, 5.5); - - decimal val3 = check fromJsonWithType(5.5); - test:assertEquals(val3, 5.5d); - - string val4 = check fromJsonWithType("hello"); - test:assertEquals(val4, "hello"); - - boolean val5 = check fromJsonWithType(true); - test:assertEquals(val5, true); - - () val6 = check fromJsonWithType(null); - test:assertEquals(val6, null); - - decimal dVal = 1.5; - decimal val7 = check fromJsonWithType(dVal); - test:assertEquals(val7, 1.5d); +@test:Config { + dataProvider: dataProviderForBasicTypeForFromJsonWithType +} +isolated function testJsonToBasicTypes(json sourceData, typedesc expType, anydata expResult) returns Error? { + anydata result = check fromJsonWithType(sourceData, {}, expType); + test:assertEquals(result, expResult); +} - string val8 = check fromJsonWithType(""); - test:assertEquals(val8, ""); +function dataProviderForBasicTypeForFromJsonWithType() returns [json, typedesc, anydata][] { + return [ + [5, int, 5], + [5.5, float, 5.5], + [5.5, decimal, 5.5d], + ["hello", string, "hello"], + [true, boolean, true], + [1.5, decimal, 1.5d], + ["", string, ""], + [1.5, decimal, 1.5d], + [1.5, float, 1.5f], + [1.5, decimal, 1.5d], + [1.5, float, 1.5f], + [1.5, int, 2] + ]; +} - float f = 1.5; - decimal val9 = check fromJsonWithType(f); - test:assertEquals(val9, 1.5d); +@test:Config +isolated function testNilAsExpectedTypeWithFromJsonWithType() returns error? { + () val1 = check fromJsonWithType(null); + test:assertEquals(val1, ()); - decimal d = 1.5; - float val10 = check fromJsonWithType(d); - test:assertEquals(val10, 1.5f); + () val2 = check fromJsonWithType(()); + test:assertEquals(val2, ()); } @test:Config -isolated function testSimpleJsonToRecord() returns error? { +isolated function testSimpleJsonToRecord() returns Error? { json j = {"a": "hello", "b": 1}; SimpleRec1 recA = check fromJsonWithType(j); @@ -72,7 +68,7 @@ isolated function testSimpleJsonToRecord() returns error? { } @test:Config -isolated function testSimpleJsonToRecordWithProjection() returns error? { +isolated function testSimpleJsonToRecordWithProjection() returns Error? { json j = {"a": "hello", "b": 1}; record {|string a;|} recA = check fromJsonWithType(j); @@ -81,7 +77,7 @@ isolated function testSimpleJsonToRecordWithProjection() returns error? { } @test:Config -isolated function testNestedJsonToRecord() returns error? { +isolated function testNestedJsonToRecord() returns Error? { json j = { "a": "hello", "b": 1, @@ -110,7 +106,7 @@ isolated function testNestedJsonToRecord() returns error? { } @test:Config -isolated function testNestedJsonToRecordWithProjection() returns error? { +isolated function testNestedJsonToRecordWithProjection() returns Error? { json j = { "a": "hello", "b": 1, @@ -127,7 +123,7 @@ isolated function testNestedJsonToRecordWithProjection() returns error? { } @test:Config -isolated function testJsonToRecordWithOptionalFields() returns error? { +isolated function testJsonToRecordWithOptionalFields() returns Error? { json j = {"a": "hello"}; record {|string a; int b?;|} recA = check fromJsonWithType(j); @@ -136,7 +132,7 @@ isolated function testJsonToRecordWithOptionalFields() returns error? { } @test:Config -isolated function testJsonToRecordWithOptionalFieldsWithProjection() returns error? { +isolated function testJsonToRecordWithOptionalFieldsWithProjection() returns Error? { json j = { "a": "hello", "b": 1, @@ -153,7 +149,7 @@ isolated function testJsonToRecordWithOptionalFieldsWithProjection() returns err } @test:Config -isolated function testFromJsonWithType1() returns error? { +isolated function testFromJsonWithType1() returns Error? { json jsonContent = { "id": 2, "name": "Anne", @@ -171,7 +167,7 @@ isolated function testFromJsonWithType1() returns error? { } @test:Config -isolated function testMapTypeAsFieldTypeInRecord() returns error? { +isolated function testMapTypeAsFieldTypeInRecord() returns Error? { json jsonContent = { "employees": { "John": "Manager", @@ -185,7 +181,7 @@ isolated function testMapTypeAsFieldTypeInRecord() returns error? { } @test:Config -isolated function testFromJsonWithType2() returns error? { +isolated function testFromJsonWithType2() returns Error? { json jsonContent = { "name": "John", "age": 30, @@ -209,7 +205,7 @@ isolated function testFromJsonWithType2() returns error? { } @test:Config -isolated function testFromJsonWithType3() returns error? { +isolated function testFromJsonWithType3() returns Error? { json jsonContent = { "title": "To Kill a Mockingbird", "author": { @@ -223,7 +219,7 @@ isolated function testFromJsonWithType3() returns error? { "name": "J. B. Lippincott & Co.", "year": 1960, "location": "Philadelphia", - "month": 4 + "month": "April" } }; @@ -234,13 +230,14 @@ isolated function testFromJsonWithType3() returns error? { test:assertEquals(x.author.hometown, "Monroeville, Alabama"); test:assertEquals(x.publisher.name, "J. B. Lippincott & Co."); test:assertEquals(x.publisher.year, 1960); + test:assertEquals(x.publisher["month"], "April"); test:assertEquals(x.publisher["location"], "Philadelphia"); test:assertEquals(x["price"], 10.5); test:assertEquals(x.author["local"], false); } @test:Config -isolated function testFromJsonWithType4() returns error? { +isolated function testFromJsonWithType4() returns Error? { json jsonContent = { "name": "School Twelve", "city": 23, @@ -259,7 +256,7 @@ isolated function testFromJsonWithType4() returns error? { } @test:Config -isolated function testFromJsonWithType5() returns error? { +isolated function testFromJsonWithType5() returns Error? { json jsonContent = { "intValue": 10, "floatValue": 10.5, @@ -277,7 +274,7 @@ isolated function testFromJsonWithType5() returns error? { } @test:Config -isolated function testFromJsonWithType6() returns error? { +isolated function testFromJsonWithType6() returns Error? { json jsonContent = { "id": 1, "name": "Class A", @@ -313,7 +310,7 @@ isolated function testFromJsonWithType6() returns error? { } @test:Config -isolated function testFromJsonWithType7() returns error? { +isolated function testFromJsonWithType7() returns Error? { json nestedJson = { "intValue": 5, "floatValue": 2.5, @@ -332,7 +329,7 @@ isolated function testFromJsonWithType7() returns error? { } @test:Config -isolated function testFromJsonWithType8() returns error? { +isolated function testFromJsonWithType8() returns Error? { json jsonContent = { "street": "Main", "city": "Mahar", @@ -345,7 +342,7 @@ isolated function testFromJsonWithType8() returns error? { } @test:Config -isolated function testFromJsonWithType9() returns error? { +isolated function testFromJsonWithType9() returns Error? { json jsonContent = { "street": "Main", "city": "Mahar", @@ -359,7 +356,7 @@ isolated function testFromJsonWithType9() returns error? { } @test:Config -isolated function testFromJsonWithType10() returns error? { +isolated function testFromJsonWithType10() returns Error? { json jsonContent = { "street": "Main", "city": 11, @@ -373,7 +370,7 @@ isolated function testFromJsonWithType10() returns error? { } @test:Config -isolated function testFromJsonWithType11() returns error? { +isolated function testFromJsonWithType11() returns Error? { json jsonContent = { "street": "Main", "city": "Mahar", @@ -387,7 +384,7 @@ isolated function testFromJsonWithType11() returns error? { } @test:Config -isolated function testFromJsonWithType12() returns error? { +isolated function testFromJsonWithType12() returns Error? { json jsonContent = { "street": "Main", "city": { @@ -403,7 +400,7 @@ isolated function testFromJsonWithType12() returns error? { } @test:Config -isolated function testFromJsonWithType14() returns error? { +isolated function testFromJsonWithType14() { json jsonContent = { "id": 12, "name": "Anne", @@ -414,12 +411,12 @@ isolated function testFromJsonWithType14() returns error? { }; RN|Error x = fromJsonWithType(jsonContent); - test:assertTrue(x is error); - test:assertEquals((x).message(), "required field 'street' not present in JSON"); + test:assertTrue(x is Error); + test:assertEquals((x).message(), "required field 'street' not present in JSON"); } @test:Config -isolated function testFromJsonWithType15() returns error? { +isolated function testFromJsonWithType15() returns Error? { json jsonContent = [1, 2, 3]; IntArr x = check fromJsonWithType(jsonContent); @@ -427,15 +424,15 @@ isolated function testFromJsonWithType15() returns error? { } @test:Config -isolated function testFromJsonWithType16() returns error? { +isolated function testFromJsonWithType16() returns Error? { json jsonContent = [1, "abc", [3, 4.0]]; - TUPLE|Error x = check fromJsonWithType(jsonContent); + Tuple|Error x = check fromJsonWithType(jsonContent); test:assertEquals(x, [1, "abc", [3, 4.0]]); } @test:Config -isolated function testFromJsonWithType17() returns error? { +isolated function testFromJsonWithType17() returns Error? { json jsonContent = { "street": "Main", "city": { @@ -455,7 +452,7 @@ isolated function testFromJsonWithType17() returns error? { } @test:Config -isolated function testFromJsonWithType18() returns error? { +isolated function testFromJsonWithType18() returns Error? { json jsonContent = { "books": [ { @@ -482,7 +479,7 @@ isolated function testFromJsonWithType18() returns error? { } @test:Config -isolated function testFromJsonWithType19() returns error? { +isolated function testFromJsonWithType19() returns Error? { json jsonContent = { "books": [ { @@ -518,8 +515,7 @@ isolated function testFromJsonWithType19() returns error? { } @test:Config -isolated function testFromJsonWithType20() returns error? { - // TODO: Fix these bugs and enable the tests. +isolated function testFromJsonWithType20() returns Error? { json jsonVal1 = { "a": { "c": "world", @@ -581,7 +577,7 @@ isolated function testFromJsonWithType20() returns error? { } @test:Config -isolated function testUnionTypeAsExpTypeForFromJsonWithType() returns error? { +isolated function testUnionTypeAsExpTypeForFromJsonWithType() returns Error? { decimal|float val1 = check fromJsonWithType(1.0); test:assertEquals(val1, 1.0d); @@ -618,7 +614,7 @@ isolated function testUnionTypeAsExpTypeForFromJsonWithType() returns error? { } @test:Config -isolated function testAnydataAsExpTypeForFromJsonWithType() returns error? { +isolated function testAnydataAsExpTypeForFromJsonWithType() returns Error? { anydata val1 = check fromJsonWithType(1); test:assertEquals(val1, 1); @@ -678,7 +674,7 @@ isolated function testAnydataAsExpTypeForFromJsonWithType() returns error? { } @test:Config -isolated function testJsonAsExpTypeForFromJsonWithType() returns error? { +isolated function testJsonAsExpTypeForFromJsonWithType() returns Error? { json val1 = check fromJsonWithType(1); test:assertEquals(val1, 1); @@ -741,7 +737,7 @@ isolated function testJsonAsExpTypeForFromJsonWithType() returns error? { } @test:Config -isolated function testMapAsExpTypeForFromJsonWithType() returns error? { +isolated function testMapAsExpTypeForFromJsonWithType() returns Error? { record {| string a; string b; @@ -794,10 +790,30 @@ isolated function testMapAsExpTypeForFromJsonWithType() returns error? { string d; |}> val5 = check fromJsonWithType(jsonVal3); test:assertEquals(val5, {"a": {"c": "world", "d": "2"}, "b": {"c": "war", "d": "3"}}); + + json jsonVal6 = { + a: "Kanth", + b: { + g: { + c: "hello", + d: "1" + }, + h: { + c: "world", + d: "2" + } + } + }; + record {| + string a; + map> b; + |} val6 = check fromJsonWithType(jsonVal6); + test:assertEquals(val6.a, "Kanth"); + test:assertEquals(val6.b, {g: {c: "hello", d: "1"}, h: {c: "world", d: "2"}}); } @test:Config -isolated function testProjectionInTupleForFromJsonWithType() returns error? { +isolated function testProjectionInTupleForFromJsonWithType() returns Error? { float[] jsonVal1 = [1, 2, 3, 4, 5, 8]; [float, float] val1 = check fromJsonWithType(jsonVal1); test:assertEquals(val1, [1.0, 2.0]); @@ -824,7 +840,7 @@ isolated function testProjectionInTupleForFromJsonWithType() returns error? { } @test:Config -isolated function testProjectionInArrayForFromJsonWithType() returns error? { +isolated function testProjectionInArrayForFromJsonWithType() returns Error? { int[2] val1 = check fromJsonWithType([1, 2, 3, 4, 5]); test:assertEquals(val1, [1, 2]); @@ -862,7 +878,7 @@ isolated function testProjectionInArrayForFromJsonWithType() returns error? { } @test:Config -isolated function testProjectionInRecordForFromJsonWithType() returns error? { +isolated function testProjectionInRecordForFromJsonWithType() returns Error? { json jsonVal1 = {"name": "John", "age": 30, "city": "New York"}; record {| string name; string city; |} val1 = check fromJsonWithType(jsonVal1); test:assertEquals(val1, {name: "John", city: "New York"}); @@ -921,7 +937,7 @@ isolated function testProjectionInRecordForFromJsonWithType() returns error? { } @test:Config -isolated function testArrayOrTupleCaseForFromJsonWithType() returns error? { +isolated function testArrayOrTupleCaseForFromJsonWithType() returns Error? { json jsonVal1 = [[1], 2.0]; [[int], float] val1 = check fromJsonWithType(jsonVal1); test:assertEquals(val1, [[1], 2.0]); @@ -959,7 +975,7 @@ isolated function testArrayOrTupleCaseForFromJsonWithType() returns error? { } @test:Config -isolated function testListFillerValuesWithFromJsonWithType() returns error? { +isolated function testListFillerValuesWithFromJsonWithType() returns Error? { int[2] jsonVal1 = check fromJsonWithType([1]); test:assertEquals(jsonVal1, [1, 0]); @@ -974,7 +990,7 @@ isolated function testListFillerValuesWithFromJsonWithType() returns error? { } @test:Config -isolated function testNameAnnotationWithFromJsonWithType() returns error? { +isolated function testNameAnnotationWithFromJsonWithType() returns Error? { json jsonContent = { "id": 1, "title-name": "Harry Potter", @@ -987,56 +1003,36 @@ isolated function testNameAnnotationWithFromJsonWithType() returns error? { test:assertEquals(book.author, "J.K. Rowling"); } -@test:Config -isolated function testSubTypeOfIntAsExpectedTypeWithFromJsonWithType() returns error? { - byte val1 = check fromJsonWithType(255); - test:assertEquals(val1, 255); - - int:Unsigned8 val2 = check fromJsonWithType(255); - test:assertEquals(val2, 255); - - int jsonVal2 = 0; - byte val3 = check fromJsonWithType(jsonVal2); - test:assertEquals(val3, 0); - - int:Unsigned8 val4 = check fromJsonWithType(jsonVal2); - test:assertEquals(val4, 0); - - int:Signed8 val5 = check fromJsonWithType(127); - test:assertEquals(val5, 127); - - int:Signed8 val6 = check fromJsonWithType(-128); - test:assertEquals(val6, -128); - - int:Unsigned16 val7 = check fromJsonWithType(65535); - test:assertEquals(val7, 65535); - - int:Unsigned16 val8 = check fromJsonWithType(0); - test:assertEquals(val8, 0); - - int:Signed16 val9 = check fromJsonWithType(32767); - test:assertEquals(val9, 32767); - - int:Signed16 val10 = check fromJsonWithType(-32768); - test:assertEquals(val10, -32768); - - int:Unsigned32 val11 = check fromJsonWithType(4294967295); - test:assertEquals(val11, 4294967295); - - int:Unsigned32 val12 = check fromJsonWithType(0); - test:assertEquals(val12, 0); - - int:Signed32 val13 = check fromJsonWithType(2147483647); - test:assertEquals(val13, 2147483647); +@test:Config { + dataProvider: dataProviderForSubTypeIntPostiveCasesWithFromJsonWithType +} +isolated function testSubTypeOfIntAsExpectedTypeWithFromJsonWithType(json sourceData, typedesc expType, anydata expectedResult) returns Error? { + anydata val = check fromJsonWithType(sourceData, {}, expType); + test:assertEquals(val, expectedResult); +} - int:Signed32 val14 = check fromJsonWithType(-2147483648); - test:assertEquals(val14, -2147483648); - - int[] jsonVal3 = [255, 127, 32767, 2147483647, 255, 32767, 2147483647]; - [byte, int:Signed8, int:Signed16, int:Signed32, int:Unsigned8, int:Unsigned16, int:Unsigned32] val15 = - check fromJsonWithType(jsonVal3); - test:assertEquals(val15, [255, 127, 32767, 2147483647, 255, 32767, 2147483647]); +function dataProviderForSubTypeIntPostiveCasesWithFromJsonWithType() returns [json, typedesc, anydata][] { + return [ + [255, byte, 255], + [255, int:Unsigned8, 255], + [0, byte, 0], + [0, int:Unsigned8, 0], + [127, int:Signed8, 127], + [-128, int:Signed8, -128], + [65535, int:Unsigned16, 65535], + [0, int:Unsigned16, 0], + [32767, int:Signed16, 32767], + [-32768, int:Signed16, -32768], + [4294967295, int:Unsigned32, 4294967295], + [0, int:Unsigned32, 0], + [2147483647, int:Signed32, 2147483647], + [-2147483648, int:Signed32, -2147483648], + [[255, 127, 32767, 2147483647, 255, 32767, 2147483647], [byte, int:Signed8, int:Signed16, int:Signed32, int:Unsigned8, int:Unsigned16, int:Unsigned32], [255, 127, 32767, 2147483647, 255, 32767, 2147483647]] + ]; +} +@test:Config +isolated function testSubTypeOfIntAsFieldTypeForFromJsonWithType() returns error? { json jsonVal4 = { "a": 1, "b": 127, @@ -1059,7 +1055,7 @@ isolated function testSubTypeOfIntAsExpectedTypeWithFromJsonWithType() returns e } @test:Config -isolated function testSingletonAsExpectedTypeForFromJsonWithType() returns error? { +isolated function testSingletonAsExpectedTypeForFromJsonWithType() returns Error? { "1" val1 = check fromJsonWithType("1"); test:assertEquals(val1, "1"); @@ -1081,10 +1077,8 @@ isolated function testSingletonAsExpectedTypeForFromJsonWithType() returns error test:assertEquals(val5.value, 1); } -// Negative tests for fromJsonWithType() function. - @test:Config -isolated function testFromJsonWithTypeNegative1() returns error? { +isolated function testFromJsonWithTypeNegative1() returns Error? { json jsonContent = { "id": 12, "name": "Anne", @@ -1096,23 +1090,23 @@ isolated function testFromJsonWithTypeNegative1() returns error? { }; RN|Error x = fromJsonWithType(jsonContent); - test:assertTrue(x is error); - test:assertEquals((x).message(), "incompatible value 'true' for type 'int' in field 'address.id'"); + test:assertTrue(x is Error); + test:assertEquals((x).message(), "incompatible value 'true' for type 'int' in field 'address.id'"); } @test:Config -isolated function testFromJsonWithTypeNegative2() returns error? { +isolated function testFromJsonWithTypeNegative2() returns Error? { json jsonContent = { "id": 12 }; RN2|Error x = fromJsonWithType(jsonContent); - test:assertTrue(x is error); - test:assertEquals((x).message(), "required field 'name' not present in JSON"); + test:assertTrue(x is Error); + test:assertEquals((x).message(), "required field 'name' not present in JSON"); } @test:Config -isolated function testFromJsonWithTypeNegative3() returns error? { +isolated function testFromJsonWithTypeNegative3() returns Error? { json jsonContent = { "id": 12, "name": "Anne", @@ -1128,30 +1122,30 @@ isolated function testFromJsonWithTypeNegative3() returns error? { } @test:Config -isolated function testFromJsonWithTypeNegative4() returns error? { +isolated function testFromJsonWithTypeNegative4() returns Error? { json jsonContent = { name: "John" }; int|Error x = fromJsonWithType(jsonContent); - test:assertTrue(x is error); - test:assertEquals((x).message(), "incompatible expected type 'int' for value '{\"name\":\"John\"}'"); + test:assertTrue(x is Error); + test:assertEquals((x).message(), "incompatible expected type 'int' for value '{\"name\":\"John\"}'"); Union|Error y = fromJsonWithType(jsonContent); - test:assertTrue(y is error); - test:assertEquals((y).message(), "invalid type 'data.jsondata:Union' expected 'anydata'"); + test:assertTrue(y is Error); + test:assertEquals((y).message(), "invalid type 'data.jsondata:Union' expected 'anydata'"); table|Error z = fromJsonWithType(jsonContent); - test:assertTrue(z is error); - test:assertEquals((z).message(), "invalid type 'table' expected 'anydata'"); + test:assertTrue(z is Error); + test:assertEquals((z).message(), "invalid type 'table' expected 'anydata'"); RN2|Error a = fromJsonWithType("1"); - test:assertTrue(a is error); - test:assertEquals((a).message(), "incompatible expected type 'data.jsondata:RN2' for value '1'"); + test:assertTrue(a is Error); + test:assertEquals((a).message(), "incompatible expected type 'data.jsondata:RN2' for value '1'"); string|Error b = fromJsonWithType(1); - test:assertTrue(b is error); - test:assertEquals((b).message(), "incompatible expected type 'string' for value '1'"); + test:assertTrue(b is Error); + test:assertEquals((b).message(), "incompatible expected type 'string' for value '1'"); } @test:Config @@ -1168,80 +1162,67 @@ isolated function testFromJsonWithTypeNegative6() { } @test:Config -isolated function testDuplicateFieldInRecordTypeWithFromJsonWithType() returns error? { +isolated function testDuplicateFieldInRecordTypeWithFromJsonWithType() returns Error? { json jsonContent = string `{ "title": "Clean Code", "author": "Robert C. Martin", `; BookN|Error x = fromJsonWithType(jsonContent); - test:assertTrue(x is error); - test:assertEquals((x).message(), "duplicate field 'author'"); + test:assertTrue(x is Error); + test:assertEquals((x).message(), "duplicate field 'author'"); } @test:Config isolated function testProjectionInArrayNegativeForFromJsonWithType() { [int, int, int, record {|int a;|}] jsonVal5 = [1, 2, 3, { a : 2 }]; - int[]|error val5 = fromJsonWithType(jsonVal5); - test:assertTrue(val5 is error); - test:assertEquals((val5).message(), "incompatible expected type 'int' for value '{\"a\":2}'"); + int[]|Error val5 = fromJsonWithType(jsonVal5); + test:assertTrue(val5 is Error); + test:assertEquals((val5).message(), "incompatible expected type 'int' for value '{\"a\":2}'"); +} + +@test:Config { + dataProvider: dataProviderForSubTypeOfIntNegativeTestForFromJsonWithType +} +isolated function testSubTypeOfIntAsExptypeWithFromJsonWithTypeNegative(json sourceData, typedesc expType, string expectedError) { + anydata|Error result = fromJsonWithType(sourceData, {}, expType); + test:assertTrue(result is Error); + test:assertEquals((result).message(), expectedError); +} + +function dataProviderForSubTypeOfIntNegativeTestForFromJsonWithType() returns [json, typedesc, string][] { + string incompatibleStr = "incompatible expected type "; + return [ + [256, byte, incompatibleStr + "'byte' for value '256'"], + [-1, byte, incompatibleStr + "'byte' for value '-1'"], + [128, int:Signed8, incompatibleStr + "'lang.int:Signed8' for value '128'"], + [-129, int:Signed8, incompatibleStr + "'lang.int:Signed8' for value '-129'"], + [256, int:Unsigned8, incompatibleStr + "'lang.int:Unsigned8' for value '256'"], + [-1, int:Unsigned8, incompatibleStr + "'lang.int:Unsigned8' for value '-1'"], + [32768, int:Signed16, incompatibleStr + "'lang.int:Signed16' for value '32768'"], + [-32769, int:Signed16, incompatibleStr + "'lang.int:Signed16' for value '-32769'"], + [65536, int:Unsigned16, incompatibleStr + "'lang.int:Unsigned16' for value '65536'"], + [-1, int:Unsigned16, incompatibleStr + "'lang.int:Unsigned16' for value '-1'"], + [2147483648, int:Signed32, incompatibleStr + "'lang.int:Signed32' for value '2147483648'"], + [-2147483649, int:Signed32, incompatibleStr + "'lang.int:Signed32' for value '-2147483649'"], + [4294967296, int:Unsigned32, incompatibleStr + "'lang.int:Unsigned32' for value '4294967296'"], + [-1, int:Unsigned32, incompatibleStr + "'lang.int:Unsigned32' for value '-1'"] + ]; } @test:Config -isolated function testSubTypeOfIntAsExptypeWithFromJsonWithTypeNegative() { - byte|error err1 = fromJsonWithType(256); - test:assertTrue(err1 is error); - test:assertEquals(( err1).message(), "incompatible expected type 'byte' for value '256'"); - - byte|error err2 = fromJsonWithType(-1); - test:assertTrue(err2 is error); - test:assertEquals(( err2).message(), "incompatible expected type 'byte' for value '-1'"); - - int:Signed8|error err3 = fromJsonWithType(128); - test:assertTrue(err3 is error); - test:assertEquals(( err3).message(), "incompatible expected type 'lang.int:Signed8' for value '128'"); - - int:Signed8|error err4 = fromJsonWithType(-129); - test:assertTrue(err4 is error); - test:assertEquals(( err4).message(), "incompatible expected type 'lang.int:Signed8' for value '-129'"); - - int:Unsigned8|error err5 = fromJsonWithType(256); - test:assertTrue(err5 is error); - test:assertEquals(( err5).message(), "incompatible expected type 'lang.int:Unsigned8' for value '256'"); - - int:Unsigned8|error err6 = fromJsonWithType(-1); - test:assertTrue(err6 is error); - test:assertEquals(( err6).message(), "incompatible expected type 'lang.int:Unsigned8' for value '-1'"); - - int:Signed16|error err7 = fromJsonWithType(32768); - test:assertTrue(err7 is error); - test:assertEquals(( err7).message(), "incompatible expected type 'lang.int:Signed16' for value '32768'"); - - int:Signed16|error err8 = fromJsonWithType(-32769); - test:assertTrue(err8 is error); - test:assertEquals(( err8).message(), "incompatible expected type 'lang.int:Signed16' for value '-32769'"); - - int:Unsigned16|error err9 = fromJsonWithType(65536); - test:assertTrue(err9 is error); - test:assertEquals(( err9).message(), "incompatible expected type 'lang.int:Unsigned16' for value '65536'"); - - int:Unsigned16|error err10 = fromJsonWithType(-1); - test:assertTrue(err10 is error); - test:assertEquals(( err10).message(), "incompatible expected type 'lang.int:Unsigned16' for value '-1'"); - - int:Signed32|error err11 = fromJsonWithType(2147483648); - test:assertTrue(err11 is error); - test:assertEquals(( err11).message(), "incompatible expected type 'lang.int:Signed32' for value '2147483648'"); - - int:Signed32|error err12 = fromJsonWithType(-2147483649); - test:assertTrue(err12 is error); - test:assertEquals(( err12).message(), "incompatible expected type 'lang.int:Signed32' for value '-2147483649'"); - - int:Unsigned32|error err13 = fromJsonWithType(4294967296); - test:assertTrue(err13 is error); - test:assertEquals(( err13).message(), "incompatible expected type 'lang.int:Unsigned32' for value '4294967296'"); - - int:Unsigned32|error err14 = fromJsonWithType(-1); - test:assertTrue(err14 is error); - test:assertEquals(( err14).message(), "incompatible expected type 'lang.int:Unsigned32' for value '-1'"); +isolated function testRecordWithRestAsExpectedTypeForFromJsonWithTypeNegative() { + json jsonVal = { + id: 1, + name: "Anne", + measurements: { + height: 5.5, + weight: 60, + shoeSize: "7" + } + }; + + PersonA|error val = fromJsonWithType(jsonVal); + test:assertTrue(val is error); + test:assertEquals((val).message(), "incompatible value '7' for type 'int' in field 'measurements'"); } diff --git a/ballerina/tests/types.bal b/ballerina/tests/types.bal index 9c6992b..22bae49 100644 --- a/ballerina/tests/types.bal +++ b/ballerina/tests/types.bal @@ -213,7 +213,7 @@ type TestJson record { type IntArr int[]; -type TUPLE [int, string, [int, float]]; +type Tuple [int, string, [int, float]]; type BookA record {| string title; @@ -233,6 +233,17 @@ type SingletonInRecord record {| SingletonUnion id; |}; +type PersonA record {| + string name; + record {|int...;|} measurements; +|}; + +type T1 (map|int|boolean)[]; +type T2 record {| + string p1; + map|int p2; +|}; + //////// Types used for Negative cases ///////// type AddressN record { diff --git a/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTest.java b/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTest.java index acd1789..cf70ad5 100644 --- a/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTest.java +++ b/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTest.java @@ -106,4 +106,18 @@ public void testDuplicateField2() { Assert.assertEquals(errorDiagnosticsList.get(1).diagnosticInfo().messageFormat(), "invalid field: duplicate field found"); } + + @Test + public void testComplexUnionTypeAsExpectedType() { + DiagnosticResult diagnosticResult = + CompilerPluginTestUtils.loadPackage("sample_package_7").getCompilation().diagnosticResult(); + List errorDiagnosticsList = diagnosticResult.diagnostics().stream() + .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) + .collect(Collectors.toList()); + Assert.assertEquals(errorDiagnosticsList.size(), 2); + Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), + "unsupported union type: union type does not support multiple complex types"); + Assert.assertEquals(errorDiagnosticsList.get(1).diagnosticInfo().messageFormat(), + "unsupported union type: union type does not support multiple complex types"); + } } diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/Ballerina.toml b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/Ballerina.toml new file mode 100644 index 0000000..203de45 --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "jsondata_test" +name = "sample_7" +version = "0.1.0" diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/sample.bal new file mode 100644 index 0000000..4088a26 --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/sample.bal @@ -0,0 +1,31 @@ +import ballerina/data.jsondata; + +type T1 (map|int|boolean)[]; +type T2 record {| + string p1; + map|int p2; +|}; + +public function main() returns error? { + string str1 = string `[ + { + "p1":"v1", + "p2":1 + }, + { + "p1":"v2", + "p2":true + } + ]`; + T1 _ = check jsondata:fromJsonStringWithType(str1); + + string str2 = string ` + { + "p1":"v1", + "p2": { + "a": 1, + "b": 2 + } + }`; + T2 _ = check jsondata:fromJsonStringWithType(str2); +} diff --git a/compiler-plugin-test/src/test/resources/testng.xml b/compiler-plugin-test/src/test/resources/testng.xml index ea625e7..5ffcaef 100644 --- a/compiler-plugin-test/src/test/resources/testng.xml +++ b/compiler-plugin-test/src/test/resources/testng.xml @@ -18,7 +18,7 @@ - + diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java index 276c91c..124de16 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java @@ -25,6 +25,6 @@ */ public class Constants { static final String FROM_JSON_STRING_WITH_TYPE = "fromJsonStringWithType"; - public static final String NAME = "Name"; - public static final String JSONDATA = "jsondata"; + static final String NAME = "Name"; + static final String JSONDATA = "jsondata"; } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java index fa5767f..73c4674 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java @@ -183,7 +183,7 @@ private void validateUnionType(UnionTypeSymbol unionTypeSymbol, Optional 1) { + if (nonPrimitiveMemberCount >= 1) { reportDiagnosticInfo(ctx, location, JsondataDiagnosticCodes.UNSUPPORTED_UNION_TYPE); } } diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java index e2ef433..ae9ef5b 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java +++ b/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java @@ -34,7 +34,6 @@ import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.utils.ValueUtils; import io.ballerina.runtime.api.values.BArray; -import io.ballerina.runtime.api.values.BDecimal; import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; @@ -260,9 +259,7 @@ private void addRestField(Type restFieldType, BString key, Object jsonMember, Ob ((BMap) currentJsonNode).put(key, jsonMember); case TypeTags.BOOLEAN_TAG, TypeTags.INT_TAG, TypeTags.FLOAT_TAG, TypeTags.DECIMAL_TAG, TypeTags.STRING_TAG -> { - if (checkTypeCompatibility(restFieldType, jsonMember)) { - ((BMap) currentJsonNode).put(key, jsonMember); - } + ((BMap) currentJsonNode).put(key, convertToBasicType(jsonMember, restFieldType)); } default -> { nextJsonValue = traverseJson(jsonMember, restFieldType); @@ -271,19 +268,6 @@ private void addRestField(Type restFieldType, BString key, Object jsonMember, Ob } } - private boolean checkTypeCompatibility(Type type, Object json) { - return ((json == null && type.getTag() == TypeTags.NULL_TAG) - || (json instanceof BString && type.getTag() == TypeTags.STRING_TAG) - || (isBallerinaInt(json) && type.getTag() == TypeTags.INT_TAG) - || (json instanceof Double && type.getTag() == TypeTags.FLOAT_TAG) - || ((json instanceof Double || json instanceof BDecimal) && type.getTag() == TypeTags.DECIMAL_TAG) - || (json instanceof Boolean && type.getTag() == TypeTags.BOOLEAN_TAG)); - } - - private boolean isBallerinaInt(Object val) { - return val instanceof Number && !(val instanceof Double); - } - private void checkOptionalFieldsAndLogError(Map currentField) { currentField.values().forEach(field -> { if (SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.REQUIRED)) { From 9e5b3f9c5395ada6f8efedb89418170db381c094 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Tue, 19 Mar 2024 19:17:02 +0530 Subject: [PATCH 26/27] Replace stdlib usage with lib --- ballerina/Ballerina.toml | 2 +- ballerina/CompilerPlugin.toml | 2 +- ballerina/init.bal | 2 +- ballerina/json_api.bal | 6 +++--- build-config/resources/Ballerina.toml | 2 +- build-config/resources/CompilerPlugin.toml | 2 +- .../data/jsondata/compiler/CompilerPluginTest.java | 2 +- .../jsondata/compiler/CompilerPluginTestUtils.java | 2 +- compiler-plugin-test/src/test/resources/testng.xml | 2 +- .../data/jsondata/compiler/Constants.java | 2 +- .../data/jsondata/compiler/JsondataCodeAnalyzer.java | 2 +- .../data/jsondata/compiler/JsondataCompilerPlugin.java | 2 +- .../jsondata/compiler/JsondataDiagnosticCodes.java | 2 +- .../data/jsondata/compiler/JsondataTypeValidator.java | 2 +- .../{stdlib => lib}/data/jsondata/FromString.java | 6 +++--- .../jsondata/io/BallerinaByteBlockInputStream.java | 4 ++-- .../data/jsondata/io/DataReaderTask.java | 6 +++--- .../data/jsondata/io/DataReaderThreadPool.java | 2 +- .../data/jsondata/json/JsonCreator.java | 10 +++++----- .../{stdlib => lib}/data/jsondata/json/JsonParser.java | 8 ++++---- .../data/jsondata/json/JsonTraverse.java | 8 ++++---- .../{stdlib => lib}/data/jsondata/json/Native.java | 10 +++++----- .../{stdlib => lib}/data/jsondata/utils/Constants.java | 2 +- .../data/jsondata/utils/DiagnosticErrorCode.java | 2 +- .../data/jsondata/utils/DiagnosticLog.java | 2 +- .../data/jsondata/utils/ModuleUtils.java | 2 +- native/src/main/java/module-info.java | 2 +- 27 files changed, 48 insertions(+), 48 deletions(-) rename compiler-plugin-test/src/test/java/io/ballerina/{stdlib => lib}/data/jsondata/compiler/CompilerPluginTest.java (99%) rename compiler-plugin-test/src/test/java/io/ballerina/{stdlib => lib}/data/jsondata/compiler/CompilerPluginTestUtils.java (97%) rename compiler-plugin/src/main/java/io/ballerina/{stdlib => lib}/data/jsondata/compiler/Constants.java (94%) rename compiler-plugin/src/main/java/io/ballerina/{stdlib => lib}/data/jsondata/compiler/JsondataCodeAnalyzer.java (95%) rename compiler-plugin/src/main/java/io/ballerina/{stdlib => lib}/data/jsondata/compiler/JsondataCompilerPlugin.java (95%) rename compiler-plugin/src/main/java/io/ballerina/{stdlib => lib}/data/jsondata/compiler/JsondataDiagnosticCodes.java (96%) rename compiler-plugin/src/main/java/io/ballerina/{stdlib => lib}/data/jsondata/compiler/JsondataTypeValidator.java (99%) rename native/src/main/java/io/ballerina/{stdlib => lib}/data/jsondata/FromString.java (98%) rename native/src/main/java/io/ballerina/{stdlib => lib}/data/jsondata/io/BallerinaByteBlockInputStream.java (98%) rename native/src/main/java/io/ballerina/{stdlib => lib}/data/jsondata/io/DataReaderTask.java (95%) rename native/src/main/java/io/ballerina/{stdlib => lib}/data/jsondata/io/DataReaderThreadPool.java (97%) rename native/src/main/java/io/ballerina/{stdlib => lib}/data/jsondata/json/JsonCreator.java (97%) rename native/src/main/java/io/ballerina/{stdlib => lib}/data/jsondata/json/JsonParser.java (99%) rename native/src/main/java/io/ballerina/{stdlib => lib}/data/jsondata/json/JsonTraverse.java (98%) rename native/src/main/java/io/ballerina/{stdlib => lib}/data/jsondata/json/Native.java (91%) rename native/src/main/java/io/ballerina/{stdlib => lib}/data/jsondata/utils/Constants.java (96%) rename native/src/main/java/io/ballerina/{stdlib => lib}/data/jsondata/utils/DiagnosticErrorCode.java (97%) rename native/src/main/java/io/ballerina/{stdlib => lib}/data/jsondata/utils/DiagnosticLog.java (97%) rename native/src/main/java/io/ballerina/{stdlib => lib}/data/jsondata/utils/ModuleUtils.java (95%) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index c76ee48..78438f3 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -12,7 +12,7 @@ distribution = "2201.8.4" graalvmCompatible = true [[platform.java17.dependency]] -groupId = "io.ballerina.stdlib" +groupId = "io.ballerina.lib" artifactId = "jsondata-native" version = "0.1.0" path = "../native/build/libs/data.jsondata-native-0.1.0-SNAPSHOT.jar" diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml index 4daf114..847ec27 100644 --- a/ballerina/CompilerPlugin.toml +++ b/ballerina/CompilerPlugin.toml @@ -1,6 +1,6 @@ [plugin] id = "constraint-compiler-plugin" -class = "io.ballerina.stdlib.data.jsondata.compiler.JsondataCompilerPlugin" +class = "io.ballerina.lib.data.jsondata.compiler.JsondataCompilerPlugin" [[dependency]] path = "../compiler-plugin/build/libs/data.jsondata-compiler-plugin-0.1.0-SNAPSHOT.jar" diff --git a/ballerina/init.bal b/ballerina/init.bal index 5e3c133..5476d5e 100644 --- a/ballerina/init.bal +++ b/ballerina/init.bal @@ -21,5 +21,5 @@ isolated function init() { } isolated function setModule() = @java:Method { - 'class: "io.ballerina.stdlib.data.jsondata.utils.ModuleUtils" + 'class: "io.ballerina.lib.data.jsondata.utils.ModuleUtils" } external; diff --git a/ballerina/json_api.bal b/ballerina/json_api.bal index d5ae7ef..6246d79 100644 --- a/ballerina/json_api.bal +++ b/ballerina/json_api.bal @@ -23,7 +23,7 @@ import ballerina/jballerina.java; # + t - Target type # + return - On success, returns value belonging to the given target type, else returns an `jsondata:Error` value. public isolated function fromJsonWithType(json v, Options options = {}, typedesc t = <>) - returns t|Error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; + returns t|Error = @java:Method {'class: "io.ballerina.lib.data.jsondata.json.Native"} external; # Converts JSON string, byte[] or byte-block-stream to subtype of anydata. # @@ -32,14 +32,14 @@ public isolated function fromJsonWithType(json v, Options options = {}, typedesc # + t - Target type # + return - On success, value belonging to the given target type, else returns an `jsondata:Error` value. public isolated function fromJsonStringWithType(string|byte[]|stream s, Options options = {}, typedesc t = <>) - returns t|Error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; + returns t|Error = @java:Method {'class: "io.ballerina.lib.data.jsondata.json.Native"} external; # Converts a value of type `anydata` to `json`. # # + v - Source anydata value # + return - representation of `v` as value of type json public isolated function toJson(anydata v) - returns json|Error = @java:Method {'class: "io.ballerina.stdlib.data.jsondata.json.Native"} external; + returns json|Error = @java:Method {'class: "io.ballerina.lib.data.jsondata.json.Native"} external; # Represent the options that can be used to modify the behaviour of conversion in `fromJsonStringWithType` and `fromJsonWithType`. # diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index c214868..4877db5 100644 --- a/build-config/resources/Ballerina.toml +++ b/build-config/resources/Ballerina.toml @@ -12,7 +12,7 @@ distribution = "2201.8.4" graalvmCompatible = true [[platform.java17.dependency]] -groupId = "io.ballerina.stdlib" +groupId = "io.ballerina.lib" artifactId = "jsondata-native" version = "@toml.version@" path = "../native/build/libs/data.jsondata-native-@project.version@.jar" diff --git a/build-config/resources/CompilerPlugin.toml b/build-config/resources/CompilerPlugin.toml index 6eabf88..e2678ce 100644 --- a/build-config/resources/CompilerPlugin.toml +++ b/build-config/resources/CompilerPlugin.toml @@ -1,6 +1,6 @@ [plugin] id = "constraint-compiler-plugin" -class = "io.ballerina.stdlib.data.jsondata.compiler.JsondataCompilerPlugin" +class = "io.ballerina.lib.data.jsondata.compiler.JsondataCompilerPlugin" [[dependency]] path = "../compiler-plugin/build/libs/data.jsondata-compiler-plugin-@project.version@.jar" diff --git a/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTest.java b/compiler-plugin-test/src/test/java/io/ballerina/lib/data/jsondata/compiler/CompilerPluginTest.java similarity index 99% rename from compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTest.java rename to compiler-plugin-test/src/test/java/io/ballerina/lib/data/jsondata/compiler/CompilerPluginTest.java index cf70ad5..bf49165 100644 --- a/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTest.java +++ b/compiler-plugin-test/src/test/java/io/ballerina/lib/data/jsondata/compiler/CompilerPluginTest.java @@ -16,7 +16,7 @@ * under the License. */ -package io.ballerina.stdlib.data.jsondata.compiler; +package io.ballerina.lib.data.jsondata.compiler; import io.ballerina.projects.DiagnosticResult; import io.ballerina.tools.diagnostics.Diagnostic; diff --git a/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTestUtils.java b/compiler-plugin-test/src/test/java/io/ballerina/lib/data/jsondata/compiler/CompilerPluginTestUtils.java similarity index 97% rename from compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTestUtils.java rename to compiler-plugin-test/src/test/java/io/ballerina/lib/data/jsondata/compiler/CompilerPluginTestUtils.java index 59d2e55..e2609b7 100644 --- a/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/jsondata/compiler/CompilerPluginTestUtils.java +++ b/compiler-plugin-test/src/test/java/io/ballerina/lib/data/jsondata/compiler/CompilerPluginTestUtils.java @@ -16,7 +16,7 @@ * under the License. */ -package io.ballerina.stdlib.data.jsondata.compiler; +package io.ballerina.lib.data.jsondata.compiler; import io.ballerina.projects.Package; import io.ballerina.projects.ProjectEnvironmentBuilder; diff --git a/compiler-plugin-test/src/test/resources/testng.xml b/compiler-plugin-test/src/test/resources/testng.xml index 5ffcaef..402d393 100644 --- a/compiler-plugin-test/src/test/resources/testng.xml +++ b/compiler-plugin-test/src/test/resources/testng.xml @@ -21,7 +21,7 @@ - + diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java b/compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/Constants.java similarity index 94% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java rename to compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/Constants.java index 124de16..3103eb1 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/Constants.java +++ b/compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/Constants.java @@ -16,7 +16,7 @@ * under the License. */ -package io.ballerina.stdlib.data.jsondata.compiler; +package io.ballerina.lib.data.jsondata.compiler; /** * Constants for Jsondata's compiler plugin. diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCodeAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/JsondataCodeAnalyzer.java similarity index 95% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCodeAnalyzer.java rename to compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/JsondataCodeAnalyzer.java index 926d270..058f8ee 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCodeAnalyzer.java +++ b/compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/JsondataCodeAnalyzer.java @@ -16,7 +16,7 @@ * under the License. */ -package io.ballerina.stdlib.data.jsondata.compiler; +package io.ballerina.lib.data.jsondata.compiler; import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.projects.plugins.CodeAnalysisContext; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCompilerPlugin.java b/compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/JsondataCompilerPlugin.java similarity index 95% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCompilerPlugin.java rename to compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/JsondataCompilerPlugin.java index a1abf2f..870fffc 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataCompilerPlugin.java +++ b/compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/JsondataCompilerPlugin.java @@ -16,7 +16,7 @@ * under the License. */ -package io.ballerina.stdlib.data.jsondata.compiler; +package io.ballerina.lib.data.jsondata.compiler; import io.ballerina.projects.plugins.CompilerPlugin; import io.ballerina.projects.plugins.CompilerPluginContext; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataDiagnosticCodes.java b/compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/JsondataDiagnosticCodes.java similarity index 96% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataDiagnosticCodes.java rename to compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/JsondataDiagnosticCodes.java index 418f52a..017084f 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataDiagnosticCodes.java +++ b/compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/JsondataDiagnosticCodes.java @@ -16,7 +16,7 @@ * under the License. */ -package io.ballerina.stdlib.data.jsondata.compiler; +package io.ballerina.lib.data.jsondata.compiler; import io.ballerina.tools.diagnostics.DiagnosticSeverity; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java b/compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/JsondataTypeValidator.java similarity index 99% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java rename to compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/JsondataTypeValidator.java index 73c4674..f1c0e02 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/jsondata/compiler/JsondataTypeValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/JsondataTypeValidator.java @@ -16,7 +16,7 @@ * under the License. */ -package io.ballerina.stdlib.data.jsondata.compiler; +package io.ballerina.lib.data.jsondata.compiler; import io.ballerina.compiler.api.SemanticModel; import io.ballerina.compiler.api.symbols.AnnotationAttachmentSymbol; diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java b/native/src/main/java/io/ballerina/lib/data/jsondata/FromString.java similarity index 98% rename from native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java rename to native/src/main/java/io/ballerina/lib/data/jsondata/FromString.java index 45262ca..b146468 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/FromString.java +++ b/native/src/main/java/io/ballerina/lib/data/jsondata/FromString.java @@ -16,8 +16,10 @@ * under the License. */ -package io.ballerina.stdlib.data.jsondata; +package io.ballerina.lib.data.jsondata; +import io.ballerina.lib.data.jsondata.utils.DiagnosticErrorCode; +import io.ballerina.lib.data.jsondata.utils.DiagnosticLog; import io.ballerina.runtime.api.PredefinedTypes; import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.creators.TypeCreator; @@ -33,8 +35,6 @@ import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.api.values.BTypedesc; -import io.ballerina.stdlib.data.jsondata.utils.DiagnosticErrorCode; -import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; import java.util.ArrayList; import java.util.Comparator; diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/BallerinaByteBlockInputStream.java b/native/src/main/java/io/ballerina/lib/data/jsondata/io/BallerinaByteBlockInputStream.java similarity index 98% rename from native/src/main/java/io/ballerina/stdlib/data/jsondata/io/BallerinaByteBlockInputStream.java rename to native/src/main/java/io/ballerina/lib/data/jsondata/io/BallerinaByteBlockInputStream.java index f065610..72cbbde 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/BallerinaByteBlockInputStream.java +++ b/native/src/main/java/io/ballerina/lib/data/jsondata/io/BallerinaByteBlockInputStream.java @@ -16,8 +16,9 @@ * under the License. */ -package io.ballerina.stdlib.data.jsondata.io; +package io.ballerina.lib.data.jsondata.io; +import io.ballerina.lib.data.jsondata.utils.DiagnosticLog; import io.ballerina.runtime.api.Environment; import io.ballerina.runtime.api.async.Callback; import io.ballerina.runtime.api.async.StrandMetadata; @@ -28,7 +29,6 @@ import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BObject; import io.ballerina.runtime.api.values.BString; -import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; import java.io.IOException; import java.io.InputStream; diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderTask.java b/native/src/main/java/io/ballerina/lib/data/jsondata/io/DataReaderTask.java similarity index 95% rename from native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderTask.java rename to native/src/main/java/io/ballerina/lib/data/jsondata/io/DataReaderTask.java index 361d147..f36bff2 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderTask.java +++ b/native/src/main/java/io/ballerina/lib/data/jsondata/io/DataReaderTask.java @@ -15,8 +15,10 @@ * specific language governing permissions and limitations * under the License. */ -package io.ballerina.stdlib.data.jsondata.io; +package io.ballerina.lib.data.jsondata.io; +import io.ballerina.lib.data.jsondata.json.JsonParser; +import io.ballerina.lib.data.jsondata.utils.DiagnosticLog; import io.ballerina.runtime.api.Environment; import io.ballerina.runtime.api.Future; import io.ballerina.runtime.api.types.MethodType; @@ -26,8 +28,6 @@ import io.ballerina.runtime.api.values.BObject; import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.api.values.BTypedesc; -import io.ballerina.stdlib.data.jsondata.json.JsonParser; -import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; import java.io.InputStreamReader; import java.util.function.Consumer; diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderThreadPool.java b/native/src/main/java/io/ballerina/lib/data/jsondata/io/DataReaderThreadPool.java similarity index 97% rename from native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderThreadPool.java rename to native/src/main/java/io/ballerina/lib/data/jsondata/io/DataReaderThreadPool.java index ef470e6..631c3e9 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/io/DataReaderThreadPool.java +++ b/native/src/main/java/io/ballerina/lib/data/jsondata/io/DataReaderThreadPool.java @@ -15,7 +15,7 @@ * specific language governing permissions and limitations * under the License. */ -package io.ballerina.stdlib.data.jsondata.io; +package io.ballerina.lib.data.jsondata.io; import java.util.concurrent.ExecutorService; import java.util.concurrent.SynchronousQueue; diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java b/native/src/main/java/io/ballerina/lib/data/jsondata/json/JsonCreator.java similarity index 97% rename from native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java rename to native/src/main/java/io/ballerina/lib/data/jsondata/json/JsonCreator.java index 566b64a..ef2c677 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonCreator.java +++ b/native/src/main/java/io/ballerina/lib/data/jsondata/json/JsonCreator.java @@ -16,8 +16,12 @@ * under the License. */ -package io.ballerina.stdlib.data.jsondata.json; +package io.ballerina.lib.data.jsondata.json; +import io.ballerina.lib.data.jsondata.FromString; +import io.ballerina.lib.data.jsondata.utils.Constants; +import io.ballerina.lib.data.jsondata.utils.DiagnosticErrorCode; +import io.ballerina.lib.data.jsondata.utils.DiagnosticLog; import io.ballerina.runtime.api.PredefinedTypes; import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.creators.ValueCreator; @@ -34,10 +38,6 @@ import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; -import io.ballerina.stdlib.data.jsondata.FromString; -import io.ballerina.stdlib.data.jsondata.utils.Constants; -import io.ballerina.stdlib.data.jsondata.utils.DiagnosticErrorCode; -import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; import org.ballerinalang.langlib.value.CloneReadOnly; import java.util.HashMap; diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java b/native/src/main/java/io/ballerina/lib/data/jsondata/json/JsonParser.java similarity index 99% rename from native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java rename to native/src/main/java/io/ballerina/lib/data/jsondata/json/JsonParser.java index 3c56c5e..f4e946d 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonParser.java +++ b/native/src/main/java/io/ballerina/lib/data/jsondata/json/JsonParser.java @@ -16,8 +16,11 @@ * under the License. */ -package io.ballerina.stdlib.data.jsondata.json; +package io.ballerina.lib.data.jsondata.json; +import io.ballerina.lib.data.jsondata.utils.Constants; +import io.ballerina.lib.data.jsondata.utils.DiagnosticErrorCode; +import io.ballerina.lib.data.jsondata.utils.DiagnosticLog; import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.creators.ErrorCreator; import io.ballerina.runtime.api.flags.SymbolFlags; @@ -34,9 +37,6 @@ import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; -import io.ballerina.stdlib.data.jsondata.utils.Constants; -import io.ballerina.stdlib.data.jsondata.utils.DiagnosticErrorCode; -import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; import org.apache.commons.lang3.StringEscapeUtils; import org.ballerinalang.langlib.value.CloneReadOnly; diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java b/native/src/main/java/io/ballerina/lib/data/jsondata/json/JsonTraverse.java similarity index 98% rename from native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java rename to native/src/main/java/io/ballerina/lib/data/jsondata/json/JsonTraverse.java index ae9ef5b..6258c9a 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/JsonTraverse.java +++ b/native/src/main/java/io/ballerina/lib/data/jsondata/json/JsonTraverse.java @@ -16,8 +16,11 @@ * under the License. */ -package io.ballerina.stdlib.data.jsondata.json; +package io.ballerina.lib.data.jsondata.json; +import io.ballerina.lib.data.jsondata.utils.Constants; +import io.ballerina.lib.data.jsondata.utils.DiagnosticErrorCode; +import io.ballerina.lib.data.jsondata.utils.DiagnosticLog; import io.ballerina.runtime.api.PredefinedTypes; import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.creators.ValueCreator; @@ -37,9 +40,6 @@ import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; -import io.ballerina.stdlib.data.jsondata.utils.Constants; -import io.ballerina.stdlib.data.jsondata.utils.DiagnosticErrorCode; -import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; import java.util.ArrayDeque; import java.util.Deque; diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java b/native/src/main/java/io/ballerina/lib/data/jsondata/json/Native.java similarity index 91% rename from native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java rename to native/src/main/java/io/ballerina/lib/data/jsondata/json/Native.java index 774fea1..be79a6b 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/json/Native.java +++ b/native/src/main/java/io/ballerina/lib/data/jsondata/json/Native.java @@ -16,8 +16,12 @@ * under the License. */ -package io.ballerina.stdlib.data.jsondata.json; +package io.ballerina.lib.data.jsondata.json; +import io.ballerina.lib.data.jsondata.io.DataReaderTask; +import io.ballerina.lib.data.jsondata.io.DataReaderThreadPool; +import io.ballerina.lib.data.jsondata.utils.DiagnosticErrorCode; +import io.ballerina.lib.data.jsondata.utils.DiagnosticLog; import io.ballerina.runtime.api.Environment; import io.ballerina.runtime.api.Future; import io.ballerina.runtime.api.types.Type; @@ -29,10 +33,6 @@ import io.ballerina.runtime.api.values.BStream; import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.api.values.BTypedesc; -import io.ballerina.stdlib.data.jsondata.io.DataReaderTask; -import io.ballerina.stdlib.data.jsondata.io.DataReaderThreadPool; -import io.ballerina.stdlib.data.jsondata.utils.DiagnosticErrorCode; -import io.ballerina.stdlib.data.jsondata.utils.DiagnosticLog; import java.io.ByteArrayInputStream; import java.io.InputStreamReader; diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/Constants.java b/native/src/main/java/io/ballerina/lib/data/jsondata/utils/Constants.java similarity index 96% rename from native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/Constants.java rename to native/src/main/java/io/ballerina/lib/data/jsondata/utils/Constants.java index e3b95c3..a319049 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/Constants.java +++ b/native/src/main/java/io/ballerina/lib/data/jsondata/utils/Constants.java @@ -16,7 +16,7 @@ * under the License. */ -package io.ballerina.stdlib.data.jsondata.utils; +package io.ballerina.lib.data.jsondata.utils; import io.ballerina.runtime.api.PredefinedTypes; import io.ballerina.runtime.api.creators.TypeCreator; diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticErrorCode.java b/native/src/main/java/io/ballerina/lib/data/jsondata/utils/DiagnosticErrorCode.java similarity index 97% rename from native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticErrorCode.java rename to native/src/main/java/io/ballerina/lib/data/jsondata/utils/DiagnosticErrorCode.java index 08c8fe2..de34ddb 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticErrorCode.java +++ b/native/src/main/java/io/ballerina/lib/data/jsondata/utils/DiagnosticErrorCode.java @@ -16,7 +16,7 @@ * under the License. */ -package io.ballerina.stdlib.data.jsondata.utils; +package io.ballerina.lib.data.jsondata.utils; /** * Represents a diagnostic error code. diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticLog.java b/native/src/main/java/io/ballerina/lib/data/jsondata/utils/DiagnosticLog.java similarity index 97% rename from native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticLog.java rename to native/src/main/java/io/ballerina/lib/data/jsondata/utils/DiagnosticLog.java index 9c586d5..437eb17 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/DiagnosticLog.java +++ b/native/src/main/java/io/ballerina/lib/data/jsondata/utils/DiagnosticLog.java @@ -16,7 +16,7 @@ * under the License. */ -package io.ballerina.stdlib.data.jsondata.utils; +package io.ballerina.lib.data.jsondata.utils; import io.ballerina.runtime.api.creators.ErrorCreator; import io.ballerina.runtime.api.utils.StringUtils; diff --git a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/ModuleUtils.java b/native/src/main/java/io/ballerina/lib/data/jsondata/utils/ModuleUtils.java similarity index 95% rename from native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/ModuleUtils.java rename to native/src/main/java/io/ballerina/lib/data/jsondata/utils/ModuleUtils.java index b2735ae..879316b 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/jsondata/utils/ModuleUtils.java +++ b/native/src/main/java/io/ballerina/lib/data/jsondata/utils/ModuleUtils.java @@ -16,7 +16,7 @@ * under the License. */ -package io.ballerina.stdlib.data.jsondata.utils; +package io.ballerina.lib.data.jsondata.utils; import io.ballerina.runtime.api.Environment; import io.ballerina.runtime.api.Module; diff --git a/native/src/main/java/module-info.java b/native/src/main/java/module-info.java index 4200453..18ec6bf 100644 --- a/native/src/main/java/module-info.java +++ b/native/src/main/java/module-info.java @@ -21,5 +21,5 @@ requires io.ballerina.lang.value; requires junit; requires org.apache.commons.lang3; - exports io.ballerina.stdlib.data.jsondata.json; + exports io.ballerina.lib.data.jsondata.json; } From d689f4fff486b1d9e020963a0968c8bc58032b55 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Wed, 20 Mar 2024 08:41:53 +0530 Subject: [PATCH 27/27] Rename apis --- README.md | 21 +- ballerina/Package.md | 35 +- ballerina/json_api.bal | 26 +- ballerina/tests/from_json_string_test.bal | 368 +++++++++--------- ballerina/tests/from_json_test.bal | 298 +++++++------- ballerina/tests/from_json_with_options.bal | 70 ++-- ...adonly_intersection_expected_type_test.bal | 16 +- ballerina/tests/stream_large_file_test.bal | 4 +- .../sample_package_1/sample.bal | 2 +- .../sample_package_2/sample.bal | 2 +- .../sample_package_3/sample.bal | 2 +- .../sample_package_4/sample.bal | 2 +- .../sample_package_6/sample.bal | 2 +- .../sample_package_7/sample.bal | 4 +- .../lib/data/jsondata/compiler/Constants.java | 4 +- .../compiler/JsondataTypeValidator.java | 9 +- .../lib/data/jsondata/json/Native.java | 45 +-- 17 files changed, 465 insertions(+), 445 deletions(-) diff --git a/README.md b/README.md index 89f508e..54f2ee0 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ The Ballerina JSON Data Library is a comprehensive toolkit designed to facilitat ### Converting JSON Document value to a record value -To convert an JSON document value to a record value, you can utilize the `fromJsonWithType` function provided by the library. The example below showcases the transformation of an JSON document value into a record value. +To convert an JSON document value to a record value, you can utilize the `parseAsType` function provided by the library. The example below showcases the transformation of an JSON document value into a record value. ```ballerina import ballerina/data.jsondata; @@ -27,18 +27,18 @@ type Book record { public function main() returns error? { json jsonContent = { "name": "Clean Code", - "author": "Robert C. Martin", + "author": "Robert C. Martin", "year": 2008 }; - Book book = check jsondata:fromJsonWithType(jsonContent); + Book book = check jsondata:parseAsType(jsonContent); io:println(book); } ``` ### Converting external JSON document to a record value -For transforming JSON content from an external source into a record value, the `fromJsonStringWithType` function can be used. This external source can be in the form of a string or a byte array/byte stream that houses the JSON data. This is commonly extracted from files or network sockets. The example below demonstrates the conversion of an JSON value from an external source into a record value. +For transforming JSON content from an external source into a record value, the `parseString`, `parseBytes`, `parseStream` functions can be used. This external source can be in the form of a string or a byte array/byte-block-stream that houses the JSON data. This is commonly extracted from files or network sockets. The example below demonstrates the conversion of an JSON value from an external source into a record value. ```ballerina import ballerina/data.jsondata; @@ -52,7 +52,7 @@ type Book record { public function main() returns error? { string jsonContent = check io:fileReadString("path/to/file.json"); - Book book = check jsondata:fromJsonStringWithType(jsonContent); + Book book = check jsondata:parseString(jsonContent); io:println(book); } ``` @@ -113,7 +113,7 @@ public function main() returns error? { ] }; - Author author = check jsondata:fromJsonWithType(jsonContent); + Author author = check jsondata:parseAsType(jsonContent); io:println(author); } ``` @@ -155,10 +155,10 @@ public function main() returns error? { } ]; - Book[] bookArr = check jsondata:fromJsonWithType(jsonContent); + Book[] bookArr = check jsondata:parseAsType(jsonContent); io:println(bookArr); - - [Book, Book] bookTuple = check jsondata:fromJsonWithType(jsonContent); + + [Book, Book] bookTuple = check jsondata:parseAsType(jsonContent); io:println(bookTuple); } ``` @@ -192,7 +192,7 @@ public function main() returns error? { "publisher": "Prentice Hall" }; - Book book = check jsondata:fromJsonWithType(jsonContent); + Book book = check jsondata:parseAsType(jsonContent); io:println(book); } ``` @@ -263,6 +263,5 @@ All contributors are encouraged to read the [Ballerina code of conduct](https:// ## Useful links -[//]: # (* For more information go to the [`jsondata` library](https://lib.ballerina.io/ballerina/data.jsondata/latest).) * Chat live with us via our [Discord server](https://discord.gg/ballerinalang). * Post all technical questions on Stack Overflow with the [#ballerina](https://stackoverflow.com/questions/tagged/ballerina) tag. diff --git a/ballerina/Package.md b/ballerina/Package.md index 4628e64..b5158ce 100644 --- a/ballerina/Package.md +++ b/ballerina/Package.md @@ -4,7 +4,7 @@ The Ballerina JSON Data Library is a comprehensive toolkit designed to facilitat ## Features -- **Versatile JSON Data Input**: Accept JSON data as a json, a string, byte array, or a stream and convert it into a subtype of anydata value. +- **Versatile JSON Data Input**: Accept JSON data as a ballerina JSON value, a string, byte array, or a stream and convert it into a subtype of anydata. - **JSON to anydata Value Conversion**: Transform JSON data into expected type which is subtype of anydata. - **Projection Support**: Perform selective conversion of JSON data subsets into anydata values through projection. @@ -12,7 +12,7 @@ The Ballerina JSON Data Library is a comprehensive toolkit designed to facilitat ### Converting JSON Document value to a record value -To convert an JSON document value to a Record value, you can utilize the `fromJsonWithType` function provided by the library. The example below showcases the transformation of an JSON document value into a Record value. +To convert an JSON document value to a record value, you can utilize the `parseAsType` function provided by the library. The example below showcases the transformation of an JSON document value into a record value. ```ballerina import ballerina/data.jsondata; @@ -31,14 +31,14 @@ public function main() returns error? { "year": 2008 }; - Book book = check jsondata:fromJsonWithType(jsonContent); - io:println(b); + Book book = check jsondata:parseAsType(jsonContent); + io:println(book); } ``` ### Converting external JSON document to a record value -For transforming JSON content from an external source into a Record value, the `fromJsonStringWithType` function can be used. This external source can be in the form of a string or a byte array/byte stream that houses the JSON data. This is commonly extracted from files or network sockets. The example below demonstrates the conversion of an JSON value from an external source into a Record value. +For transforming JSON content from an external source into a record value, the `parseString`, `parseBytes`, `parseStream` functions can be used. This external source can be in the form of a string or a byte array/byte-block-stream that houses the JSON data. This is commonly extracted from files or network sockets. The example below demonstrates the conversion of an JSON value from an external source into a record value. ```ballerina import ballerina/data.jsondata; @@ -52,7 +52,7 @@ type Book record { public function main() returns error? { string jsonContent = check io:fileReadString("path/to/file.json"); - Book book = check jsondata:fromJsonStringWithType(jsonContent); + Book book = check jsondata:parseString(jsonContent); io:println(book); } ``` @@ -65,9 +65,10 @@ The conversion of JSON data to subtype of anydata representation is a fundamenta ### JSON Object -The JSON Object can be represented as a record value in Ballerina which facilitates a structured and type-safe approach to handling JSON data. +The JSON Object can be represented as a value of type record/map in Ballerina which facilitates a structured and type-safe approach to handling JSON data. Take for instance the following JSON Object snippet: + ```json { "author": "Robert C. Martin", @@ -85,6 +86,7 @@ Take for instance the following JSON Object snippet: ``` This JSON Object can be represented as a record value in Ballerina as follows: + ```ballerina type Author record { string author; @@ -111,7 +113,7 @@ public function main() returns error? { ] }; - Author author = check jsondata:fromJsonWithType(jsonContent); + Author author = check jsondata:parseAsType(jsonContent); io:println(author); } ``` @@ -134,6 +136,7 @@ The JSON Array can be represented as an array/tuple values in Ballerina. ``` This JSON Array can be converted as an array/tuple in Ballerina as follows: + ```ballerina type Book record { string name; @@ -152,17 +155,17 @@ public function main() returns error? { } ]; - Book[] bookArr = check jsondata:fromJsonWithType(jsonContent); + Book[] bookArr = check jsondata:parseAsType(jsonContent); io:println(bookArr); - - [Book, Book] bookTuple = check jsondata:fromJsonWithType(jsonContent); + + [Book, Book] bookTuple = check jsondata:parseAsType(jsonContent); io:println(bookTuple); } ``` ### Controlling the JSON to record conversion -The library allows for selective conversion of JSON into records through the use of fields. This is beneficial when the JSON data contains elements that are not necessary to be transformed into record fields. +The library allows for selective conversion of JSON into closed records. This is beneficial when the JSON data contains members that are not necessary to be transformed into record fields. ```json { @@ -189,12 +192,12 @@ public function main() returns error? { "publisher": "Prentice Hall" }; - Book book = check jsondata:fromJsonWithType(jsonContent); + Book book = check jsondata:parseAsType(jsonContent); io:println(book); } ``` -However, if the rest field is utilized (or if the record type is defined as an open record), all elements in the JSON data will be transformed into record fields: +However, if the rest field is utilized (or if the record type is defined as an open record), all members in the JSON data will be transformed into record fields: ```ballerina type Book record { @@ -203,8 +206,8 @@ type Book record { } ``` -In this instance, all other elements in the JSON data, such as `year` and `publisher` will be transformed into `string` type fields with the corresponding json object member as the key. +In this instance, all other members in the JSON data, such as `year` and `publisher` will be transformed into `anydata-typed` fields with the corresponding JSON object member as the key-value pair. This behavior extends to arrays as well. -The process of projecting JSON data into a record supports various use cases, including the filtering out of unnecessary elements. This functionality is anticipated to be enhanced in the future to accommodate more complex scenarios, such as filtering values based on regular expressions, among others. +The process of projecting JSON data into a record supports various use cases, including the filtering out of unnecessary members. This functionality is anticipated to be enhanced in the future to accommodate more complex scenarios, such as filtering values based on regular expressions, among others. diff --git a/ballerina/json_api.bal b/ballerina/json_api.bal index 6246d79..b6cf13b 100644 --- a/ballerina/json_api.bal +++ b/ballerina/json_api.bal @@ -22,16 +22,34 @@ import ballerina/jballerina.java; # + options - Options to be used for filtering in the projection # + t - Target type # + return - On success, returns value belonging to the given target type, else returns an `jsondata:Error` value. -public isolated function fromJsonWithType(json v, Options options = {}, typedesc t = <>) +public isolated function parseAsType(json v, Options options = {}, typedesc t = <>) returns t|Error = @java:Method {'class: "io.ballerina.lib.data.jsondata.json.Native"} external; -# Converts JSON string, byte[] or byte-block-stream to subtype of anydata. +# Converts JSON string to subtype of anydata. # # + s - Source JSON string value or byte[] or byte-block-stream # + options - Options to be used for filtering in the projection # + t - Target type # + return - On success, value belonging to the given target type, else returns an `jsondata:Error` value. -public isolated function fromJsonStringWithType(string|byte[]|stream s, Options options = {}, typedesc t = <>) +public isolated function parseString(string s, Options options = {}, typedesc t = <>) + returns t|Error = @java:Method {'class: "io.ballerina.lib.data.jsondata.json.Native"} external; + +# Converts JSON byte[] to subtype of anydata. +# +# + s - Source JSON byte[] +# + options - Options to be used for filtering in the projection +# + t - Target type +# + return - On success, value belonging to the given target type, else returns an `jsondata:Error` value. +public isolated function parseBytes(byte[] s, Options options = {}, typedesc t = <>) + returns t|Error = @java:Method {'class: "io.ballerina.lib.data.jsondata.json.Native"} external; + +# Converts JSON byte-block-stream to subtype of anydata. +# +# + s - Source JSON byte-block-stream +# + options - Options to be used for filtering in the projection +# + t - Target type +# + return - On success, value belonging to the given target type, else returns an `jsondata:Error` value. +public isolated function parseStream(stream s, Options options = {}, typedesc t = <>) returns t|Error = @java:Method {'class: "io.ballerina.lib.data.jsondata.json.Native"} external; # Converts a value of type `anydata` to `json`. @@ -41,7 +59,7 @@ public isolated function fromJsonStringWithType(string|byte[]|stream expType, anydata expectedData) returns Error? { - anydata val1 = check fromJsonStringWithType(sourceData, {}, expType); + anydata val1 = check parseString(sourceData, {}, expType); test:assertEquals(val1, expectedData); } -function basicTypeDataProviderForFromJsonStringWithType() returns [string, typedesc, anydata][] { +function basicTypeDataProviderForParseString() returns [string, typedesc, anydata][] { return [ ["5", int, 5], ["5.5", float, 5.5], @@ -36,8 +36,8 @@ function basicTypeDataProviderForFromJsonStringWithType() returns [string, typed } @test:Config -isolated function testNilAsExpectedTypeWithFromJsonStringWithType() returns error? { - () val = check fromJsonStringWithType("null"); +isolated function testNilAsExpectedTypeWithParseString() returns error? { + () val = check parseString("null"); test:assertEquals(val, null); } @@ -45,15 +45,15 @@ isolated function testNilAsExpectedTypeWithFromJsonStringWithType() returns erro isolated function testSimpleJsonStringToRecord() returns Error? { string j = string `{"a": "hello", "b": 1}`; - SimpleRec1 recA = check fromJsonStringWithType(j); + SimpleRec1 recA = check parseString(j); test:assertEquals(recA.a, "hello"); test:assertEquals(recA.b, 1); - SimpleRec2 recB = check fromJsonStringWithType(j); + SimpleRec2 recB = check parseString(j); test:assertEquals(recB.a, "hello"); test:assertEquals(recB.b, 1); - OpenRecord recC = check fromJsonStringWithType(j); + OpenRecord recC = check parseString(j); test:assertEquals(recC.get("a"), "hello"); test:assertEquals(recC.get("b"), 1); } @@ -62,7 +62,7 @@ isolated function testSimpleJsonStringToRecord() returns Error? { isolated function testSimpleJsonStringToRecordWithProjection() returns Error? { string str = string `{"a": "hello", "b": 1}`; - record {|string a;|} recA = check fromJsonStringWithType(str); + record {|string a;|} recA = check parseString(str); test:assertEquals(recA.length(), 1); test:assertEquals(recA.a, "hello"); test:assertEquals(recA, {"a": "hello"}); @@ -79,7 +79,7 @@ isolated function testNestedJsonStringToRecord() returns Error? { } }`; - NestedRecord1 recA = check fromJsonStringWithType(str); + NestedRecord1 recA = check parseString(str); test:assertEquals(recA.length(), 3); test:assertEquals(recA.a, "hello"); test:assertEquals(recA.b, 1); @@ -87,7 +87,7 @@ isolated function testNestedJsonStringToRecord() returns Error? { test:assertEquals(recA.c.d, "world"); test:assertEquals(recA.c.e, 2); - NestedRecord2 recB = check fromJsonStringWithType(str); + NestedRecord2 recB = check parseString(str); test:assertEquals(recB.length(), 3); test:assertEquals(recB.a, "hello"); test:assertEquals(recB.b, 1); @@ -95,7 +95,7 @@ isolated function testNestedJsonStringToRecord() returns Error? { test:assertEquals(recB.c.d, "world"); test:assertEquals(recB.c.e, 2); - OpenRecord recC = check fromJsonStringWithType(str); + OpenRecord recC = check parseString(str); test:assertEquals(recC.get("a"), "hello"); test:assertEquals(recC.get("b"), 1); test:assertEquals(recC.get("c"), {d: "world", e: 2}); @@ -112,7 +112,7 @@ isolated function testNestedJsonStringToRecordWithProjection() returns Error? { } }`; - record {|string a; record {|string d;|} c;|} recA = check fromJsonStringWithType(str); + record {|string a; record {|string d;|} c;|} recA = check parseString(str); test:assertEquals(recA.a, "hello"); test:assertEquals(recA.c.d, "world"); test:assertEquals(recA, {"a": "hello", "c": {"d": "world"}}); @@ -122,7 +122,7 @@ isolated function testNestedJsonStringToRecordWithProjection() returns Error? { isolated function testJsonStringToRecordWithOptionalFields() returns Error? { string str = string `{"a": "hello"}`; - record {|string a; int b?;|} recA = check fromJsonStringWithType(str); + record {|string a; int b?;|} recA = check parseString(str); test:assertEquals(recA.length(), 1); test:assertEquals(recA.a, "hello"); test:assertEquals(recA.b, null); @@ -139,14 +139,14 @@ isolated function testJsonStringToRecordWithOptionalFieldsWithProjection() retur } }`; - record {|string a; record {|string d; int f?;|} c;|} recA = check fromJsonStringWithType(str); + record {|string a; record {|string d; int f?;|} c;|} recA = check parseString(str); test:assertEquals(recA.a, "hello"); test:assertEquals(recA.c.d, "world"); test:assertEquals(recA, {"a": "hello", "c": {"d": "world"}}); } @test:Config -isolated function testFromJsonStringWithType1() returns Error? { +isolated function testParseString1() returns Error? { string str = string `{ "id": 2, "name": "Anne", @@ -156,7 +156,7 @@ isolated function testFromJsonStringWithType1() returns Error? { } }`; - R x = check fromJsonStringWithType(str); + R x = check parseString(str); test:assertEquals(x.id, 2); test:assertEquals(x.name, "Anne"); test:assertEquals(x.address.street, "Main"); @@ -172,13 +172,13 @@ isolated function testMapTypeAsFieldTypeInRecordForJsonString() returns Error? { } }`; - Company x = check fromJsonStringWithType(str); + Company x = check parseString(str); test:assertEquals(x.employees["John"], "Manager"); test:assertEquals(x.employees["Anne"], "Developer"); } @test:Config -isolated function testFromJsonStringWithType2() returns Error? { +isolated function testParseString2() returns Error? { string str = string `{ "name": "John", "age": 30, @@ -192,7 +192,7 @@ isolated function testFromJsonStringWithType2() returns Error? { } }`; - Person x = check fromJsonStringWithType(str); + Person x = check parseString(str); test:assertEquals(x.length(), 3); test:assertEquals(x.name, "John"); test:assertEquals(x.age, 30); @@ -205,7 +205,7 @@ isolated function testFromJsonStringWithType2() returns Error? { } @test:Config -isolated function testFromJsonStringWithType3() returns Error? { +isolated function testParseString3() returns Error? { string str = string `{ "title": "To Kill a Mockingbird", "author": { @@ -223,7 +223,7 @@ isolated function testFromJsonStringWithType3() returns Error? { } }`; - Book x = check fromJsonStringWithType(str); + Book x = check parseString(str); test:assertEquals(x.title, "To Kill a Mockingbird"); test:assertEquals(x.author.name, "Harper Lee"); test:assertEquals(x.author.birthdate, "1926-04-28"); @@ -236,7 +236,7 @@ isolated function testFromJsonStringWithType3() returns Error? { } @test:Config -isolated function testFromJsonStringWithType4() returns Error? { +isolated function testParseString4() returns Error? { string str = string `{ "name": "School Twelve", "city": 23, @@ -246,7 +246,7 @@ isolated function testFromJsonStringWithType4() returns Error? { "tp": 12345 }`; - School x = check fromJsonStringWithType(str); + School x = check parseString(str); test:assertEquals(x.length(), 6); test:assertEquals(x.name, "School Twelve"); test:assertEquals(x.number, 12); @@ -256,7 +256,7 @@ isolated function testFromJsonStringWithType4() returns Error? { } @test:Config -isolated function testFromJsonStringWithType5() returns Error? { +isolated function testParseString5() returns Error? { string str = string `{ "intValue": 10, "floatValue": 10.5, @@ -265,7 +265,7 @@ isolated function testFromJsonStringWithType5() returns Error? { "doNotParse": "abc" }`; - TestRecord x = check fromJsonStringWithType(str); + TestRecord x = check parseString(str); test:assertEquals(x.length(), 5); test:assertEquals(x.intValue, 10); test:assertEquals(x.floatValue, 10.5f); @@ -275,7 +275,7 @@ isolated function testFromJsonStringWithType5() returns Error? { } @test:Config -isolated function testFromJsonStringWithType6() returns Error? { +isolated function testParseString6() returns Error? { string str = string `{ "id": 1, "name": "Class A", @@ -297,7 +297,7 @@ isolated function testFromJsonStringWithType6() returns Error? { "monitor": null }`; - Class x = check fromJsonStringWithType(str); + Class x = check parseString(str); test:assertEquals(x.length(), 5); test:assertEquals(x.id, 1); test:assertEquals(x.name, "Class A"); @@ -316,7 +316,7 @@ isolated function testFromJsonStringWithType6() returns Error? { } @test:Config -isolated function testFromJsonStringWithType7() returns Error? { +isolated function testParseString7() returns Error? { string nestedJsonStr = string `{ "intValue": 5, "floatValue": 2.5, @@ -329,7 +329,7 @@ isolated function testFromJsonStringWithType7() returns Error? { "nested1": ${nestedJsonStr} }`; - TestRecord2 x = check fromJsonStringWithType(str); + TestRecord2 x = check parseString(str); test:assertEquals(x.length(), 2); test:assertEquals(x.intValue, 10); test:assertEquals(x.nested1.length(), 4); @@ -337,27 +337,27 @@ isolated function testFromJsonStringWithType7() returns Error? { } @test:Config -isolated function testFromJsonStringWithType8() returns Error? { +isolated function testParseString8() returns Error? { string str = string `{ "street": "Main", "city": "Mahar", "house": 94 }`; - TestR x = check fromJsonStringWithType(str); + TestR x = check parseString(str); test:assertEquals(x.street, "Main"); test:assertEquals(x.city, "Mahar"); } @test:Config -isolated function testFromJsonStringWithType9() returns Error? { +isolated function testParseString9() returns Error? { string str = string `{ "street": "Main", "city": "Mahar", "houses": [94, 95, 96] }`; - TestArr1 x = check fromJsonStringWithType(str); + TestArr1 x = check parseString(str); test:assertEquals(x.length(), 3); test:assertEquals(x.street, "Main"); test:assertEquals(x.city, "Mahar"); @@ -365,14 +365,14 @@ isolated function testFromJsonStringWithType9() returns Error? { } @test:Config -isolated function testFromJsonStringWithType10() returns Error? { +isolated function testParseString10() returns Error? { string str = string `{ "street": "Main", "city": 11, "house": [94, "Gedara"] }`; - TestArr2 x = check fromJsonStringWithType(str); + TestArr2 x = check parseString(str); test:assertEquals(x.length(), 3); test:assertEquals(x.street, "Main"); test:assertEquals(x.city, 11); @@ -380,14 +380,14 @@ isolated function testFromJsonStringWithType10() returns Error? { } @test:Config -isolated function testFromJsonStringWithType11() returns Error? { +isolated function testParseString11() returns Error? { string str = string `{ "street": "Main", "city": "Mahar", "house": [94, [1, 2, 3]] }`; - TestArr3 x = check fromJsonStringWithType(str); + TestArr3 x = check parseString(str); test:assertEquals(x.length(), 3); test:assertEquals(x.street, "Main"); test:assertEquals(x.city, "Mahar"); @@ -395,7 +395,7 @@ isolated function testFromJsonStringWithType11() returns Error? { } @test:Config -isolated function testFromJsonStringWithType12() returns Error? { +isolated function testParseString12() returns Error? { string str = string `{ "street": "Main", "city": { @@ -405,21 +405,21 @@ isolated function testFromJsonStringWithType12() returns Error? { "flag": true }`; - TestJson x = check fromJsonStringWithType(str); + TestJson x = check parseString(str); test:assertEquals(x.length(), 3); test:assertEquals(x.street, "Main"); test:assertEquals(x.city, {"name": "Mahar", "code": 94}); } @test:Config -isolated function testFromJsonStringWithType13() returns Error? { +isolated function testParseString13() returns Error? { string str = string `{ "street": "Main", "city": "Mahar", "house": [94, [1, 3, "4"]] }`; - TestArr3 x = check fromJsonStringWithType(str); + TestArr3 x = check parseString(str); test:assertEquals(x.length(), 3); test:assertEquals(x.street, "Main"); test:assertEquals(x.city, "Mahar"); @@ -427,7 +427,7 @@ isolated function testFromJsonStringWithType13() returns Error? { } @test:Config -isolated function testFromJsonStringWithType14() returns Error? { +isolated function testParseString14() returns Error? { string str = string `{ "id": 12, "name": "Anne", @@ -438,7 +438,7 @@ isolated function testFromJsonStringWithType14() returns Error? { } }`; - RN x = check fromJsonStringWithType(str); + RN x = check parseString(str); test:assertEquals(x.length(), 3); test:assertEquals(x.id, 12); test:assertEquals(x.name, "Anne"); @@ -449,23 +449,23 @@ isolated function testFromJsonStringWithType14() returns Error? { } @test:Config -isolated function testFromJsonStringWithType15() returns Error? { +isolated function testParseString15() returns Error? { string str = string `[1, 2, 3]`; - IntArr x = check fromJsonStringWithType(str); + IntArr x = check parseString(str); test:assertEquals(x, [1, 2, 3]); } @test:Config -isolated function testFromJsonStringWithType16() returns Error? { +isolated function testParseString16() returns Error? { string str = string `[1, "abc", [3, 4.0]]`; - Tuple x = check fromJsonStringWithType(str); + Tuple x = check parseString(str); test:assertEquals(x, [1, "abc", [3, 4.0]]); } @test:Config -isolated function testFromJsonStringWithType17() returns Error? { +isolated function testParseString17() returns Error? { string str = string `{ "street": "Main", "city": { @@ -479,14 +479,14 @@ isolated function testFromJsonStringWithType17() returns Error? { "flag": true }`; - TestJson x = check fromJsonStringWithType(str); + TestJson x = check parseString(str); test:assertEquals(x.length(), 3); test:assertEquals(x.street, "Main"); test:assertEquals(x.city, {"name": "Mahar", "code": 94, "internal": {"id": 12, "agent": "Anne"}}); } @test:Config -isolated function testFromJsonStringWithType18() returns Error? { +isolated function testParseString18() returns Error? { string str = string `{ "books": [ { @@ -504,7 +504,7 @@ isolated function testFromJsonStringWithType18() returns Error? { ] }`; - Library x = check fromJsonStringWithType(str); + Library x = check parseString(str); test:assertEquals(x.books.length(), 2); test:assertEquals(x.books[0].title, "The Great Gatsby"); test:assertEquals(x.books[0].author, "F. Scott Fitzgerald"); @@ -521,7 +521,7 @@ type LibraryC record {| |}; @test:Config -isolated function testFromJsonStringWithType19() returns Error? { +isolated function testParseString19() returns Error? { string str = string `{ "books": [ { @@ -539,14 +539,14 @@ isolated function testFromJsonStringWithType19() returns Error? { ] }`; - LibraryB x = check fromJsonStringWithType(str); + LibraryB x = check parseString(str); test:assertEquals(x.books.length(), 2); test:assertEquals(x.books[0].title, "The Great Gatsby"); test:assertEquals(x.books[0].author, "F. Scott Fitzgerald"); test:assertEquals(x.books[1].title, "The Grapes of Wrath"); test:assertEquals(x.books[1].author, "John Steinbeck"); - LibraryC y = check fromJsonStringWithType(str); + LibraryC y = check parseString(str); test:assertEquals(y.books.length(), 3); test:assertEquals(y.books[0].title, "The Great Gatsby"); test:assertEquals(y.books[0].author, "F. Scott Fitzgerald"); @@ -557,7 +557,7 @@ isolated function testFromJsonStringWithType19() returns Error? { } @test:Config -isolated function testFromJsonStringWithType20() returns Error? { +isolated function testParseString20() returns Error? { string str1 = string `{ "a": { "c": "world", @@ -574,7 +574,7 @@ isolated function testFromJsonStringWithType20() returns Error? { string c; string d; |}...; - |} val1 = check fromJsonStringWithType(str1); + |} val1 = check parseString(str1); test:assertEquals(val1.length(), 2); test:assertEquals(val1["a"]["c"], "world"); test:assertEquals(val1["a"]["d"], "2"); @@ -583,7 +583,7 @@ isolated function testFromJsonStringWithType20() returns Error? { record {| map...; - |} val2 = check fromJsonStringWithType(str1); + |} val2 = check parseString(str1); test:assertEquals(val2.length(), 2); test:assertEquals(val2["a"]["c"], "world"); test:assertEquals(val2["a"]["d"], "2"); @@ -606,7 +606,7 @@ isolated function testFromJsonStringWithType20() returns Error? { string c; string d; |}[]...; - |} val3 = check fromJsonStringWithType(str3); + |} val3 = check parseString(str3); test:assertEquals(val3.length(), 2); test:assertEquals(val3["a"], [ { @@ -623,8 +623,8 @@ isolated function testFromJsonStringWithType20() returns Error? { } @test:Config -isolated function testUnionTypeAsExpTypeForFromJsonStringWithType() returns Error? { - decimal|float val1 = check fromJsonStringWithType("1.0"); +isolated function testUnionTypeAsExpTypeForParseString() returns Error? { + decimal|float val1 = check parseString("1.0"); test:assertEquals(val1, 1.0); string str2 = string `{ @@ -634,7 +634,7 @@ isolated function testUnionTypeAsExpTypeForFromJsonStringWithType() returns Erro record {| decimal|float b; - |} val2 = check fromJsonStringWithType(str2); + |} val2 = check parseString(str2); test:assertEquals(val2.length(), 1); test:assertEquals(val2.b, 1.0); @@ -651,7 +651,7 @@ isolated function testUnionTypeAsExpTypeForFromJsonStringWithType() returns Erro record {| record {|decimal|int b; record {|string|boolean e;|} d;|} a; decimal|float c; - |} val3 = check fromJsonStringWithType(str3); + |} val3 = check parseString(str3); test:assertEquals(val3.length(), 2); test:assertEquals(val3.a.length(), 2); test:assertEquals(val3.a.b, 1); @@ -660,9 +660,9 @@ isolated function testUnionTypeAsExpTypeForFromJsonStringWithType() returns Erro } @test:Config -isolated function testAnydataAsExpTypeForFromJsonStringWithType() returns Error? { +isolated function testAnydataAsExpTypeForParseString() returns Error? { string jsonStr1 = string `1`; - anydata val1 = check fromJsonStringWithType(jsonStr1); + anydata val1 = check parseString(jsonStr1); test:assertEquals(val1, 1); string jsonStr2 = string `{ @@ -670,7 +670,7 @@ isolated function testAnydataAsExpTypeForFromJsonStringWithType() returns Error? "b": 1 }`; - anydata val2 = check fromJsonStringWithType(jsonStr2); + anydata val2 = check parseString(jsonStr2); test:assertEquals(val2, {"a": "hello", "b": 1}); string jsonStr3 = string `{ @@ -683,7 +683,7 @@ isolated function testAnydataAsExpTypeForFromJsonStringWithType() returns Error? "c": 2 }`; - anydata val3 = check fromJsonStringWithType(jsonStr3); + anydata val3 = check parseString(jsonStr3); test:assertEquals(val3, {"a": {"b": 1, "d": {"e": "hello"}}, "c": 2}); string jsonStr4 = string `{ @@ -696,18 +696,18 @@ isolated function testAnydataAsExpTypeForFromJsonStringWithType() returns Error? "c": 2 }`; - anydata val4 = check fromJsonStringWithType(jsonStr4); + anydata val4 = check parseString(jsonStr4); test:assertEquals(val4, {"a": [{"b": 1, "d": {"e": "hello"}}], "c": 2}); string str5 = string `[[1], 2]`; - anydata val5 = check fromJsonStringWithType(str5); + anydata val5 = check parseString(str5); test:assertEquals(val5, [[1], 2]); } @test:Config -isolated function testJsonAsExpTypeForFromJsonStringWithType() returns Error? { +isolated function testJsonAsExpTypeForParseString() returns Error? { string jsonStr1 = string `1`; - json val1 = check fromJsonStringWithType(jsonStr1); + json val1 = check parseString(jsonStr1); test:assertEquals(val1, 1); string jsonStr2 = string `{ @@ -715,7 +715,7 @@ isolated function testJsonAsExpTypeForFromJsonStringWithType() returns Error? { "b": 1 }`; - json val2 = check fromJsonStringWithType(jsonStr2); + json val2 = check parseString(jsonStr2); test:assertEquals(val2, {"a": "hello", "b": 1}); string jsonStr3 = string `{ @@ -728,7 +728,7 @@ isolated function testJsonAsExpTypeForFromJsonStringWithType() returns Error? { "c": 2 }`; - json val3 = check fromJsonStringWithType(jsonStr3); + json val3 = check parseString(jsonStr3); test:assertEquals(val3, {"a": {"b": 1, "d": {"e": "hello"}}, "c": 2}); string jsonStr4 = string `{ @@ -741,22 +741,22 @@ isolated function testJsonAsExpTypeForFromJsonStringWithType() returns Error? { "c": 2 }`; - json val4 = check fromJsonStringWithType(jsonStr4); + json val4 = check parseString(jsonStr4); test:assertEquals(val4, {"a": [{"b": 1, "d": {"e": "hello"}}], "c": 2}); string str5 = string `[[1], 2]`; - json val5 = check fromJsonStringWithType(str5); + json val5 = check parseString(str5); test:assertEquals(val5, [[1], 2]); } @test:Config -isolated function testMapAsExpTypeForFromJsonStringWithType() returns Error? { +isolated function testMapAsExpTypeForParseString() returns Error? { string jsonStr1 = string `{ "a": "hello", "b": 1 }`; - map val1 = check fromJsonStringWithType(jsonStr1); + map val1 = check parseString(jsonStr1); test:assertEquals(val1, {"a": "hello", "b": "1"}); string jsonStr2 = string `{ @@ -771,7 +771,7 @@ isolated function testMapAsExpTypeForFromJsonStringWithType() returns Error? { string a; int b; map c; - |} val2 = check fromJsonStringWithType(jsonStr2); + |} val2 = check parseString(jsonStr2); test:assertEquals(val2.a, "hello"); test:assertEquals(val2.b, 1); test:assertEquals(val2.c, {"d": "world", "e": "2"}); @@ -787,18 +787,18 @@ isolated function testMapAsExpTypeForFromJsonStringWithType() returns Error? { } }`; - map> val3 = check fromJsonStringWithType(jsonStr3); + map> val3 = check parseString(jsonStr3); test:assertEquals(val3, {"a": {"c": "world", "d": "2"}, "b": {"c": "world", "d": "2"}}); record {| map a; - |} val4 = check fromJsonStringWithType(jsonStr3); + |} val4 = check parseString(jsonStr3); test:assertEquals(val4.a, {"c": "world", "d": "2"}); map val5 = check fromJsonStringWithType(jsonStr3); + |}> val5 = check parseString(jsonStr3); test:assertEquals(val5, {"a": {"c": "world", "d": 2}, "b": {"c": "world", "d": 2}}); string jsonStr6 = string `{ @@ -817,57 +817,57 @@ isolated function testMapAsExpTypeForFromJsonStringWithType() returns Error? { record {| string a; map> b; - |} val6 = check fromJsonStringWithType(jsonStr6); + |} val6 = check parseString(jsonStr6); test:assertEquals(val6.a, "Kanth"); test:assertEquals(val6.b, {"g": {"c": "hello", "d": "1"}, "h": {"c": "world", "d": "2"}}); } @test:Config -isolated function testProjectionInTupleForFromJsonStringWithType() returns Error? { +isolated function testProjectionInTupleForParseString() returns Error? { string str1 = string `[1, 2, 3, 4, 5, 8]`; - [string, float] val1 = check fromJsonStringWithType(str1); + [string, float] val1 = check parseString(str1); test:assertEquals(val1, ["1", 2.0]); string str2 = string `{ "a": [1, 2, 3, 4, 5, 8] }`; - record {|[string, float] a;|} val2 = check fromJsonStringWithType(str2); + record {|[string, float] a;|} val2 = check parseString(str2); test:assertEquals(val2.a, ["1", 2.0]); string str3 = string `[1, "4"]`; - [float] val3 = check fromJsonStringWithType(str3); + [float] val3 = check parseString(str3); test:assertEquals(val3, [1.0]); string str4 = string `["1", {}]`; - [float] val4 = check fromJsonStringWithType(str4); + [float] val4 = check parseString(str4); test:assertEquals(val4, [1.0]); string str5 = string `["1", [], {"name": 1}]`; - [float] val5 = check fromJsonStringWithType(str5); + [float] val5 = check parseString(str5); test:assertEquals(val5, [1.0]); } @test:Config -isolated function testProjectionInArrayForFromJsonStringWithType() returns Error? { +isolated function testProjectionInArrayForParseString() returns Error? { string strVal = string `[1, 2, 3, 4, 5]`; - int[] val = check fromJsonStringWithType(strVal); + int[] val = check parseString(strVal); test:assertEquals(val, [1, 2, 3, 4, 5]); string strVal2 = string `[1, 2, 3, 4, 5]`; - int[2] val2 = check fromJsonStringWithType(strVal2); + int[2] val2 = check parseString(strVal2); test:assertEquals(val2, [1, 2]); string strVal3 = string `{ "a": [1, 2, 3, 4, 5] }`; - record {|int[2] a;|} val3 = check fromJsonStringWithType(strVal3); + record {|int[2] a;|} val3 = check parseString(strVal3); test:assertEquals(val3, {a: [1, 2]}); string strVal4 = string `{ "a": [1, 2, 3, 4, 5], "b": [1, 2, 3, 4, 5] }`; - record {|int[2] a; int[3] b;|} val4 = check fromJsonStringWithType(strVal4); + record {|int[2] a; int[3] b;|} val4 = check parseString(strVal4); test:assertEquals(val4, {a: [1, 2], b: [1, 2, 3]}); string strVal5 = string `{ @@ -880,22 +880,22 @@ isolated function testProjectionInArrayForFromJsonStringWithType() returns Error } ] }`; - record {|record {|string name; int age;|}[1] employees;|} val5 = check fromJsonStringWithType(strVal5); + record {|record {|string name; int age;|}[1] employees;|} val5 = check parseString(strVal5); test:assertEquals(val5, {employees: [{name: "Prakanth", age: 26}]}); string strVal6 = string `["1", 2, 3, { "a" : val_a }]`; - int[3] val6 = check fromJsonStringWithType(strVal6); + int[3] val6 = check parseString(strVal6); test:assertEquals(val6, [1, 2, 3]); } @test:Config -isolated function testProjectionInRecordForFromJsonStringWithType() returns Error? { +isolated function testProjectionInRecordForParseString() returns Error? { string jsonStr1 = string `{"name": "John", "age": 30, "city": "New York"}`; - record {|string name; string city;|} val1 = check fromJsonStringWithType(jsonStr1); + record {|string name; string city;|} val1 = check parseString(jsonStr1); test:assertEquals(val1, {name: "John", city: "New York"}); string jsonStr2 = string `{"name": John, "age": "30", "city": "New York"}`; - record {|string name; string city;|} val2 = check fromJsonStringWithType(jsonStr2); + record {|string name; string city;|} val2 = check parseString(jsonStr2); test:assertEquals(val2, {name: "John", city: "New York"}); string jsonStr3 = string `{ "name": "John", @@ -908,7 +908,7 @@ isolated function testProjectionInRecordForFromJsonStringWithType() returns Erro } }, "city": "New York" }`; - record {|string name; string city;|} val3 = check fromJsonStringWithType(jsonStr3); + record {|string name; string city;|} val3 = check parseString(jsonStr3); test:assertEquals(val3, {name: "John", city: "New York"}); string jsonStr4 = string `{ "name": "John", @@ -921,7 +921,7 @@ isolated function testProjectionInRecordForFromJsonStringWithType() returns Erro } }], "city": "New York" }`; - record {|string name; string city;|} val4 = check fromJsonStringWithType(jsonStr4); + record {|string name; string city;|} val4 = check parseString(jsonStr4); test:assertEquals(val4, {name: "John", city: "New York"}); string jsonStr5 = string `{ "name": "John", @@ -943,82 +943,82 @@ isolated function testProjectionInRecordForFromJsonStringWithType() returns Erro } }] }`; - record {|string name; string city;|} val5 = check fromJsonStringWithType(jsonStr5); + record {|string name; string city;|} val5 = check parseString(jsonStr5); test:assertEquals(val5, {name: "John", city: "New York"}); } @test:Config -isolated function testArrayOrTupleCaseForFromJsonStringWithType() returns Error? { +isolated function testArrayOrTupleCaseForParseString() returns Error? { string jsonStr1 = string `[["1"], 2.0]`; - [[int], float] val1 = check fromJsonStringWithType(jsonStr1); + [[int], float] val1 = check parseString(jsonStr1); test:assertEquals(val1, [[1], 2.0]); string jsonStr2 = string `[["1", 2], 2.0]`; - [[int, float], string] val2 = check fromJsonStringWithType(jsonStr2); + [[int, float], string] val2 = check parseString(jsonStr2); test:assertEquals(val2, [[1, 2.0], "2.0"]); string jsonStr3 = string `[["1", 2], [2, "3"]]`; - int[][] val3 = check fromJsonStringWithType(jsonStr3); + int[][] val3 = check parseString(jsonStr3); test:assertEquals(val3, [[1, 2], [2, 3]]); string jsonStr4 = string `{"val" : [[1, 2], "2.0", 3.0, [5, 6]]}`; record {| [[int, float], string, float, [string, int]] val; - |} val4 = check fromJsonStringWithType(jsonStr4); + |} val4 = check parseString(jsonStr4); test:assertEquals(val4, {val: [[1, 2.0], "2.0", 3.0, ["5", 6]]}); string jsonStr41 = string `{"val1" : [[1, 2], "2.0", 3.0, [5, 6]], "val2" : [[1, 2], "2.0", 3.0, [5, 6]]}`; record {| [[int, float], string, float, [string, int]] val1; [[float, float], string, float, [string, float]] val2; - |} val41 = check fromJsonStringWithType(jsonStr41); + |} val41 = check parseString(jsonStr41); test:assertEquals(val41, {val1: [[1, 2.0], "2.0", 3.0, ["5", 6]], val2: [[1.0, 2.0], "2.0", 3.0, ["5", 6.0]]}); string jsonStr5 = string `{"val" : [["1", 2], [2, "3"]]}`; record {| int[][] val; - |} val5 = check fromJsonStringWithType(jsonStr5); + |} val5 = check parseString(jsonStr5); test:assertEquals(val5, {val: [[1, 2], [2, 3]]}); string jsonStr6 = string `[{"val" : [["1", 2], [2, "3"]]}]`; - [record {|int[][] val;|}] val6 = check fromJsonStringWithType(jsonStr6); + [record {|int[][] val;|}] val6 = check parseString(jsonStr6); test:assertEquals(val6, [{val: [[1, 2], [2, 3]]}]); } @test:Config -isolated function testListFillerValuesWithFromJsonStringWithType() returns Error? { - int[2] jsonVal1 = check fromJsonStringWithType("[1]"); +isolated function testListFillerValuesWithParseString() returns Error? { + int[2] jsonVal1 = check parseString("[1]"); test:assertEquals(jsonVal1, [1, 0]); - [int, float, string, boolean] jsonVal2 = check fromJsonStringWithType("[1]"); + [int, float, string, boolean] jsonVal2 = check parseString("[1]"); test:assertEquals(jsonVal2, [1, 0.0, "", false]); record {| float[3] A; [int, decimal, float, boolean] B; - |} jsonVal3 = check fromJsonStringWithType(string `{"A": [1], "B": [1]}`); + |} jsonVal3 = check parseString(string `{"A": [1], "B": [1]}`); test:assertEquals(jsonVal3, {A: [1.0, 0.0, 0.0], B: [1, 0d, 0.0, false]}); } @test:Config -isolated function testSingletonAsExpectedTypeForFromJsonStringWithType() returns Error? { - "1" val1 = check fromJsonStringWithType("1"); +isolated function testSingletonAsExpectedTypeForParseString() returns Error? { + "1" val1 = check parseString("1"); test:assertEquals(val1, "1"); - Singleton1 val2 = check fromJsonStringWithType("1"); + Singleton1 val2 = check parseString("1"); test:assertEquals(val2, 1); - SingletonUnion val3 = check fromJsonStringWithType("1"); + SingletonUnion val3 = check parseString("1"); test:assertEquals(val3, 1); - () val4 = check fromJsonStringWithType("null"); + () val4 = check parseString("null"); test:assertEquals(val4, ()); string str5 = string `{ "value": "1", "id": "3" }`; - SingletonInRecord val5 = check fromJsonStringWithType(str5); + SingletonInRecord val5 = check parseString(str5); test:assertEquals(val5.id, "3"); test:assertEquals(val5.value, 1); } @@ -1034,32 +1034,32 @@ function testDuplicateKeyInTheStringSource() returns Error? { record { int id; string name; - } employee = check fromJsonStringWithType(str); + } employee = check parseString(str); test:assertEquals(employee.length(), 2); test:assertEquals(employee.id, 2); test:assertEquals(employee.name, "Anne"); } @test:Config -function testNameAnnotationWithFromJsonStringWithType() returns Error? { +function testNameAnnotationWithParseString() returns Error? { string jsonStr = string `{ "id": 1, "title-name": "Harry Potter", "author-name": "J.K. Rowling" }`; - Book2 book = check fromJsonStringWithType(jsonStr); + Book2 book = check parseString(jsonStr); test:assertEquals(book.id, 1); test:assertEquals(book.title, "Harry Potter"); test:assertEquals(book.author, "J.K. Rowling"); } @test:Config -isolated function testByteAsExpectedTypeForFromJsonStringWithType() returns Error? { - byte val1 = check fromJsonStringWithType("1"); +isolated function testByteAsExpectedTypeForParseString() returns Error? { + byte val1 = check parseString("1"); test:assertEquals(val1, 1); - [byte, int] val2 = check fromJsonStringWithType("[255, 2000]"); + [byte, int] val2 = check parseString("[255, 2000]"); test:assertEquals(val2, [255, 2000]); string str4 = string `{ @@ -1080,7 +1080,7 @@ isolated function testByteAsExpectedTypeForFromJsonStringWithType() returns Erro string city; byte id; } address; - } val4 = check fromJsonStringWithType(str4); + } val4 = check parseString(str4); test:assertEquals(val4.length(), 3); test:assertEquals(val4.id, 1); test:assertEquals(val4.name, "Anne"); @@ -1091,14 +1091,14 @@ isolated function testByteAsExpectedTypeForFromJsonStringWithType() returns Erro } @test:Config -isolated function testSignedInt8AsExpectedTypeForFromJsonStringWithType() returns Error? { - int:Signed8 val1 = check fromJsonStringWithType("-128"); +isolated function testSignedInt8AsExpectedTypeForParseString() returns Error? { + int:Signed8 val1 = check parseString("-128"); test:assertEquals(val1, -128); - int:Signed8 val2 = check fromJsonStringWithType("127"); + int:Signed8 val2 = check parseString("127"); test:assertEquals(val2, 127); - [int:Signed8, int] val3 = check fromJsonStringWithType("[127, 2000]"); + [int:Signed8, int] val3 = check parseString("[127, 2000]"); test:assertEquals(val3, [127, 2000]); string str4 = string `{ @@ -1119,7 +1119,7 @@ isolated function testSignedInt8AsExpectedTypeForFromJsonStringWithType() return string city; int:Signed8 id; } address; - } val4 = check fromJsonStringWithType(str4); + } val4 = check parseString(str4); test:assertEquals(val4.length(), 3); test:assertEquals(val4.id, 100); test:assertEquals(val4.name, "Anne"); @@ -1130,14 +1130,14 @@ isolated function testSignedInt8AsExpectedTypeForFromJsonStringWithType() return } @test:Config -isolated function testSignedInt16AsExpectedTypeForFromJsonStringWithType() returns Error? { - int:Signed16 val1 = check fromJsonStringWithType("-32768"); +isolated function testSignedInt16AsExpectedTypeForParseString() returns Error? { + int:Signed16 val1 = check parseString("-32768"); test:assertEquals(val1, -32768); - int:Signed16 val2 = check fromJsonStringWithType("32767"); + int:Signed16 val2 = check parseString("32767"); test:assertEquals(val2, 32767); - [int:Signed16, int] val3 = check fromJsonStringWithType("[32767, -324234]"); + [int:Signed16, int] val3 = check parseString("[32767, -324234]"); test:assertEquals(val3, [32767, -324234]); string str4 = string `{ @@ -1158,7 +1158,7 @@ isolated function testSignedInt16AsExpectedTypeForFromJsonStringWithType() retur string city; int:Signed16 id; } address; - } val4 = check fromJsonStringWithType(str4); + } val4 = check parseString(str4); test:assertEquals(val4.length(), 3); test:assertEquals(val4.id, 100); test:assertEquals(val4.name, "Anne"); @@ -1169,14 +1169,14 @@ isolated function testSignedInt16AsExpectedTypeForFromJsonStringWithType() retur } @test:Config -isolated function testSignedInt32AsExpectedTypeForFromJsonStringWithType() returns Error? { - int:Signed32 val1 = check fromJsonStringWithType("-2147483648"); +isolated function testSignedInt32AsExpectedTypeForParseString() returns Error? { + int:Signed32 val1 = check parseString("-2147483648"); test:assertEquals(val1, -2147483648); - int:Signed32 val2 = check fromJsonStringWithType("2147483647"); + int:Signed32 val2 = check parseString("2147483647"); test:assertEquals(val2, 2147483647); - int:Signed32[] val3 = check fromJsonStringWithType("[2147483647, -2147483648]"); + int:Signed32[] val3 = check parseString("[2147483647, -2147483648]"); test:assertEquals(val3, [2147483647, -2147483648]); string str4 = string `{ @@ -1197,7 +1197,7 @@ isolated function testSignedInt32AsExpectedTypeForFromJsonStringWithType() retur string city; int:Signed32 id; } address; - } val4 = check fromJsonStringWithType(str4); + } val4 = check parseString(str4); test:assertEquals(val4.length(), 3); test:assertEquals(val4.id, 2147483647); test:assertEquals(val4.name, "Anne"); @@ -1208,14 +1208,14 @@ isolated function testSignedInt32AsExpectedTypeForFromJsonStringWithType() retur } @test:Config -isolated function testUnSignedInt8AsExpectedTypeForFromJsonStringWithType() returns Error? { - int:Unsigned8 val1 = check fromJsonStringWithType("255"); +isolated function testUnSignedInt8AsExpectedTypeForParseString() returns Error? { + int:Unsigned8 val1 = check parseString("255"); test:assertEquals(val1, 255); - int:Unsigned8 val2 = check fromJsonStringWithType("0"); + int:Unsigned8 val2 = check parseString("0"); test:assertEquals(val2, 0); - int:Unsigned8[] val3 = check fromJsonStringWithType("[0, 255]"); + int:Unsigned8[] val3 = check parseString("[0, 255]"); test:assertEquals(val3, [0, 255]); string str4 = string `{ @@ -1236,7 +1236,7 @@ isolated function testUnSignedInt8AsExpectedTypeForFromJsonStringWithType() retu string city; int:Unsigned8 id; } address; - } val4 = check fromJsonStringWithType(str4); + } val4 = check parseString(str4); test:assertEquals(val4.length(), 3); test:assertEquals(val4.id, 0); test:assertEquals(val4.name, "Anne"); @@ -1247,14 +1247,14 @@ isolated function testUnSignedInt8AsExpectedTypeForFromJsonStringWithType() retu } @test:Config -isolated function testUnSignedInt16AsExpectedTypeForFromJsonStringWithType() returns Error? { - int:Unsigned16 val1 = check fromJsonStringWithType("65535"); +isolated function testUnSignedInt16AsExpectedTypeForParseString() returns Error? { + int:Unsigned16 val1 = check parseString("65535"); test:assertEquals(val1, 65535); - int:Unsigned16 val2 = check fromJsonStringWithType("0"); + int:Unsigned16 val2 = check parseString("0"); test:assertEquals(val2, 0); - int:Unsigned16[] val3 = check fromJsonStringWithType("[0, 65535]"); + int:Unsigned16[] val3 = check parseString("[0, 65535]"); test:assertEquals(val3, [0, 65535]); string str4 = string `{ @@ -1275,7 +1275,7 @@ isolated function testUnSignedInt16AsExpectedTypeForFromJsonStringWithType() ret string city; int:Unsigned16 id; } address; - } val4 = check fromJsonStringWithType(str4); + } val4 = check parseString(str4); test:assertEquals(val4.length(), 3); test:assertEquals(val4.id, 0); test:assertEquals(val4.name, "Anne"); @@ -1286,14 +1286,14 @@ isolated function testUnSignedInt16AsExpectedTypeForFromJsonStringWithType() ret } @test:Config -isolated function testUnSignedInt32AsExpectedTypeForFromJsonStringWithType() returns Error? { - int:Unsigned32 val1 = check fromJsonStringWithType("4294967295"); +isolated function testUnSignedInt32AsExpectedTypeForParseString() returns Error? { + int:Unsigned32 val1 = check parseString("4294967295"); test:assertEquals(val1, 4294967295); - int:Unsigned32 val2 = check fromJsonStringWithType("0"); + int:Unsigned32 val2 = check parseString("0"); test:assertEquals(val2, 0); - int:Unsigned32[] val3 = check fromJsonStringWithType("[0, 4294967295]"); + int:Unsigned32[] val3 = check parseString("[0, 4294967295]"); test:assertEquals(val3, [0, 4294967295]); string str4 = string `{ @@ -1314,7 +1314,7 @@ isolated function testUnSignedInt32AsExpectedTypeForFromJsonStringWithType() ret string city; int:Unsigned32 id; } address; - } val4 = check fromJsonStringWithType(str4); + } val4 = check parseString(str4); test:assertEquals(val4.length(), 3); test:assertEquals(val4.id, 0); test:assertEquals(val4.name, "Anne"); @@ -1338,13 +1338,13 @@ ello", record {| string a; int b; - |} val = check fromJsonStringWithType(jsonStr); + |} val = check parseString(jsonStr); test:assertEquals(val.a, "h\nello"); test:assertEquals(val.b, 1); } @test:Config -isolated function testFromJsonStringWithTypeNegative1() returns Error? { +isolated function testParseStringNegative1() returns Error? { string str = string `{ "id": 12, "name": "Anne", @@ -1355,24 +1355,24 @@ isolated function testFromJsonStringWithTypeNegative1() returns Error? { } }`; - RN|Error x = fromJsonStringWithType(str); + RN|Error x = parseString(str); test:assertTrue(x is Error); test:assertEquals((x).message(), "incompatible value 'true' for type 'int' in field 'address.id'"); } @test:Config -isolated function testFromJsonStringWithTypeNegative2() returns Error? { +isolated function testParseStringNegative2() returns Error? { string str = string `{ "id": 12 }`; - RN2|Error x = fromJsonStringWithType(str); + RN2|Error x = parseString(str); test:assertTrue(x is Error); test:assertEquals((x).message(), "required field 'name' not present in JSON"); } @test:Config -isolated function testFromJsonStringWithTypeNegative3() returns Error? { +isolated function testParseStringNegative3() returns Error? { string str = string `{ "id": 12, "name": "Anne", @@ -1382,56 +1382,56 @@ isolated function testFromJsonStringWithTypeNegative3() returns Error? { } }`; - RN|Error x = fromJsonStringWithType(str); + RN|Error x = parseString(str); test:assertTrue(x is Error); test:assertEquals((x).message(), "required field 'id' not present in JSON"); } @test:Config -isolated function testFromJsonStringWithTypeNegative4() returns Error? { +isolated function testParseStringNegative4() returns Error? { string str = string `{ name: "John" }`; - int|Error x = fromJsonStringWithType(str); + int|Error x = parseString(str); test:assertTrue(x is Error); test:assertEquals((x).message(), "invalid type 'int' expected 'map type'"); - Union|Error y = fromJsonStringWithType(str); + Union|Error y = parseString(str); test:assertTrue(y is Error); test:assertEquals((y).message(), "unsupported type 'ballerina/data.jsondata:0:Union'"); - table|Error z = fromJsonStringWithType(str); + table|Error z = parseString(str); test:assertTrue(z is Error); test:assertEquals((z).message(), "unsupported type 'table'"); - RN2|Error a = fromJsonStringWithType("1"); + RN2|Error a = parseString("1"); test:assertTrue(a is Error); test:assertEquals((a).message(), "incompatible expected type 'data.jsondata:RN2' for value '1'"); } @test:Config -isolated function testDuplicateFieldInRecordTypeWithFromJsonStringWithType() returns Error? { +isolated function testDuplicateFieldInRecordTypeWithParseString() returns Error? { string str = string `{ "title": "Clean Code", "author": "Robert C. Martin", `; - BookN|Error x = fromJsonStringWithType(str); + BookN|Error x = parseString(str); test:assertTrue(x is Error); test:assertEquals((x).message(), "duplicate field 'author'"); } @test:Config -isolated function testProjectionInArrayNegativeForFromJsonStringWithType() { +isolated function testProjectionInArrayNegativeForParseString() { string strVal1 = string `["1", 2, 3, { "a" : val_a }]`; - int[]|Error val1 = fromJsonStringWithType(strVal1); + int[]|Error val1 = parseString(strVal1); test:assertTrue(val1 is Error); test:assertEquals((val1).message(), "invalid type 'int' expected 'map type'"); } @test:Config -isolated function testUnionTypeAsExpTypeForFromJsonStringWithTypeNegative() { +isolated function testUnionTypeAsExpTypeForParseStringNegative() { string str1 = string `[ 123, "Lakshan", @@ -1445,7 +1445,7 @@ isolated function testUnionTypeAsExpTypeForFromJsonStringWithTypeNegative() { "subject": "Bio" } ]`; - (map|int|float)[]|Error err1 = fromJsonStringWithType(str1); + (map|int|float)[]|Error err1 = parseString(str1); test:assertTrue(err1 is Error); test:assertEquals((err1).message(), "incompatible expected type '(map|int|float)' for value 'Lakshan'"); @@ -1460,7 +1460,7 @@ isolated function testUnionTypeAsExpTypeForFromJsonStringWithTypeNegative() { "subject": "Bio" } ]`; - (map|int|float)[]|Error err2 = fromJsonStringWithType(str2); + (map|int|float)[]|Error err2 = parseString(str2); test:assertTrue(err2 is Error); test:assertEquals((err2).message(), "unsupported type '(map|int|float)'"); @@ -1472,21 +1472,21 @@ isolated function testUnionTypeAsExpTypeForFromJsonStringWithTypeNegative() { "e": 2 } }`; - (map|int|float)|Error err3 = fromJsonStringWithType(str3); + (map|int|float)|Error err3 = parseString(str3); test:assertTrue(err3 is Error); test:assertEquals((err3).message(), "unsupported type '(map|int|float)'"); } @test:Config { - dataProvider: dataProviderForSubTypeOfIntNegativeTestForFromJsonStringWithType + dataProvider: dataProviderForSubTypeOfIntNegativeTestForParseString } isolated function testSubTypeOfIntAsExptypeNegative(string sourceData, typedesc expType, string expectedError) { - anydata|Error err = fromJsonStringWithType(sourceData, {}, expType); + anydata|Error err = parseString(sourceData, {}, expType); test:assertTrue(err is Error); test:assertEquals((err).message(), expectedError); } -function dataProviderForSubTypeOfIntNegativeTestForFromJsonStringWithType() returns [string, typedesc, string][] { +function dataProviderForSubTypeOfIntNegativeTestForParseString() returns [string, typedesc, string][] { string incompatibleStr = "incompatible expected type "; return [ ["256", byte, incompatibleStr + "'byte' for value '256'"], @@ -1508,13 +1508,13 @@ function dataProviderForSubTypeOfIntNegativeTestForFromJsonStringWithType() retu @test:Config isolated function testEmptyJsonDocumentNegative() { - string|Error err = fromJsonStringWithType(""); + string|Error err = parseString(""); test:assertTrue(err is Error); test:assertEquals((err).message(), "'empty JSON document' at line: '1' column: '1'"); } @test:Config -isolated function testRecordWithRestAsExpectedTypeForFromJsonStringWithTypeNegative() { +isolated function testRecordWithRestAsExpectedTypeForParseStringNegative() { string personStr = string ` { "id": 1, @@ -1525,7 +1525,7 @@ isolated function testRecordWithRestAsExpectedTypeForFromJsonStringWithTypeNegat } }`; - PersonA|error val = fromJsonStringWithType(personStr); + PersonA|error val = parseString(personStr); test:assertTrue(val is error); test:assertEquals((val).message(), "incompatible expected type 'int' for value '5.5'"); } @@ -1542,7 +1542,7 @@ function testComplexTypeAsUnionMemberAsExpTypeNegative() { "p2":true } ]`; - T1|error t1 = fromJsonStringWithType(str1); + T1|error t1 = parseString(str1); test:assertTrue(t1 is error); test:assertEquals((t1).message(), "unsupported type '(map|int|boolean)'"); @@ -1554,7 +1554,7 @@ function testComplexTypeAsUnionMemberAsExpTypeNegative() { "b": 2 } }`; - T2|error t2 = fromJsonStringWithType(str2); + T2|error t2 = parseString(str2); test:assertTrue(t2 is error); test:assertEquals((t2).message(), "unsupported type '(map|int)'"); } diff --git a/ballerina/tests/from_json_test.bal b/ballerina/tests/from_json_test.bal index 53b4e92..e663db9 100644 --- a/ballerina/tests/from_json_test.bal +++ b/ballerina/tests/from_json_test.bal @@ -17,14 +17,14 @@ import ballerina/test; @test:Config { - dataProvider: dataProviderForBasicTypeForFromJsonWithType + dataProvider: dataProviderForBasicTypeForParseAsType } isolated function testJsonToBasicTypes(json sourceData, typedesc expType, anydata expResult) returns Error? { - anydata result = check fromJsonWithType(sourceData, {}, expType); + anydata result = check parseAsType(sourceData, {}, expType); test:assertEquals(result, expResult); } -function dataProviderForBasicTypeForFromJsonWithType() returns [json, typedesc, anydata][] { +function dataProviderForBasicTypeForParseAsType() returns [json, typedesc, anydata][] { return [ [5, int, 5], [5.5, float, 5.5], @@ -42,11 +42,11 @@ function dataProviderForBasicTypeForFromJsonWithType() returns [json, typedescx).message(), "required field 'street' not present in JSON"); } @test:Config -isolated function testFromJsonWithType15() returns Error? { +isolated function testParseAsType15() returns Error? { json jsonContent = [1, 2, 3]; - IntArr x = check fromJsonWithType(jsonContent); + IntArr x = check parseAsType(jsonContent); test:assertEquals(x, [1, 2, 3]); } @test:Config -isolated function testFromJsonWithType16() returns Error? { +isolated function testParseAsType16() returns Error? { json jsonContent = [1, "abc", [3, 4.0]]; - Tuple|Error x = check fromJsonWithType(jsonContent); + Tuple|Error x = check parseAsType(jsonContent); test:assertEquals(x, [1, "abc", [3, 4.0]]); } @test:Config -isolated function testFromJsonWithType17() returns Error? { +isolated function testParseAsType17() returns Error? { json jsonContent = { "street": "Main", "city": { @@ -446,13 +446,13 @@ isolated function testFromJsonWithType17() returns Error? { "flag": true }; - TestJson x = check fromJsonWithType(jsonContent); + TestJson x = check parseAsType(jsonContent); test:assertEquals(x.street, "Main"); test:assertEquals(x.city, {"name": "Mahar", "code": 94, "internal": {"id": 12, "agent": "Anne"}}); } @test:Config -isolated function testFromJsonWithType18() returns Error? { +isolated function testParseAsType18() returns Error? { json jsonContent = { "books": [ { @@ -470,7 +470,7 @@ isolated function testFromJsonWithType18() returns Error? { ] }; - Library x = check fromJsonWithType(jsonContent); + Library x = check parseAsType(jsonContent); test:assertEquals(x.books.length(), 2); test:assertEquals(x.books[0].title, "The Great Gatsby"); test:assertEquals(x.books[0].author, "F. Scott Fitzgerald"); @@ -479,7 +479,7 @@ isolated function testFromJsonWithType18() returns Error? { } @test:Config -isolated function testFromJsonWithType19() returns Error? { +isolated function testParseAsType19() returns Error? { json jsonContent = { "books": [ { @@ -497,14 +497,14 @@ isolated function testFromJsonWithType19() returns Error? { ] }; - LibraryB x = check fromJsonWithType(jsonContent); + LibraryB x = check parseAsType(jsonContent); test:assertEquals(x.books.length(), 2); test:assertEquals(x.books[0].title, "The Great Gatsby"); test:assertEquals(x.books[0].author, "F. Scott Fitzgerald"); test:assertEquals(x.books[1].title, "The Grapes of Wrath"); test:assertEquals(x.books[1].author, "John Steinbeck"); - LibraryC y = check fromJsonWithType(jsonContent); + LibraryC y = check parseAsType(jsonContent); test:assertEquals(y.books.length(), 3); test:assertEquals(y.books[0].title, "The Great Gatsby"); test:assertEquals(y.books[0].author, "F. Scott Fitzgerald"); @@ -515,7 +515,7 @@ isolated function testFromJsonWithType19() returns Error? { } @test:Config -isolated function testFromJsonWithType20() returns Error? { +isolated function testParseAsType20() returns Error? { json jsonVal1 = { "a": { "c": "world", @@ -532,7 +532,7 @@ isolated function testFromJsonWithType20() returns Error? { string c; string d; |}...; - |} val1 = check fromJsonWithType(jsonVal1); + |} val1 = check parseAsType(jsonVal1); test:assertEquals(val1.length(), 2); test:assertEquals(val1["a"]["c"], "world"); test:assertEquals(val1["a"]["d"], "2"); @@ -541,7 +541,7 @@ isolated function testFromJsonWithType20() returns Error? { record {| map...; - |} val2 = check fromJsonWithType(jsonVal1); + |} val2 = check parseAsType(jsonVal1); test:assertEquals(val2.length(), 2); test:assertEquals(val2["a"]["c"], "world"); test:assertEquals(val2["a"]["d"], "2"); @@ -564,7 +564,7 @@ isolated function testFromJsonWithType20() returns Error? { string c; string d; |}[]...; - |} val3 = check fromJsonWithType(jsonVal3); + |} val3 = check parseAsType(jsonVal3); test:assertEquals(val3.length(), 2); test:assertEquals(val3["a"], [{ "c": "world", @@ -577,8 +577,8 @@ isolated function testFromJsonWithType20() returns Error? { } @test:Config -isolated function testUnionTypeAsExpTypeForFromJsonWithType() returns Error? { - decimal|float val1 = check fromJsonWithType(1.0); +isolated function testUnionTypeAsExpTypeForParseAsType() returns Error? { + decimal|float val1 = check parseAsType(1.0); test:assertEquals(val1, 1.0d); json jsonVal2 = { @@ -588,7 +588,7 @@ isolated function testUnionTypeAsExpTypeForFromJsonWithType() returns Error? { record {| decimal|float b; - |} val2 = check fromJsonWithType(jsonVal2); + |} val2 = check parseAsType(jsonVal2); test:assertEquals(val2.length(), 1); test:assertEquals(val2.b, 1.0d); @@ -605,7 +605,7 @@ isolated function testUnionTypeAsExpTypeForFromJsonWithType() returns Error? { record {| record {| int|decimal b; record {| string|boolean e; |} d; |} a; decimal|float c; - |} val3 = check fromJsonWithType(jsonVal3); + |} val3 = check parseAsType(jsonVal3); test:assertEquals(val3.length(), 2); test:assertEquals(val3.a.length(), 2); test:assertEquals(val3.a.b, 1); @@ -614,8 +614,8 @@ isolated function testUnionTypeAsExpTypeForFromJsonWithType() returns Error? { } @test:Config -isolated function testAnydataAsExpTypeForFromJsonWithType() returns Error? { - anydata val1 = check fromJsonWithType(1); +isolated function testAnydataAsExpTypeForParseAsType() returns Error? { + anydata val1 = check parseAsType(1); test:assertEquals(val1, 1); json jsonVal2 = { @@ -623,7 +623,7 @@ isolated function testAnydataAsExpTypeForFromJsonWithType() returns Error? { "b": 1 }; - anydata val2 = check fromJsonWithType(jsonVal2); + anydata val2 = check parseAsType(jsonVal2); test:assertEquals(val2, {"a": "hello", "b": 1}); record {| @@ -644,7 +644,7 @@ isolated function testAnydataAsExpTypeForFromJsonWithType() returns Error? { "c": 2 }; - anydata val3 = check fromJsonWithType(jsonVal3); + anydata val3 = check parseAsType(jsonVal3); test:assertEquals(val3, {"a": {"b": 1, "d": {"e": "hello"}}, "c": 2}); record {| @@ -665,17 +665,17 @@ isolated function testAnydataAsExpTypeForFromJsonWithType() returns Error? { "c": 2 }; - anydata val4 = check fromJsonWithType(jsonVal4); + anydata val4 = check parseAsType(jsonVal4); test:assertEquals(val4, {"a": [{"b": 1, "d": {"e": "hello"}}], "c": 2}); [[int], int] str5 = [[1], 2]; - anydata val5 = check fromJsonWithType(str5); + anydata val5 = check parseAsType(str5); test:assertEquals(val5, [[1], 2]); } @test:Config -isolated function testJsonAsExpTypeForFromJsonWithType() returns Error? { - json val1 = check fromJsonWithType(1); +isolated function testJsonAsExpTypeForParseAsType() returns Error? { + json val1 = check parseAsType(1); test:assertEquals(val1, 1); record {| @@ -686,7 +686,7 @@ isolated function testJsonAsExpTypeForFromJsonWithType() returns Error? { "b": 1 }; - json val2 = check fromJsonWithType(jsonVal2); + json val2 = check parseAsType(jsonVal2); test:assertEquals(val2, {"a": "hello", "b": 1}); record {| @@ -707,7 +707,7 @@ isolated function testJsonAsExpTypeForFromJsonWithType() returns Error? { "c": 2 }; - json val3 = check fromJsonWithType(jsonVal3); + json val3 = check parseAsType(jsonVal3); test:assertEquals(val3, {"a": {"b": 1, "d": {"e": "hello"}}, "c": 2}); record {| @@ -728,16 +728,16 @@ isolated function testJsonAsExpTypeForFromJsonWithType() returns Error? { "c": 2 }; - json val4 = check fromJsonWithType(jsonVal4); + json val4 = check parseAsType(jsonVal4); test:assertEquals(val4, {"a": [{"b": 1, "d": {"e": "hello"}}], "c": 2}); [[int], float] jsonVal5 = [[1], 2]; - json val5 = check fromJsonWithType(jsonVal5); + json val5 = check parseAsType(jsonVal5); test:assertEquals(val5, [[1], 2.0]); } @test:Config -isolated function testMapAsExpTypeForFromJsonWithType() returns Error? { +isolated function testMapAsExpTypeForParseAsType() returns Error? { record {| string a; string b; @@ -746,7 +746,7 @@ isolated function testMapAsExpTypeForFromJsonWithType() returns Error? { "b": "1" }; - map val1 = check fromJsonWithType(jsonVal1); + map val1 = check parseAsType(jsonVal1); test:assertEquals(val1, {"a": "hello", "b": "1"}); json jsonVal2 = { @@ -761,7 +761,7 @@ isolated function testMapAsExpTypeForFromJsonWithType() returns Error? { string a; int b; map c; - |} val2 = check fromJsonWithType(jsonVal2); + |} val2 = check parseAsType(jsonVal2); test:assertEquals(val2.a, "hello"); test:assertEquals(val2.b, 1); test:assertEquals(val2.c, {"d": "world", "e": "2"}); @@ -777,18 +777,18 @@ isolated function testMapAsExpTypeForFromJsonWithType() returns Error? { } }; - map> val3 = check fromJsonWithType(jsonVal3); + map> val3 = check parseAsType(jsonVal3); test:assertEquals(val3, {"a": {"c": "world", "d": "2"}, "b": {"c": "war", "d": "3"}}); record {| map a; - |} val4 = check fromJsonWithType(jsonVal3); + |} val4 = check parseAsType(jsonVal3); test:assertEquals(val4.a, {"c": "world", "d": "2"}); map val5 = check fromJsonWithType(jsonVal3); + |}> val5 = check parseAsType(jsonVal3); test:assertEquals(val5, {"a": {"c": "world", "d": "2"}, "b": {"c": "war", "d": "3"}}); json jsonVal6 = { @@ -807,15 +807,15 @@ isolated function testMapAsExpTypeForFromJsonWithType() returns Error? { record {| string a; map> b; - |} val6 = check fromJsonWithType(jsonVal6); + |} val6 = check parseAsType(jsonVal6); test:assertEquals(val6.a, "Kanth"); test:assertEquals(val6.b, {g: {c: "hello", d: "1"}, h: {c: "world", d: "2"}}); } @test:Config -isolated function testProjectionInTupleForFromJsonWithType() returns Error? { +isolated function testProjectionInTupleForParseAsType() returns Error? { float[] jsonVal1 = [1, 2, 3, 4, 5, 8]; - [float, float] val1 = check fromJsonWithType(jsonVal1); + [float, float] val1 = check parseAsType(jsonVal1); test:assertEquals(val1, [1.0, 2.0]); record {| @@ -823,25 +823,25 @@ isolated function testProjectionInTupleForFromJsonWithType() returns Error? { |} jsonVal2 = { "a": [1, 2, 3, 4, 5, 8] }; - record {| [float, float] a; |} val2 = check fromJsonWithType(jsonVal2); + record {| [float, float] a; |} val2 = check parseAsType(jsonVal2); test:assertEquals(val2.a, [1.0, 2.0]); [int, string] str3 = [1, "4"]; - [int] val3 = check fromJsonWithType(str3); + [int] val3 = check parseAsType(str3); test:assertEquals(val3, [1]); [string, record {|json...;|}] jsonVal4 = ["1", {}]; - [string] val4 = check fromJsonWithType(jsonVal4); + [string] val4 = check parseAsType(jsonVal4); test:assertEquals(val4, ["1"]); [string, int[], map] jsonVal5 = ["1", [], {"name": 1}]; - [string] val5 = check fromJsonWithType(jsonVal5); + [string] val5 = check parseAsType(jsonVal5); test:assertEquals(val5, ["1"]); } @test:Config -isolated function testProjectionInArrayForFromJsonWithType() returns Error? { - int[2] val1 = check fromJsonWithType([1, 2, 3, 4, 5]); +isolated function testProjectionInArrayForParseAsType() returns Error? { + int[2] val1 = check parseAsType([1, 2, 3, 4, 5]); test:assertEquals(val1, [1, 2]); record {| @@ -849,14 +849,14 @@ isolated function testProjectionInArrayForFromJsonWithType() returns Error? { |} jsonVal2 = { "a": [1, 2, 3, 4, 5] }; - record {| int[2] a; |} val2 = check fromJsonWithType(jsonVal2); + record {| int[2] a; |} val2 = check parseAsType(jsonVal2); test:assertEquals(val2, {a: [1, 2]}); json jsonVal3 = { "a": [1, 2, 3, 4, 5], "b": [1, 2, 3, 4, 5] }; - record {| int[2] a; int[3] b; |} val3 = check fromJsonWithType(jsonVal3); + record {| int[2] a; int[3] b; |} val3 = check parseAsType(jsonVal3); test:assertEquals(val3, {a: [1, 2], b: [1, 2, 3]}); json jsonVal4 = { @@ -869,22 +869,22 @@ isolated function testProjectionInArrayForFromJsonWithType() returns Error? { } ] }; - record {| record {| string name; int age; |}[1] employees; |} val4 = check fromJsonWithType(jsonVal4); + record {| record {| string name; int age; |}[1] employees; |} val4 = check parseAsType(jsonVal4); test:assertEquals(val4, {employees: [{name: "Prakanth", age: 26}]}); [int, int, int, record {|int a;|}] jsonVal5 = [1, 2, 3, { a : 2 }]; - int[2] val5 = check fromJsonWithType(jsonVal5); + int[2] val5 = check parseAsType(jsonVal5); test:assertEquals(val5, [1, 2]); } @test:Config -isolated function testProjectionInRecordForFromJsonWithType() returns Error? { +isolated function testProjectionInRecordForParseAsType() returns Error? { json jsonVal1 = {"name": "John", "age": 30, "city": "New York"}; - record {| string name; string city; |} val1 = check fromJsonWithType(jsonVal1); + record {| string name; string city; |} val1 = check parseAsType(jsonVal1); test:assertEquals(val1, {name: "John", city: "New York"}); json jsonVal2 = {"name": "John", "age": "30", "city": "New York"}; - record {| string name; string city; |} val2 = check fromJsonWithType(jsonVal2); + record {| string name; string city; |} val2 = check parseAsType(jsonVal2); test:assertEquals(val2, {name: "John", city: "New York"}); json jsonVal3 = { "name": "John", @@ -897,7 +897,7 @@ isolated function testProjectionInRecordForFromJsonWithType() returns Error? { } }, "city": "New York" }; - record {| string name; string city; |} val3 = check fromJsonWithType(jsonVal3); + record {| string name; string city; |} val3 = check parseAsType(jsonVal3); test:assertEquals(val3, {name: "John", city: "New York"}); json jsonVal4 = { "name": "John", @@ -910,7 +910,7 @@ isolated function testProjectionInRecordForFromJsonWithType() returns Error? { } }], "city": "New York" }; - record {| string name; string city; |} val4 = check fromJsonWithType(jsonVal4); + record {| string name; string city; |} val4 = check parseAsType(jsonVal4); test:assertEquals(val4, {name: "John", city: "New York"}); json jsonVal5 = { "name": "John", @@ -932,86 +932,86 @@ isolated function testProjectionInRecordForFromJsonWithType() returns Error? { } }] }; - record {| string name; string city; |} val5 = check fromJsonWithType(jsonVal5); + record {| string name; string city; |} val5 = check parseAsType(jsonVal5); test:assertEquals(val5, {name: "John", city: "New York"}); } @test:Config -isolated function testArrayOrTupleCaseForFromJsonWithType() returns Error? { +isolated function testArrayOrTupleCaseForParseAsType() returns Error? { json jsonVal1 = [[1], 2.0]; - [[int], float] val1 = check fromJsonWithType(jsonVal1); + [[int], float] val1 = check parseAsType(jsonVal1); test:assertEquals(val1, [[1], 2.0]); json jsonVal2 = [[1, 2], 2.0]; - [[int, int], float] val2 = check fromJsonWithType(jsonVal2); + [[int, int], float] val2 = check parseAsType(jsonVal2); test:assertEquals(val2, [[1, 2], 2.0]); json jsonStr3 = [[1, 2], [2, 3]]; - int[][] val3 = check fromJsonWithType(jsonStr3); + int[][] val3 = check parseAsType(jsonStr3); test:assertEquals(val3, [[1, 2], [2, 3]]); json jsonVal4 = {"val" : [[1, 2], "2.0", 3.0, [5, 6]]}; record {| [[int, int], string, float, [int, int]] val; - |} val4 = check fromJsonWithType(jsonVal4); + |} val4 = check parseAsType(jsonVal4); test:assertEquals(val4, {val: [[1, 2], "2.0", 3.0, [5, 6]]}); json jsonVal41 = {"val1" : [[1, 2], "2.0", 3.0, [5, 6]], "val2" : [[1, 2], "2.0", 3.0, [5, 6]]}; record {| [[int, int], string, float, [int, int]] val1; [[int, int], string, float, [int, int]] val2; - |} val41 = check fromJsonWithType(jsonVal41); + |} val41 = check parseAsType(jsonVal41); test:assertEquals(val41, {val1: [[1, 2], "2.0", 3.0, [5, 6]], val2: [[1, 2], "2.0", 3.0, [5, 6]]}); json jsonVal5 = {"val" : [[1, 2], [2, 3]]}; record {| int[][] val; - |} val5 = check fromJsonWithType(jsonVal5); + |} val5 = check parseAsType(jsonVal5); test:assertEquals(val5, {val: [[1, 2], [2, 3]]}); json jsonVal6 = [{"val" : [[1, 2], [2, 3]]}]; - [record {|int[][] val;|}] val6 = check fromJsonWithType(jsonVal6); + [record {|int[][] val;|}] val6 = check parseAsType(jsonVal6); test:assertEquals(val6, [{val: [[1, 2], [2, 3]]}]); } @test:Config -isolated function testListFillerValuesWithFromJsonWithType() returns Error? { - int[2] jsonVal1 = check fromJsonWithType([1]); +isolated function testListFillerValuesWithParseAsType() returns Error? { + int[2] jsonVal1 = check parseAsType([1]); test:assertEquals(jsonVal1, [1, 0]); - [int, float, string, boolean] jsonVal2 = check fromJsonWithType([1]); + [int, float, string, boolean] jsonVal2 = check parseAsType([1]); test:assertEquals(jsonVal2, [1, 0.0, "", false]); record {| float[3] A; [int, decimal, float, boolean] B; - |} jsonVal3 = check fromJsonWithType({A: [1], B: [1]}); + |} jsonVal3 = check parseAsType({A: [1], B: [1]}); test:assertEquals(jsonVal3, {A: [1.0, 0.0, 0.0], B: [1, 0d, 0.0, false]}); } @test:Config -isolated function testNameAnnotationWithFromJsonWithType() returns Error? { +isolated function testNameAnnotationWithParseAsType() returns Error? { json jsonContent = { "id": 1, "title-name": "Harry Potter", "author-name": "J.K. Rowling" }; - Book2 book = check fromJsonWithType(jsonContent); + Book2 book = check parseAsType(jsonContent); test:assertEquals(book.id, 1); test:assertEquals(book.title, "Harry Potter"); test:assertEquals(book.author, "J.K. Rowling"); } @test:Config { - dataProvider: dataProviderForSubTypeIntPostiveCasesWithFromJsonWithType + dataProvider: dataProviderForSubTypeIntPostiveCasesWithParseAsType } -isolated function testSubTypeOfIntAsExpectedTypeWithFromJsonWithType(json sourceData, typedesc expType, anydata expectedResult) returns Error? { - anydata val = check fromJsonWithType(sourceData, {}, expType); +isolated function testSubTypeOfIntAsExpectedTypeWithParseAsType(json sourceData, typedesc expType, anydata expectedResult) returns Error? { + anydata val = check parseAsType(sourceData, {}, expType); test:assertEquals(val, expectedResult); } -function dataProviderForSubTypeIntPostiveCasesWithFromJsonWithType() returns [json, typedesc, anydata][] { +function dataProviderForSubTypeIntPostiveCasesWithParseAsType() returns [json, typedesc, anydata][] { return [ [255, byte, 255], [255, int:Unsigned8, 255], @@ -1032,7 +1032,7 @@ function dataProviderForSubTypeIntPostiveCasesWithFromJsonWithType() returns [js } @test:Config -isolated function testSubTypeOfIntAsFieldTypeForFromJsonWithType() returns error? { +isolated function testSubTypeOfIntAsFieldTypeForParseAsType() returns error? { json jsonVal4 = { "a": 1, "b": 127, @@ -1050,35 +1050,35 @@ isolated function testSubTypeOfIntAsFieldTypeForFromJsonWithType() returns error int:Unsigned8 e; int:Unsigned16 f; int:Unsigned32 g; - |} val16 = check fromJsonWithType(jsonVal4); + |} val16 = check parseAsType(jsonVal4); test:assertEquals(val16, {a: 1, b: 127, c: 32767, d: 2147483647, e: 255, f: 32767, g: 2147483647}); } @test:Config -isolated function testSingletonAsExpectedTypeForFromJsonWithType() returns Error? { - "1" val1 = check fromJsonWithType("1"); +isolated function testSingletonAsExpectedTypeForParseAsType() returns Error? { + "1" val1 = check parseAsType("1"); test:assertEquals(val1, "1"); - Singleton1 val2 = check fromJsonWithType(1); + Singleton1 val2 = check parseAsType(1); test:assertEquals(val2, 1); - SingletonUnion val3 = check fromJsonWithType(2); + SingletonUnion val3 = check parseAsType(2); test:assertEquals(val3, 2); - () val4 = check fromJsonWithType(null); + () val4 = check parseAsType(null); test:assertEquals(val4, ()); json jsonContent = { value: 1, id: "3" }; - SingletonInRecord val5 = check fromJsonWithType(jsonContent); + SingletonInRecord val5 = check parseAsType(jsonContent); test:assertEquals(val5.id, "3"); test:assertEquals(val5.value, 1); } @test:Config -isolated function testFromJsonWithTypeNegative1() returns Error? { +isolated function testParseAsTypeNegative1() returns Error? { json jsonContent = { "id": 12, "name": "Anne", @@ -1089,24 +1089,24 @@ isolated function testFromJsonWithTypeNegative1() returns Error? { } }; - RN|Error x = fromJsonWithType(jsonContent); + RN|Error x = parseAsType(jsonContent); test:assertTrue(x is Error); test:assertEquals((x).message(), "incompatible value 'true' for type 'int' in field 'address.id'"); } @test:Config -isolated function testFromJsonWithTypeNegative2() returns Error? { +isolated function testParseAsTypeNegative2() returns Error? { json jsonContent = { "id": 12 }; - RN2|Error x = fromJsonWithType(jsonContent); + RN2|Error x = parseAsType(jsonContent); test:assertTrue(x is Error); test:assertEquals((x).message(), "required field 'name' not present in JSON"); } @test:Config -isolated function testFromJsonWithTypeNegative3() returns Error? { +isolated function testParseAsTypeNegative3() returns Error? { json jsonContent = { "id": 12, "name": "Anne", @@ -1116,81 +1116,81 @@ isolated function testFromJsonWithTypeNegative3() returns Error? { } }; - RN|Error x = fromJsonWithType(jsonContent); + RN|Error x = parseAsType(jsonContent); test:assertTrue(x is Error); test:assertEquals((x).message(), "required field 'id' not present in JSON"); } @test:Config -isolated function testFromJsonWithTypeNegative4() returns Error? { +isolated function testParseAsTypeNegative4() returns Error? { json jsonContent = { name: "John" }; - int|Error x = fromJsonWithType(jsonContent); + int|Error x = parseAsType(jsonContent); test:assertTrue(x is Error); test:assertEquals((x).message(), "incompatible expected type 'int' for value '{\"name\":\"John\"}'"); - Union|Error y = fromJsonWithType(jsonContent); + Union|Error y = parseAsType(jsonContent); test:assertTrue(y is Error); test:assertEquals((y).message(), "invalid type 'data.jsondata:Union' expected 'anydata'"); - table|Error z = fromJsonWithType(jsonContent); + table|Error z = parseAsType(jsonContent); test:assertTrue(z is Error); test:assertEquals((z).message(), "invalid type 'table' expected 'anydata'"); - RN2|Error a = fromJsonWithType("1"); + RN2|Error a = parseAsType("1"); test:assertTrue(a is Error); test:assertEquals((a).message(), "incompatible expected type 'data.jsondata:RN2' for value '1'"); - string|Error b = fromJsonWithType(1); + string|Error b = parseAsType(1); test:assertTrue(b is Error); test:assertEquals((b).message(), "incompatible expected type 'string' for value '1'"); } @test:Config -isolated function testFromJsonWithTypeNegative6() { +isolated function testParseAsTypeNegative6() { json jsonContent = { "street": "Main", "city": "Mahar", "house": [94, [1, 3, "4"]] }; - TestArr3|Error x = fromJsonWithType(jsonContent); + TestArr3|Error x = parseAsType(jsonContent); test:assertTrue(x is Error); test:assertEquals((x).message(), "incompatible value '4' for type 'int' in field 'house'"); } @test:Config -isolated function testDuplicateFieldInRecordTypeWithFromJsonWithType() returns Error? { +isolated function testDuplicateFieldInRecordTypeWithParseAsType() returns Error? { json jsonContent = string `{ "title": "Clean Code", "author": "Robert C. Martin", `; - BookN|Error x = fromJsonWithType(jsonContent); + BookN|Error x = parseAsType(jsonContent); test:assertTrue(x is Error); test:assertEquals((x).message(), "duplicate field 'author'"); } @test:Config -isolated function testProjectionInArrayNegativeForFromJsonWithType() { +isolated function testProjectionInArrayNegativeForParseAsType() { [int, int, int, record {|int a;|}] jsonVal5 = [1, 2, 3, { a : 2 }]; - int[]|Error val5 = fromJsonWithType(jsonVal5); + int[]|Error val5 = parseAsType(jsonVal5); test:assertTrue(val5 is Error); test:assertEquals((val5).message(), "incompatible expected type 'int' for value '{\"a\":2}'"); } @test:Config { - dataProvider: dataProviderForSubTypeOfIntNegativeTestForFromJsonWithType + dataProvider: dataProviderForSubTypeOfIntNegativeTestForParseAsType } -isolated function testSubTypeOfIntAsExptypeWithFromJsonWithTypeNegative(json sourceData, typedesc expType, string expectedError) { - anydata|Error result = fromJsonWithType(sourceData, {}, expType); +isolated function testSubTypeOfIntAsExptypeWithParseAsTypeNegative(json sourceData, typedesc expType, string expectedError) { + anydata|Error result = parseAsType(sourceData, {}, expType); test:assertTrue(result is Error); test:assertEquals((result).message(), expectedError); } -function dataProviderForSubTypeOfIntNegativeTestForFromJsonWithType() returns [json, typedesc, string][] { +function dataProviderForSubTypeOfIntNegativeTestForParseAsType() returns [json, typedesc, string][] { string incompatibleStr = "incompatible expected type "; return [ [256, byte, incompatibleStr + "'byte' for value '256'"], @@ -1211,7 +1211,7 @@ function dataProviderForSubTypeOfIntNegativeTestForFromJsonWithType() returns [j } @test:Config -isolated function testRecordWithRestAsExpectedTypeForFromJsonWithTypeNegative() { +isolated function testRecordWithRestAsExpectedTypeForParseAsTypeNegative() { json jsonVal = { id: 1, name: "Anne", @@ -1222,7 +1222,7 @@ isolated function testRecordWithRestAsExpectedTypeForFromJsonWithTypeNegative() } }; - PersonA|error val = fromJsonWithType(jsonVal); + PersonA|error val = parseAsType(jsonVal); test:assertTrue(val is error); test:assertEquals((val).message(), "incompatible value '7' for type 'int' in field 'measurements'"); } diff --git a/ballerina/tests/from_json_with_options.bal b/ballerina/tests/from_json_with_options.bal index 01ecce1..fbb56ac 100644 --- a/ballerina/tests/from_json_with_options.bal +++ b/ballerina/tests/from_json_with_options.bal @@ -21,16 +21,16 @@ const options = { }; @test:Config -isolated function testDisableDataProjectionInArrayTypeForFromJsonStringWithType() { +isolated function testDisableDataProjectionInArrayTypeForParseString() { string jsonStr1 = string `[1, 2, 3, 4]`; - int[2]|error val1 = fromJsonStringWithType(jsonStr1, options); + int[2]|error val1 = parseString(jsonStr1, options); test:assertTrue(val1 is error); test:assertEquals((val1).message(), "array size is not compatible with the expected size"); string strVal2 = string `{ "a": [1, 2, 3, 4, 5] }`; - record {|int[2] a;|}|error val2 = fromJsonStringWithType(strVal2, options); + record {|int[2] a;|}|error val2 = parseString(strVal2, options); test:assertTrue(val2 is error); test:assertEquals((val2).message(), "array size is not compatible with the expected size"); @@ -38,7 +38,7 @@ isolated function testDisableDataProjectionInArrayTypeForFromJsonStringWithType( "a": [1, 2, 3, 4, 5], "b": [1, 2, 3, 4, 5] }`; - record {|int[2] a; int[3] b;|}|error val3 = fromJsonStringWithType(strVal3, options); + record {|int[2] a; int[3] b;|}|error val3 = parseString(strVal3, options); test:assertTrue(val3 is error); test:assertEquals((val3).message(), "array size is not compatible with the expected size"); @@ -52,55 +52,55 @@ isolated function testDisableDataProjectionInArrayTypeForFromJsonStringWithType( } ] }`; - record {|record {|string name; int age;|}[1] employees;|}|error val4 = fromJsonStringWithType(strVal4, options); + record {|record {|string name; int age;|}[1] employees;|}|error val4 = parseString(strVal4, options); test:assertTrue(val4 is error); test:assertEquals((val4).message(), "array size is not compatible with the expected size"); string strVal5 = string `["1", 2, 3, { "a" : val_a }]`; - int[3]|error val5 = fromJsonStringWithType(strVal5, options); + int[3]|error val5 = parseString(strVal5, options); test:assertTrue(val5 is error); test:assertEquals((val5).message(), "array size is not compatible with the expected size"); } @test:Config -isolated function testDisableDataProjectionInTupleTypeForFromJsonStringWithType() { +isolated function testDisableDataProjectionInTupleTypeForParseString() { string str1 = string `[1, 2, 3, 4, 5, 8]`; - [string, float]|error val1 = fromJsonStringWithType(str1, options); + [string, float]|error val1 = parseString(str1, options); test:assertTrue(val1 is error); test:assertEquals((val1).message(), "array size is not compatible with the expected size"); string str2 = string `{ "a": [1, 2, 3, 4, 5, 8] }`; - record {|[string, float] a;|}|error val2 = fromJsonStringWithType(str2, options); + record {|[string, float] a;|}|error val2 = parseString(str2, options); test:assertTrue(val2 is error); test:assertEquals((val2).message(), "array size is not compatible with the expected size"); string str3 = string `[1, "4"]`; - [float]|error val3 = fromJsonStringWithType(str3, options); + [float]|error val3 = parseString(str3, options); test:assertTrue(val3 is error); test:assertEquals((val3).message(), "array size is not compatible with the expected size"); string str4 = string `["1", {}]`; - [float]|error val4 = fromJsonStringWithType(str4, options); + [float]|error val4 = parseString(str4, options); test:assertTrue(val4 is error); test:assertEquals((val4).message(), "array size is not compatible with the expected size"); string str5 = string `["1", [], {"name": 1}]`; - [float]|error val5 = fromJsonStringWithType(str5, options); + [float]|error val5 = parseString(str5, options); test:assertTrue(val5 is error); test:assertEquals((val5).message(), "array size is not compatible with the expected size"); } @test:Config -isolated function testDisableDataProjectionInRecordTypeWithFromJsonStringWithType() { +isolated function testDisableDataProjectionInRecordTypeWithParseString() { string jsonStr1 = string `{"name": "John", "age": 30, "city": "New York"}`; - record {|string name; string city;|}|error val1 = fromJsonStringWithType(jsonStr1, options); + record {|string name; string city;|}|error val1 = parseString(jsonStr1, options); test:assertTrue(val1 is error); test:assertEquals((val1).message(), "undefined field 'age'"); string jsonStr2 = string `{"name": John, "age": "30", "city": "New York"}`; - record {|string name; string city;|}|error val2 = fromJsonStringWithType(jsonStr2, options); + record {|string name; string city;|}|error val2 = parseString(jsonStr2, options); test:assertTrue(val2 is error); test:assertEquals((val2).message(), "undefined field 'age'"); @@ -114,7 +114,7 @@ isolated function testDisableDataProjectionInRecordTypeWithFromJsonStringWithTyp } }, "city": "New York" }`; - record {|string name; string city;|}|error val3 = fromJsonStringWithType(jsonStr3, options); + record {|string name; string city;|}|error val3 = parseString(jsonStr3, options); test:assertTrue(val3 is error); test:assertEquals((val3).message(), "undefined field 'company'"); @@ -128,7 +128,7 @@ isolated function testDisableDataProjectionInRecordTypeWithFromJsonStringWithTyp } }], "city": "New York" }`; - record {|string name; string city;|}|error val4 = fromJsonStringWithType(jsonStr4, options); + record {|string name; string city;|}|error val4 = parseString(jsonStr4, options); test:assertTrue(val4 is error); test:assertEquals((val4).message(), "undefined field 'company'"); @@ -151,22 +151,22 @@ isolated function testDisableDataProjectionInRecordTypeWithFromJsonStringWithTyp } }] }`; - record {|string name; string city;|}|error val5 = fromJsonStringWithType(jsonStr5, options); + record {|string name; string city;|}|error val5 = parseString(jsonStr5, options); test:assertTrue(val5 is error); test:assertEquals((val5).message(), "undefined field 'company1'"); } @test:Config -isolated function testDisableDataProjectionInArrayTypeForFromJsonWithType() { +isolated function testDisableDataProjectionInArrayTypeForParseAsType() { json jsonVal1 = [1, 2, 3, 4]; - int[2]|error val1 = fromJsonWithType(jsonVal1, options); + int[2]|error val1 = parseAsType(jsonVal1, options); test:assertTrue(val1 is error); test:assertEquals((val1).message(), "array size is not compatible with the expected size"); json jsonVal2 = { a: [1, 2, 3, 4, 5] }; - record {|int[2] a;|}|error val2 = fromJsonWithType(jsonVal2, options); + record {|int[2] a;|}|error val2 = parseAsType(jsonVal2, options); test:assertTrue(val2 is error); test:assertEquals((val2).message(), "array size is not compatible with the expected size"); @@ -174,7 +174,7 @@ isolated function testDisableDataProjectionInArrayTypeForFromJsonWithType() { a: [1, 2, 3, 4, 5], b: [1, 2, 3, 4, 5] }; - record {|int[2] a; int[3] b;|}|error val3 = fromJsonWithType(jsonVal3, options); + record {|int[2] a; int[3] b;|}|error val3 = parseAsType(jsonVal3, options); test:assertTrue(val3 is error); test:assertEquals((val3).message(), "array size is not compatible with the expected size"); @@ -190,50 +190,50 @@ isolated function testDisableDataProjectionInArrayTypeForFromJsonWithType() { } ] }; - record {|record {|string name; int age;|}[1] employees;|}|error val4 = fromJsonWithType(jsonVal4, options); + record {|record {|string name; int age;|}[1] employees;|}|error val4 = parseAsType(jsonVal4, options); test:assertTrue(val4 is error); test:assertEquals((val4).message(), "array size is not compatible with the expected size"); json jsonVal5 = ["1", 2, 3, {a: "val_a"}]; - int[3]|error val5 = fromJsonWithType(jsonVal5, options); + int[3]|error val5 = parseAsType(jsonVal5, options); test:assertTrue(val5 is error); test:assertEquals((val5).message(), "array size is not compatible with the expected size"); } @test:Config -isolated function testDisableDataProjectionInTupleTypeForFromJsonWithType() { +isolated function testDisableDataProjectionInTupleTypeForParseAsType() { json jsonVal1 = [1, 2, 3, 4, 5, 8]; - [int, int]|error val1 = fromJsonWithType(jsonVal1, options); + [int, int]|error val1 = parseAsType(jsonVal1, options); test:assertTrue(val1 is error); test:assertEquals((val1).message(), "array size is not compatible with the expected size"); json jsonVal2 = { a: [1, 2, 3, 4, 5, 8] }; - record {|[int, int] a;|}|error val2 = fromJsonWithType(jsonVal2, options); + record {|[int, int] a;|}|error val2 = parseAsType(jsonVal2, options); test:assertTrue(val2 is error); test:assertEquals((val2).message(), "array size is not compatible with the expected size"); json jsonVal3 = [1, "4"]; - [int]|error val3 = fromJsonWithType(jsonVal3, options); + [int]|error val3 = parseAsType(jsonVal3, options); test:assertTrue(val3 is error); test:assertEquals((val3).message(), "array size is not compatible with the expected size"); json jsonVal4 = ["1", {}]; - [string]|error val4 = fromJsonWithType(jsonVal4, options); + [string]|error val4 = parseAsType(jsonVal4, options); test:assertTrue(val4 is error); test:assertEquals((val4).message(), "array size is not compatible with the expected size"); json jsonVal5 = ["1", [], {"name": 1}]; - [string]|error val5 = fromJsonWithType(jsonVal5, options); + [string]|error val5 = parseAsType(jsonVal5, options); test:assertTrue(val5 is error); test:assertEquals((val5).message(), "array size is not compatible with the expected size"); } @test:Config -isolated function testDisableDataProjectionInRecordTypeWithFromJsonWithType() { +isolated function testDisableDataProjectionInRecordTypeWithParseAsType() { json jsonVal1 = {"name": "John", "age": 30, "city": "New York"}; - record {|string name; string city;|}|error val1 = fromJsonWithType(jsonVal1, options); + record {|string name; string city;|}|error val1 = parseAsType(jsonVal1, options); test:assertTrue(val1 is error); test:assertEquals((val1).message(), "undefined field 'age'"); @@ -249,7 +249,7 @@ isolated function testDisableDataProjectionInRecordTypeWithFromJsonWithType() { }, "city": "New York" }; - record {|string name; string city;|}|error val2 = fromJsonWithType(jsonVal2, options); + record {|string name; string city;|}|error val2 = parseAsType(jsonVal2, options); test:assertTrue(val2 is error); test:assertEquals((val2).message(), "undefined field 'company'"); @@ -267,7 +267,7 @@ isolated function testDisableDataProjectionInRecordTypeWithFromJsonWithType() { ], "city": "New York" }; - record {|string name; string city;|}|error val3 = fromJsonWithType(jsonVal3, options); + record {|string name; string city;|}|error val3 = parseAsType(jsonVal3, options); test:assertTrue(val3 is error); test:assertEquals((val3).message(), "undefined field 'company'"); @@ -295,7 +295,7 @@ isolated function testDisableDataProjectionInRecordTypeWithFromJsonWithType() { } ] }; - record {|string name; string city;|}|error val4 = fromJsonWithType(jsonVal4, options); + record {|string name; string city;|}|error val4 = parseAsType(jsonVal4, options); test:assertTrue(val4 is error); test:assertEquals((val4).message(), "undefined field 'company1'"); } diff --git a/ballerina/tests/readonly_intersection_expected_type_test.bal b/ballerina/tests/readonly_intersection_expected_type_test.bal index c797fac..4ca2ede 100644 --- a/ballerina/tests/readonly_intersection_expected_type_test.bal +++ b/ballerina/tests/readonly_intersection_expected_type_test.bal @@ -207,15 +207,15 @@ ExpectedTuple expectedResults = [ ]; @test:Config { - dataProvider: readonlyIntersectionTestDataForFromJsonStringWithType + dataProvider: readonlyIntersectionTestDataForParseString } -isolated function testReadOnlyIntersectionTypeAsExpTypForFromJsonStringWithType(string sourceData, +isolated function testReadOnlyIntersectionTypeAsExpTypForParseString(string sourceData, typedesc expType, anydata expectedData) returns error? { - anydata result = check fromJsonStringWithType(sourceData, {}, expType); + anydata result = check parseString(sourceData, {}, expType); test:assertEquals(result, expectedData); } -function readonlyIntersectionTestDataForFromJsonStringWithType() returns [string, typedesc, anydata][] { +function readonlyIntersectionTestDataForParseString() returns [string, typedesc, anydata][] { return [ [string `[1, 2, 3]`, intArrayReadonly, expectedResults[0]], ["[12, true, 123.4, \"hello\"]", type1Readonly, expectedResults[1]], @@ -258,15 +258,15 @@ function readonlyIntersectionTestDataForFromJsonStringWithType() returns [string } @test:Config { - dataProvider: readonlyIntersectionTestDataForFromJsonWithType + dataProvider: readonlyIntersectionTestDataForParseAsType } -isolated function testReadOnlyIntersectionTypeAsExpTypForFromJsonWithType(json sourceData, +isolated function testReadOnlyIntersectionTypeAsExpTypForParseAsType(json sourceData, typedesc expType, anydata expectedData) returns error? { - anydata result = check fromJsonWithType(sourceData, {}, expType); + anydata result = check parseAsType(sourceData, {}, expType); test:assertEquals(result, expectedData); } -function readonlyIntersectionTestDataForFromJsonWithType() returns [json, typedesc, anydata][] { +function readonlyIntersectionTestDataForParseAsType() returns [json, typedesc, anydata][] { return [ [[1, 2, 3], intArrayReadonly, expectedResults[0]], [[12, true, 123.4, "hello"], type1Readonly, expectedResults[1]], diff --git a/ballerina/tests/stream_large_file_test.bal b/ballerina/tests/stream_large_file_test.bal index 52a96bc..f7314fb 100644 --- a/ballerina/tests/stream_large_file_test.bal +++ b/ballerina/tests/stream_large_file_test.bal @@ -81,7 +81,7 @@ function createLargeFile() returns error? { @test:Config function testLargeFileStream() returns error? { stream dataStream = check io:fileReadBlocksAsStream(LARGE_JSON_FILE); - CompanyR1 company = check fromJsonStringWithType(dataStream); + CompanyR1 company = check parseStream(dataStream); test:assertEquals(company.employees.length(), 1001); test:assertEquals(company.customers.length(), 1001); @@ -118,7 +118,7 @@ function testLargeFileStreamWithProjection() returns error? { record {| EmployeeR2[5] employees; CustomerR2[9] customers; - |} company = check fromJsonStringWithType(dataStream); + |} company = check parseStream(dataStream); test:assertEquals(company.employees.length(), 5); test:assertEquals(company.customers.length(), 9); diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_1/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_1/sample.bal index a091071..1b525ab 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_1/sample.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_1/sample.bal @@ -1,5 +1,5 @@ import ballerina/data.jsondata; public function main() returns error? { - int|record {| int a;|}|record {| int b;|} val = check jsondata:fromJsonStringWithType("1"); + int|record {| int a;|}|record {| int b;|} val = check jsondata:parseString("1"); } diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_2/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_2/sample.bal index b60447d..e4ea557 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_2/sample.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_2/sample.bal @@ -3,5 +3,5 @@ import ballerina/data.jsondata; type Union int|record {| int a;|}|record {| int b;|}; public function main() returns error? { - Union val = check jsondata:fromJsonStringWithType("1"); + Union val = check jsondata:parseString("1"); } diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/sample.bal index b82566e..c080f6b 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/sample.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/sample.bal @@ -19,5 +19,5 @@ public function main() returns error? { "country": "USA" } }`; - Person _ = check jsondata:fromJsonStringWithType(str); + Person _ = check jsondata:parseString(str); } diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/sample.bal index c7d5d0e..304e361 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/sample.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/sample.bal @@ -17,4 +17,4 @@ string str = string `{ "country": "USA" } }`; -Person _ = check jsondata:fromJsonStringWithType(str); +Person _ = check jsondata:parseString(str); diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_6/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_6/sample.bal index 29fea39..933b17c 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_6/sample.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_6/sample.bal @@ -7,7 +7,7 @@ public function main() returns error? { } string A; string B; - } _ = check jsondata:fromJsonWithType({ + } _ = check jsondata:parseAsType({ "A": "Hello", "B": "World" }); diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/sample.bal index 4088a26..853e06a 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/sample.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/sample.bal @@ -17,7 +17,7 @@ public function main() returns error? { "p2":true } ]`; - T1 _ = check jsondata:fromJsonStringWithType(str1); + T1 _ = check jsondata:parseString(str1); string str2 = string ` { @@ -27,5 +27,5 @@ public function main() returns error? { "b": 2 } }`; - T2 _ = check jsondata:fromJsonStringWithType(str2); + T2 _ = check jsondata:parseString(str2); } diff --git a/compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/Constants.java b/compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/Constants.java index 3103eb1..9478613 100644 --- a/compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/Constants.java +++ b/compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/Constants.java @@ -24,7 +24,9 @@ * @since 0.1.0 */ public class Constants { - static final String FROM_JSON_STRING_WITH_TYPE = "fromJsonStringWithType"; + static final String PARSE_STRING = "parseString"; + static final String PARSE_BYTES = "parseBytes"; + static final String PARSE_STREAM = "parseStream"; static final String NAME = "Name"; static final String JSONDATA = "jsondata"; } diff --git a/compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/JsondataTypeValidator.java b/compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/JsondataTypeValidator.java index f1c0e02..d6285b2 100644 --- a/compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/JsondataTypeValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/lib/data/jsondata/compiler/JsondataTypeValidator.java @@ -113,7 +113,7 @@ private void processFunctionDefinitionNode(FunctionDefinitionNode functionDefini } TypeSymbol typeSymbol = ((VariableSymbol) symbol.get()).typeDescriptor(); - if (!isFromJsonFunctionFromJsondata(initializer.get())) { + if (!isParseFunctionOfStringSource(initializer.get())) { if (typeSymbol.typeKind() == TypeDescKind.RECORD) { detectDuplicateFields((RecordTypeSymbol) typeSymbol, ctx); } @@ -124,7 +124,7 @@ private void processFunctionDefinitionNode(FunctionDefinitionNode functionDefini } } - private boolean isFromJsonFunctionFromJsondata(ExpressionNode expressionNode) { + private boolean isParseFunctionOfStringSource(ExpressionNode expressionNode) { if (expressionNode.kind() == SyntaxKind.CHECK_EXPRESSION) { expressionNode = ((CheckExpressionNode) expressionNode).expression(); } @@ -133,7 +133,8 @@ private boolean isFromJsonFunctionFromJsondata(ExpressionNode expressionNode) { return false; } String functionName = ((FunctionCallExpressionNode) expressionNode).functionName().toString().trim(); - return functionName.contains(Constants.FROM_JSON_STRING_WITH_TYPE); + return functionName.contains(Constants.PARSE_STRING) || functionName.contains(Constants.PARSE_BYTES) + || functionName.contains(Constants.PARSE_STREAM); } private void validateExpectedType(TypeSymbol typeSymbol, SyntaxNodeAnalysisContext ctx) { @@ -219,7 +220,7 @@ private void reportDiagnosticInfo(SyntaxNodeAnalysisContext ctx, Optional initializer = moduleVariableDeclarationNode.initializer(); - if (initializer.isEmpty() || !isFromJsonFunctionFromJsondata(initializer.get())) { + if (initializer.isEmpty() || !isParseFunctionOfStringSource(initializer.get())) { return; } diff --git a/native/src/main/java/io/ballerina/lib/data/jsondata/json/Native.java b/native/src/main/java/io/ballerina/lib/data/jsondata/json/Native.java index be79a6b..23a9d3d 100644 --- a/native/src/main/java/io/ballerina/lib/data/jsondata/json/Native.java +++ b/native/src/main/java/io/ballerina/lib/data/jsondata/json/Native.java @@ -20,11 +20,8 @@ import io.ballerina.lib.data.jsondata.io.DataReaderTask; import io.ballerina.lib.data.jsondata.io.DataReaderThreadPool; -import io.ballerina.lib.data.jsondata.utils.DiagnosticErrorCode; -import io.ballerina.lib.data.jsondata.utils.DiagnosticLog; import io.ballerina.runtime.api.Environment; import io.ballerina.runtime.api.Future; -import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.utils.JsonUtils; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BError; @@ -45,7 +42,7 @@ */ public class Native { - public static Object fromJsonWithType(Object json, BMap options, BTypedesc typed) { + public static Object parseAsType(Object json, BMap options, BTypedesc typed) { try { return JsonTraverse.traverse(json, options, typed.getDescribingType()); } catch (BError e) { @@ -53,32 +50,32 @@ public static Object fromJsonWithType(Object json, BMap options } } - public static Object fromJsonStringWithType(Environment env, Object json, BMap options, - BTypedesc typed) { + public static Object parseString(BString json, BMap options, BTypedesc typed) { try { - Type expType = typed.getDescribingType(); - if (json instanceof BString) { - return JsonParser.parse(new StringReader(((BString) json).getValue()), options, expType); - } else if (json instanceof BArray) { - byte[] bytes = ((BArray) json).getBytes(); - return JsonParser.parse(new InputStreamReader(new ByteArrayInputStream(bytes)), options, - typed.getDescribingType()); - } else if (json instanceof BStream) { - final BObject iteratorObj = ((BStream) json).getIteratorObj(); - final Future future = env.markAsync(); - DataReaderTask task = new DataReaderTask(env, iteratorObj, future, typed, options); - DataReaderThreadPool.EXECUTOR_SERVICE.submit(task); - return null; - } else { - return DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE, expType); - } + return JsonParser.parse(new StringReader(json.getValue()), options, typed.getDescribingType()); } catch (BError e) { return e; - } catch (Exception e) { - return DiagnosticLog.error(DiagnosticErrorCode.JSON_PARSER_EXCEPTION, e.getMessage()); } } + public static Object parseBytes(BArray json, BMap options, BTypedesc typed) { + try { + byte[] bytes = json.getBytes(); + return JsonParser.parse(new InputStreamReader(new ByteArrayInputStream(bytes)), options, + typed.getDescribingType()); + } catch (BError e) { + return e; + } + } + + public static Object parseStream(Environment env, BStream json, BMap options, BTypedesc typed) { + final BObject iteratorObj = json.getIteratorObj(); + final Future future = env.markAsync(); + DataReaderTask task = new DataReaderTask(env, iteratorObj, future, typed, options); + DataReaderThreadPool.EXECUTOR_SERVICE.submit(task); + return null; + } + public static Object toJson(Object value) { return JsonUtils.convertToJson(value); }