Q:

Remove keys from anywhere in a JSON string without deserialization

Without deserializing and reserializing the following deeply-nested JSON string (if this is possible), how to remove any key with the name "REMOVEME" from any nested level?
Tried:
Reluctantly tried deserializing to JsonObject / LinkedHashMap to traverse the structure with node.remove(e.getKey()) on offending keys, but Gson throws concurrent modification exceptions, and Jackson requires full deserialization. 
Before:
{
    "a": {
        "REMOVEME" : "unwanted",
        "b": {
            "REMOVEME" : "unwanted",
            "c": {
                "d": {
                    "REMOVEME" : "unwanted",
                    "something": "default",
                    "e": {
                        "f": {
                            "REMOVEME" : "unwanted",
                            "g": {
                                "REMOVEME" : "unwanted",        
                                "h": {
                                    ... ,

After:
{
    "a": {
        "b": {
            "c": {
                "d": {
                    "something": "default",
                    "e": {
                        "f": {
                            "g": {
                                "h": {
                                    ... ,

A:

One way to solve this is with streaming token-parsing with Gson. This method handles extremely large JSON strings with ease.
Example before and after:
{"a":{"removeme":"unwanted","b":{"c":{"removeme":{"x":1},"d":{"e":123}}}}}
{"a":{"b":{"c":{"d":{"e":123}}}}}

Essential test harness:
String rawJson = "{\"a\":{\"removeme\":\"unwanted\",\"b\":{\"c\":{\"removeme\":{\"x\":1},\"d\":{\"e\":123}}}}}";

final Gson gson = new GsonBuilder().create();
JsonReader reader = gson.newJsonReader( new StringReader( rawJson ) );

StringWriter outWriter = new StringWriter();
JsonWriter writer = gson.newJsonWriter( outWriter );

JsonStreamFilter.streamFilter( reader, writer, Arrays.asList( "removeme" ) );

System.out.println( rawJson );
System.out.println( outWriter.toString() );

Tokenizing and filtering magic:
public class JsonStreamFilter
{
    /**
     * Filter out all properties with names included in the `propertiesToRemove` list.
     * 
     * @param reader JsonReader to read in the JSON token
     * @param writer JsonWriter to accept modified JSON tokens
     * @param propertiesToRemove List of property names to remove
     * @throws IOException
     * @see Gson docs at https://sites.google.com/site/gson/streaming
     */
    public static void streamFilter(
        final JsonReader reader,
        final JsonWriter writer,
        final List<String> propertiesToRemove
    ) throws IOException
    {
        while ( true )
        {
            JsonToken token = reader.peek();
            switch ( token )
            {
                case BEGIN_ARRAY:
                    reader.beginArray();
                    writer.beginArray();
                    break;
                case END_ARRAY:
                    reader.endArray();
                    writer.endArray();
                    break;
                case BEGIN_OBJECT:
                    reader.beginObject();
                    writer.beginObject();
                    break;
                case END_OBJECT:
                    reader.endObject();
                    writer.endObject();
                    break;
                case NAME:
                    String name = reader.nextName();

                    // Skip all nested structures stemming from this property
                    if ( propertiesToRemove.contains( name ) )
                    {
                        reader.skipValue();
                        break;
                    }

                    writer.name( name );
                    break;
                case STRING:
                    String s = reader.nextString();
                    writer.value( s );
                    break;
                case NUMBER:
                    String n = reader.nextString();
                    writer.value( new BigDecimal( n ) );
                    break;
                case BOOLEAN:
                    boolean b = reader.nextBoolean();
                    writer.value( b );
                    break;
                case NULL:
                    reader.nextNull();
                    writer.nullValue();
                    break;
                case END_DOCUMENT:
                    return;
            }
        }
    }
}

