question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Gson fails to convert ContentValues with Android 10

See original GitHub issue

The internal implementation of ContentValues changed from using a Hashmap to using an ArrayMap in Android 10. I’ve included some sample code below to demonstrate how it fails now.

ContentValues values = new ContentValues();
values.put("Key 1", "Key 1 value");
values.put("Key 2", "Key 2 value");
values.put("Key 3", "Key 3 value");
String json = new Gson().toJson(values);
Log.d("XXX", String.format("Content values json: '%s'", json));

On Android 10 the output looks like: Output: XXX: Content values json: ‘{}’

On Android 9 the output looks like: Output: XXX: Content values json: {“mValues”:{“Key 1”:“Key 1 value”,“Key 2”:“Key 2 value”,“Key 3”:“Key 3 value”}}

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:6 (2 by maintainers)

github_iconTop GitHub Comments

2reactions
lyubomyr-shaydarivcommented, Sep 6, 2019

@tethridge

I’m not into the Android API, but I don’t really believe using Gson for this case is a good choice. (Except probably an attempt of nesting/packing some data as string values, however Parcelable looks like a (de)serialization tool already.) If you still need using Gson, you probably might fine-tune the example below (avoid intermediate maps, avoid the map type adapter and use readers/writers directly, etc):

@SuppressWarnings("all")
class FakeContentValues {

	private final Map<String, Object> mValues = new LinkedHashMap<>();

	FakeContentValues() {
	}

	// @formatter:off
	void putNull(final String key) { mValues.put(key, null); }
	void put(final String key, final Short value) { mValues.put(key, value); }
	void put(final String key, final Long value) { mValues.put(key, value); }
	void put(final String key, final Double value) { mValues.put(key, value); }
	void put(final String key, final Integer value) { mValues.put(key, value); }
	void put(final String key, final String value) { mValues.put(key, value); }
	void put(final String key, final Boolean value) { mValues.put(key, value); }
	void put(final String key, final Float value) { mValues.put(key, value); }
	void put(final String key, final byte[] value) { mValues.put(key, value); }
	void put(final String key, final Byte value) { mValues.put(key, value); }
	Object get(final String key) { return mValues.get(key); }
	Set<String> keySet() { return mValues.keySet(); }
	// @formatter:on

	@Override
	public boolean equals(final Object o) {
		if ( this == o ) {
			return true;
		}
		if ( o == null || getClass() != o.getClass() ) {
			return false;
		}
		final FakeContentValues that = (FakeContentValues) o;
		return mValues.equals(that.mValues);
	}

	@Override
	public int hashCode() {
		return mValues.hashCode();
	}

	@Override
	public String toString() {
		return "{mValues=" + mValues + "}";
	}

}
private static final Gson gson = new GsonBuilder()
	.disableHtmlEscaping()
	.disableInnerClassSerialization()
	.registerTypeAdapterFactory(new TypeAdapterFactory() {
		@Override
		@SuppressWarnings("ReturnOfNull")
		public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
			final Class<? super T> rawType = typeToken.getRawType();
			if ( !FakeContentValues.class.isAssignableFrom(rawType) ) {
				return null;
			}
			final TypeAdapter<LinkedHashMap<String, Object>> delegateTypeAdapter = gson.getDelegateAdapter(this, mapStringToObjectTypeToken);
			final TypeAdapter<FakeContentValues> typeAdapter = new TypeAdapter<FakeContentValues>() {
				@Override
				public void write(final JsonWriter out, final FakeContentValues contentValues)
						throws IOException {
					final LinkedHashMap<String, Object> intermediate = new LinkedHashMap<>();
					for ( final String key : contentValues.keySet() ) {
						intermediate.put(key, contentValues.get(key));
					}
					delegateTypeAdapter.write(out, intermediate);
				}

				@Override
				@SuppressWarnings("IfStatementWithTooManyBranches")
				public FakeContentValues read(final JsonReader in)
						throws IOException {
					final Map<String, Object> intermediate = delegateTypeAdapter.read(in);
					final FakeContentValues contentValues = new FakeContentValues();
					for ( final Map.Entry<String, Object> e : intermediate.entrySet() ) {
						final String k = e.getKey();
						final Object v = e.getValue();
						// @formatter:off
						if ( v == null ) { contentValues.putNull(k); }
						else if ( v instanceof Short ) { contentValues.put(k, (Short) v); }
						else if ( v instanceof Long ) { contentValues.put(k, (Long) v); }
						else if ( v instanceof Double ) { contentValues.put(k, (Double) v); }
						else if ( v instanceof Integer ) { contentValues.put(k, (Integer) v); }
						else if ( v instanceof String ) { contentValues.put(k, (String) v); }
						else if ( v instanceof Boolean ) { contentValues.put(k, (Boolean) v); }
						else if ( v instanceof Float ) { contentValues.put(k, (Float) v); }
						else if ( v instanceof byte[] ) { contentValues.put(k, (byte[]) v); }
						else if ( v instanceof Byte ) { contentValues.put(k, (Byte) v); }
						else { throw new UnsupportedOperationException(String.valueOf(v.getClass())); }
						// @formatter:on
					}
					return contentValues;
				}
			};
			@SuppressWarnings("unchecked")
			final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) typeAdapter;
			return castTypeAdapter;
		}
	})
	.create();
final FakeContentValues before = new FakeContentValues();
before.put("Key 1", "Key 1 value");
before.put("Key 2", "Key 2 value");
before.put("Key 3", "Key 3 value");
System.out.println("before=" + before);
final String json = gson.toJson(before);
System.out.println(json);
final FakeContentValues after = gson.fromJson(json, FakeContentValues.class);
System.out.println("after=" + after);
System.out.println("equals=" + before.equals(after));

gives

before={mValues={Key 1=Key 1 value, Key 2=Key 2 value, Key 3=Key 3 value}}
{"Key 1":"Key 1 value","Key 2":"Key 2 value","Key 3":"Key 3 value"}
after={mValues={Key 1=Key 1 value, Key 2=Key 2 value, Key 3=Key 3 value}}
equals=true
1reaction
tethridgecommented, Sep 17, 2019

Thanks for the feedback. I’ll close this issue. I resolved the issue in my app by implementing my own serialization code.

Read more comments on GitHub >

github_iconTop Results From Across the Web

convert JSON to contentvalues - android - Stack Overflow
I made a static class that will convert any object into contentvalues using reflection. I'm sure there's an equivalent ...
Read more >
JSONObject - Android Developers
This two-step conversion is lossy for very large values. ... This fails with a JSONException if the requested name has no value or...
Read more >
SQLite - Android - DevTut
It is called when the database file already exists, but its version is lower than the one specified in the current version of...
Read more >
Università degli Studi di Parma - Unipr
This mechanism on Android platform is called “Shared Preferences” and ... Gson is a Java library that can be used to convert Java...
Read more >
Università degli Studi di Parma - Piazza
This mechanism on Android platform is called “Shared Preferences” and ... Gson is a Java library that can be used to convert Java...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found