From 00ae9c1615c01cd30c11e407e374386d9e7e3370 Mon Sep 17 00:00:00 2001 From: Darin Dimitrov Date: Thu, 10 Oct 2019 15:26:56 +0300 Subject: [PATCH 1/2] feat: Add org.json.JSONObject.from method (#1500) --- test-app/app/src/main/assets/app/mainpage.js | 1 + .../main/assets/app/tests/testJSONObjects.js | 62 ++++++++++ .../java/com/tns/tests/JSONObjectMethods.java | 14 +++ test-app/runtime/CMakeLists.txt | 1 + .../runtime/src/main/cpp/JSONObjectHelper.cpp | 114 ++++++++++++++++++ .../runtime/src/main/cpp/JSONObjectHelper.h | 24 ++++ test-app/runtime/src/main/cpp/Runtime.cpp | 1 + test-app/runtime/src/main/cpp/Runtime.h | 2 + 8 files changed, 219 insertions(+) create mode 100644 test-app/app/src/main/assets/app/tests/testJSONObjects.js create mode 100644 test-app/app/src/main/java/com/tns/tests/JSONObjectMethods.java create mode 100644 test-app/runtime/src/main/cpp/JSONObjectHelper.cpp create mode 100644 test-app/runtime/src/main/cpp/JSONObjectHelper.h diff --git a/test-app/app/src/main/assets/app/mainpage.js b/test-app/app/src/main/assets/app/mainpage.js index 031ae8ddd..1893b4b73 100644 --- a/test-app/app/src/main/assets/app/mainpage.js +++ b/test-app/app/src/main/assets/app/mainpage.js @@ -54,6 +54,7 @@ require("./tests/testInterfaceImplementation"); require("./tests/testRuntimeImplementedAPIs"); require("./tests/testsInstanceOfOperator"); require("./tests/testReleaseNativeCounterpart"); +require("./tests/testJSONObjects"); require("./tests/kotlin/companions/testCompanionObjectsSupport"); require("./tests/kotlin/properties/testPropertiesSupport"); require("./tests/kotlin/delegation/testDelegationSupport"); diff --git a/test-app/app/src/main/assets/app/tests/testJSONObjects.js b/test-app/app/src/main/assets/app/tests/testJSONObjects.js new file mode 100644 index 000000000..8b37ab200 --- /dev/null +++ b/test-app/app/src/main/assets/app/tests/testJSONObjects.js @@ -0,0 +1,62 @@ +describe("Test JSONObject conversions", () => { + it("org.json.JSONObject.from method is defined", () => { + expect(typeof org.json.JSONObject.from).toBe("function"); + }); + + it("JSONObject.from takes at least one argument", () => { + expect(() => org.json.JSONObject.from()).toThrowError(); + }); + + it("JSONObject.from with boolean", () => { + let param = true; + let actual = org.json.JSONObject.from(param); + expect(actual).toBe(param); + }); + + it("JSONObject.from with string", () => { + let param = "some param"; + let actual = org.json.JSONObject.from(param); + expect(actual).toBe(param); + }); + + it("JSONObject.from with number", () => { + let param = 123; + let actual = org.json.JSONObject.from(param); + expect(actual).toBe(param); + }); + + it("JSONObject.from with date must serialize it to JSON using ISO8601", () => { + let timestamp = 1570696661136; + let param = new Date(timestamp); + let actual = org.json.JSONObject.from(param); + expect(actual).toBe("2019-10-10T08:37:41.136Z"); + }); + + it("JSONObject.from with object", () => { + let param = { + prop1: "prop1 value", + prop2: 123, + prop3: { + prop4: "prop 4 value" + } + }; + let actual = org.json.JSONObject.from(param); + expect(actual instanceof org.json.JSONObject).toBe(true); + let actualStr = com.tns.tests.JSONObjectMethods.testWithObject(actual); + expect(actualStr).toBe(`{"prop1":"prop1 value","prop2":123,"prop3":{"prop4":"prop 4 value"}}`); + }); + + it("JSONObject.from with array", () => { + let param = [{ + prop1: "item 1, prop1 value", + prop2: 123 + }, { + prop1: "item 2, prop1 value", + prop2: 456 + }]; + let actual = org.json.JSONObject.from(param); + expect(actual instanceof org.json.JSONArray).toBe(true); + let actualStr = com.tns.tests.JSONObjectMethods.testWithArray(actual); + expect(actualStr).toBe(`[{"prop1":"item 1, prop1 value","prop2":123},{"prop1":"item 2, prop1 value","prop2":456}]`); + }); +}); \ No newline at end of file diff --git a/test-app/app/src/main/java/com/tns/tests/JSONObjectMethods.java b/test-app/app/src/main/java/com/tns/tests/JSONObjectMethods.java new file mode 100644 index 000000000..9bac67ea3 --- /dev/null +++ b/test-app/app/src/main/java/com/tns/tests/JSONObjectMethods.java @@ -0,0 +1,14 @@ +package com.tns.tests; + +import org.json.JSONArray; +import org.json.JSONObject; + +public class JSONObjectMethods { + public static String testWithObject(JSONObject obj) { + return obj.toString(); + } + + public static String testWithArray(JSONArray obj) { + return obj.toString(); + } +} diff --git a/test-app/runtime/CMakeLists.txt b/test-app/runtime/CMakeLists.txt index bd3ecc348..1f8a95269 100644 --- a/test-app/runtime/CMakeLists.txt +++ b/test-app/runtime/CMakeLists.txt @@ -149,6 +149,7 @@ add_library( src/main/cpp/JniSignatureParser.cpp src/main/cpp/JsArgConverter.cpp src/main/cpp/JsArgToArrayConverter.cpp + src/main/cpp/JSONObjectHelper.cpp src/main/cpp/Logger.cpp src/main/cpp/ManualInstrumentation.cpp src/main/cpp/MetadataMethodInfo.cpp diff --git a/test-app/runtime/src/main/cpp/JSONObjectHelper.cpp b/test-app/runtime/src/main/cpp/JSONObjectHelper.cpp new file mode 100644 index 000000000..9ca78d5a2 --- /dev/null +++ b/test-app/runtime/src/main/cpp/JSONObjectHelper.cpp @@ -0,0 +1,114 @@ +#include "NativeScriptException.h" +#include "JSONObjectHelper.h" +#include "ArgConverter.h" +#include +#include + +using namespace v8; +using namespace tns; + +JSONObjectHelper::JSONObjectHelper() + : m_objectManager(nullptr), m_serializeFunc(nullptr) { +} + +void JSONObjectHelper::CreateConvertFunctions(Isolate *isolate, const Local &global, ObjectManager* objectManager) { + m_objectManager = objectManager; + + m_serializeFunc = new Persistent(isolate, CreateSerializeFunc(isolate)); + + Local context = isolate->GetCurrentContext(); + + Local extData = External::New(isolate, this); + Local fromFunc = FunctionTemplate::New(isolate, ConvertCallbackStatic, extData)->GetFunction(context).ToLocalChecked(); + + Local jsonObjectFunc = global->Get(context, ArgConverter::ConvertToV8String(isolate, "org")) + .ToLocalChecked().As()->Get(context, ArgConverter::ConvertToV8String(isolate, "json")) + .ToLocalChecked().As()->Get(context, ArgConverter::ConvertToV8String(isolate, "JSONObject")) + .ToLocalChecked().As(); + + jsonObjectFunc->Set(context, ArgConverter::ConvertToV8String(isolate, "from"), fromFunc); +} + +void JSONObjectHelper::ConvertCallbackStatic(const FunctionCallbackInfo& info) { + try { + Local extData = info.Data().As(); + auto thiz = reinterpret_cast(extData->Value()); + thiz->ConvertCallback(info); + } catch (NativeScriptException& e) { + e.ReThrowToV8(); + } catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToV8(); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToV8(); + } +} + +void JSONObjectHelper::ConvertCallback(const FunctionCallbackInfo& info) { + if (info.Length() < 1) { + NativeScriptException nsEx(std::string("The \"from\" function expects one parameter")); + nsEx.ReThrowToV8(); + return; + } + + Isolate* isolate = info.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + Local serializeFunc = m_serializeFunc->Get(isolate); + Local args[] = { info[0] }; + Local result; + TryCatch tc(isolate); + if (!serializeFunc->Call(context, Undefined(isolate), 1, args).ToLocal(&result)) { + throw NativeScriptException(tc, "Error serializing to JSONObject"); + } + + info.GetReturnValue().Set(result); +} + +Local JSONObjectHelper::CreateSerializeFunc(Isolate* isolate) { + std::string source = + "(() => function serialize(data) {" + " let store;" + " switch (typeof data) {" + " case \"string\":" + " case \"boolean\":" + " case \"number\": {" + " return data;" + " }" + " case \"object\": {" + " if (!data) {" + " return null;" + " }" + "" + " if (data instanceof Date) {" + " return data.toJSON();" + " }" + "" + " if (Array.isArray(data)) {" + " store = new org.json.JSONArray();" + " data.forEach((item) => store.put(serialize(item)));" + " return store;" + " }" + "" + " store = new org.json.JSONObject();" + " Object.keys(data).forEach((key) => store.put(key, serialize(data[key])));" + " return store;" + " }" + " default:" + " return null;" + " }" + "})()"; + + Local context = isolate->GetCurrentContext(); + + Local