-1

I have a nested Json that I need to remove some objects in array with a filter, this in a dynamic way, this Json structure is not the same all time, for example:

{
    "A": "HI",
    "B": 1, 
    "C": [
        {
            "TIME": "TODAY",
            "LOCATION": "USA",
            "BALANCE": 100,
            "STATE": "TX",
            "NAME": "JHON"
        },
        {
            "TIME": "YESTERDAY",
            "LOCATION": "USA",
            "BALANCE": 100,
            "STATE": "TX",
            "NAME": "MICHAEL"
        },
        {
            "TIME": "YESTERDAY",
            "LOCATION": "USA",
            "BALANCE": 100,
            "STATE": "TX",
            "NAME": "REBECCA"
        }
    ]
}

And now, from this kind of nested Json I want to remove the Object that contains key "NAME" with VALUE "Michael", and the result have to be this one:

{
    "A": "HI",
    "B": 1, 
    "C": [
        {
            "TIME": "TODAY",
            "LOCATION": "USA",
            "BALANCE": 100,
            "STATE": "TX",
            "NAME": "JHON"
        },
        {
            "TIME": "YESTERDAY",
            "LOCATION": "USA",
            "BALANCE": 100,
            "STATE": "TX",
            "NAME": "REBECCA"
        }
    ]
}

This JSON change every time depending on reponse from an API, just I have to match KEY - VALUE to remove the Object that I need filter without modify the Json structure, in this case I need to recive KEY = "NAME" and VALUE = "Michael" to filter this object.

In this case "C" is a variable key and I could have more keys with arrays in the same json that need to be filtered, I need a dynamic way to filter in array of objects based just in key-value

Could you help me find a way to perform this functionality?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197

1 Answers1

0

Here is a streaming solution that can deal with huge responses without any significant impact on your system. It also does not require any class mappings using the built-in JSON node representation (therefore saving time and probably memory on type bindings).

public static void filterAbcBySpecializedStreaming(final JsonReader input, final JsonWriter output)
        throws IOException {
    input.beginObject();
    output.beginObject();
    // iterating over each entry of the outer object
    while ( input.hasNext() ) {
        final String name = input.nextName();
        output.name(name);
        switch ( name ) {
        // assuming "A" is known to be a string always
        case "A":
            output.value(input.nextString());
            break;
        // assuming "B" is known to be a number always
        case "B":
            // note: JsonReader does not allow to read a number of an arbitrary length as an instance of `java.lang.Number`
            output.value(new LazilyParsedNumber(input.nextString()));
            break;
        // assuming "C" is known to be an array of objects always
        case "C":
            input.beginArray();
            output.beginArray();
            // iterating over each element of the array
            while ( input.hasNext() ) {
                // assuming array elements are not very big and it trusts their size
                final JsonObject jsonObject = Streams.parse(input)
                        .getAsJsonObject();
                // if the current element JSON object has a property named "NAME" and its value is set to "MICHAEL", the skip it
                // of course, this can also be externalized using the Strategy design pattern (e.g. using java.lang.function.Predicate)
                // but this entire method is not that generic so probably it's fine
                if ( jsonObject.get("NAME").getAsString().equals("MICHAEL") ) {
                    continue;
                }
                Streams.write(jsonObject, output);
            }
            input.endArray();
            output.endArray();
            break;
        default:
            throw new IllegalStateException("Unknown: " + name);
        }
    }
    input.endObject();
    output.endObject();
}

The test:

final JsonElement expected = Streams.parse(expectedJsonReader);
final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
final JsonWriter output = new JsonWriter(new OutputStreamWriter(buffer));
Filter.filterAbcBySpecializedStreaming(input, output);
output.flush();
final JsonElement actual = JsonParser.parseReader(new InputStreamReader(new ByteArrayInputStream(buffer.toByteArray())));
Assertions.assertEquals(expected, actual);

Of course, it's not that easy, but it may result in the best performance. Making it generic and "dynamic" is an option, and it can be done according to your needs. If you find it too complex, the input JSON document is known not to be very big (therefore causing OutOfMemoryErrors), you can also filter it out as a tree, but again without any type bindings:

public static void filterAbcBySpecializedTree(final JsonElement input, final JsonElement output) {
    final JsonObject inputJsonObject = input.getAsJsonObject();
    final JsonObject outputJsonObject = output.getAsJsonObject();
    for ( final Map.Entry<String, JsonElement> e : inputJsonObject.entrySet() ) {
        final String name = e.getKey();
        final JsonElement value = e.getValue();
        switch ( name ) {
        case "A":
        case "B":
            outputJsonObject.add(name, value.deepCopy());
            break;
        case "C":
            final JsonArray valueJsonArray = value.getAsJsonArray()
                    .deepCopy();
            for ( final Iterator<JsonElement> it = valueJsonArray.iterator(); it.hasNext(); ) {
                final JsonObject elementJsonObject = it.next().getAsJsonObject();
                if ( elementJsonObject.get("NAME").getAsString().equals("MICHAEL") ) {
                    it.remove();
                }
            }
            outputJsonObject.add(name, valueJsonArray);
            break;
        default:
            throw new IllegalStateException("Unknown: " + name);
        }
    }
}

Test:

final JsonElement input = Streams.parse(inputJsonReader);
final JsonElement expected = Streams.parse(expectedJsonReader);
final JsonElement actual = new JsonObject();
Filter.filterAbcBySpecializedTree(input, actual);
Assertions.assertEquals(expected, actual);