Skip to content
Closed
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package io.sentry.rnsentryandroidtester

import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.facebook.react.bridge.JavaOnlyMap
import com.facebook.soloader.SoLoader
import io.sentry.react.RNSentryJsonUtils
import org.json.JSONArray
import org.json.JSONObject
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class RNSentryJsonUtilsTest {
@Before
fun setUp() {
val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
SoLoader.init(context, false)
}
Comment on lines +20 to +24
Copy link
Contributor

@krystofwoldrich krystofwoldrich Jan 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be needed. Let's remove it, to know 100% that these conversions can run before the RN initializes.


@Test
fun testJsonObjectToReadableMap() {
val json =
JSONObject().apply {
put("stringKey", "stringValue")
put("booleanKey", true)
put("intKey", 123)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also test test doubles/floats as those are common in Sentry configs.

}

val result = RNSentryJsonUtils.jsonObjectToReadableMap(json)

assertNotNull(result)
assertTrue(result is JavaOnlyMap)
assertEquals("stringValue", result?.getString("stringKey"))
assertEquals(true, result?.getBoolean("booleanKey"))
assertEquals(123, result?.getInt("intKey"))
}

@Test
fun testNestedJsonObjectToReadableMap() {
val json =
JSONObject().apply {
put("stringKey", "stringValue")
put("booleanKey", true)
put("intKey", 123)
put(
"nestedKey",
JSONObject().apply {
put("nestedStringKey", "nestedStringValue")
put("nestedBooleanKey", false)
put(
"deepNestedArrayKey",
JSONArray().apply {
put("deepNestedArrayValue")
},
)
},
)
put(
"arrayKey",
JSONArray().apply {
put("arrayStringValue")
put(789)
put(
JSONObject().apply {
put("deepNestedStringKey", "deepNestedStringValue")
put("deepNestedBooleanKey", false)
},
)
},
)
}

val result = RNSentryJsonUtils.jsonObjectToReadableMap(json)

assertNotNull(result)
assertTrue(result is JavaOnlyMap)
assertEquals("stringValue", result?.getString("stringKey"))
assertEquals(true, result?.getBoolean("booleanKey"))
assertEquals(123, result?.getInt("intKey"))
val nested = result?.getMap("nestedKey")
assertNotNull(nested)
assertEquals("nestedStringValue", nested?.getString("nestedStringKey"))
assertEquals(false, nested?.getBoolean("nestedBooleanKey"))
val deepNestedArray = nested?.getArray("deepNestedArrayKey")
assertNotNull(deepNestedArray)
assertEquals("deepNestedArrayValue", deepNestedArray?.getString(0))
val array = result?.getArray("arrayKey")
assertNotNull(array)
assertEquals("arrayStringValue", array?.getString(0))
assertEquals(789, array?.getInt(1))
val deepNested = array?.getMap(2)
assertNotNull(deepNested)
assertEquals("deepNestedStringValue", deepNested?.getString("deepNestedStringKey"))
assertEquals(false, deepNested?.getBoolean("deepNestedBooleanKey"))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@ import com.facebook.react.bridge.JavaOnlyArray
import com.facebook.react.bridge.JavaOnlyMap
import com.facebook.soloader.SoLoader
import io.sentry.react.RNSentryMapConverter
import org.json.JSONObject
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
Expand Down Expand Up @@ -700,22 +697,4 @@ class MapConverterTest {

assertEquals(actual, expectedMap1)
}

@Test
fun testJsonObjectToReadableMap() {
val json =
JSONObject().apply {
put("stringKey", "stringValue")
put("booleanKey", true)
put("intKey", 123)
}

val result = RNSentryMapConverter.jsonObjectToReadableMap(json)

assertNotNull(result)
assertTrue(result is JavaOnlyMap)
assertEquals("stringValue", result.getString("stringKey"))
assertEquals(true, result.getBoolean("booleanKey"))
assertEquals(123, result.getInt("intKey"))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package io.sentry.react;

import android.content.Context;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import io.sentry.ILogger;
import io.sentry.SentryLevel;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public final class RNSentryJsonUtils {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not expose this outside of the package.

private RNSentryJsonUtils() {
throw new AssertionError("Utility class should not be instantiated");
}

/**
* Read the configuration file in the Android assets folder and return the options as a
* JSONObject.
*
* @param context Android Context
* @param fileName configuration file name
* @param logger Sentry logger
* @return JSONObject with the configuration options
*/
public static @Nullable JSONObject getOptionsFromConfigurationFile(
@NotNull Context context, @NotNull String fileName, @NotNull ILogger logger) {
try (InputStream inputStream = context.getAssets().open(fileName);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {

StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
String configFileContent = stringBuilder.toString();
return new JSONObject(configFileContent);

} catch (Exception e) {
logger.log(
SentryLevel.ERROR,
"Failed to read configuration file. Please make sure "
+ fileName
+ " exists in the root of your project.",
e);
return null;
}
}

private static @NotNull Map<String, Object> jsonObjectToMap(@NotNull JSONObject jsonObject) {
Map<String, Object> map = new HashMap<>();
Iterator<String> keys = jsonObject.keys();
while (keys.hasNext()) {
String key = keys.next();
Object value = null;
try {
value = jsonObject.get(key);
} catch (JSONException e) {
throw new RuntimeException(e);
}
map.put(key, convertValue(value));
}
return map;
}

private static @NotNull List<Object> jsonArrayToList(@NotNull JSONArray jsonArray) {
List<Object> list = new ArrayList<>();

for (int i = 0; i < jsonArray.length(); i++) {
Object value = jsonArray.opt(i);
list.add(convertValue(value));
}

return list;
}

private static @Nullable Object convertValue(@Nullable Object value) {
if (value instanceof JSONObject) {
return jsonObjectToMap((JSONObject) value);
} else if (value instanceof JSONArray) {
return jsonArrayToList((JSONArray) value);
} else {
return value; // Primitive type or null
}
}

/**
* Convert a JSONObject to a ReadableMap
*
* @param jsonObject JSONObject to convert
* @return ReadableMap with the same data as the JSONObject
*/
public static @Nullable ReadableMap jsonObjectToReadableMap(@Nullable JSONObject jsonObject) {
if (jsonObject == null) {
return null;
}
Map<String, Object> map = jsonObjectToMap(jsonObject);
return (WritableMap) RNSentryMapConverter.convertToJavaWritable(map);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,10 @@
import io.sentry.android.core.AndroidLogger;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.json.JSONException;
import org.json.JSONObject;

public final class RNSentryMapConverter {
public static final String NAME = "RNSentry.MapConverter";
Expand Down Expand Up @@ -202,25 +198,4 @@ private static void addValueToWritableMap(WritableMap writableMap, String key, O
logger.log(SentryLevel.ERROR, "Could not convert object" + value);
}
}

public static ReadableMap jsonObjectToReadableMap(JSONObject jsonObject) {
Map<String, Object> map = jsonObjectToMap(jsonObject);
return (WritableMap) convertToJavaWritable(map);
}

private static Map<String, Object> jsonObjectToMap(JSONObject jsonObject) {
Map<String, Object> map = new HashMap<>();
Iterator<String> keys = jsonObject.keys();
while (keys.hasNext()) {
String key = keys.next();
Object value = null;
try {
value = jsonObject.get(key);
} catch (JSONException e) {
throw new RuntimeException(e);
}
map.put(key, value);
}
return map;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
import io.sentry.SentryLevel;
import io.sentry.android.core.AndroidLogger;
import io.sentry.android.core.SentryAndroidOptions;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.jetbrains.annotations.NotNull;
import org.json.JSONObject;

Expand All @@ -33,8 +30,9 @@ public static void init(
@NotNull final Context context,
@NotNull Sentry.OptionsConfiguration<SentryAndroidOptions> configuration) {
try {
JSONObject jsonObject = getOptionsFromConfigurationFile(context);
ReadableMap rnOptions = RNSentryMapConverter.jsonObjectToReadableMap(jsonObject);
JSONObject jsonObject =
RNSentryJsonUtils.getOptionsFromConfigurationFile(context, CONFIGURATION_FILE, logger);
ReadableMap rnOptions = RNSentryJsonUtils.jsonObjectToReadableMap(jsonObject);
RNSentryStart.startWithOptions(context, rnOptions, configuration, null, logger);
} catch (Exception e) {
logger.log(
Expand All @@ -51,27 +49,4 @@ public static void init(
public static void init(@NotNull final Context context) {
init(context, options -> {});
}

private static JSONObject getOptionsFromConfigurationFile(Context context) {
try (InputStream inputStream = context.getAssets().open(CONFIGURATION_FILE);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {

StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
String configFileContent = stringBuilder.toString();
return new JSONObject(configFileContent);

} catch (Exception e) {
logger.log(
SentryLevel.ERROR,
"Failed to read configuration file. Please make sure "
+ CONFIGURATION_FILE
+ " exists in the root of your project.",
e);
return null;
}
}
}
Loading