Skip to content

Ref: Handle nested json configuration structures #4470

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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
Member

@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
Member

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
Member

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