1

I'm trying to process a json file using gson, but I'm running into a weird error. The json I'm reading from (and can't modify) has a weird way of dealing with null fields. It puts an [] in places where there is no data, causing gson to think it's an array when it's expecting a object.

An example from the gson:

//non-empty field
"prizes":[
            {
                "year":"1902",
                "category":"physics",
                "share":"2",
                "motivation":"\"in recognition of the extraordinary service they rendered by their researches into the influence of magnetism upon radiation phenomena\"",
                "affiliations":[
                    {
                        "name":"Leiden University",
                        "city":"Leiden",
                        "country":"the Netherlands"
                    }
                ]
            }
        ]

//empty field
"prizes":[
            {
                "year":"1903",
                "category":"physics",
                "share":"4",
                "motivation":"\"in recognition of the extraordinary services they have rendered by their joint researches on the radiation phenomena discovered by Professor Henri Becquerel\"",
                "affiliations":[
                    []
                ]
            }
        ]

And this is my code for processing the json:

public static void main(String[] args) throws IOException {
    // Get Gson object
    Gson gson = new Gson();

    // read JSON file data as String
    String fileData = new 
    String(Files.readAllBytes(Paths.get("laureates.json")));

    // parse json string to object
    Example laur = gson.fromJson(fileData, Example.class);

    // print object data
    System.out.println("\n\nLaureates Object\n\n" + laur);
}

And I have all my classes set up, i believe it will work once this issue is resolved. The error I'm getting is "Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 3401" (column 3401 is the exact location of the first [])

Crushinz
  • 13
  • 1
  • 3

3 Answers3

0

The correct way to set the empty object is without the brackets. You know that. :-)

"prizes":[
        {
            "year":"1903",
            "category":"physics",
            "share":"4",
            "motivation":"\"in recognition of the extraordinary services they have rendered by their joint researches on the radiation phenomena discovered by Professor Henri Becquerel\"",
            "affiliations":[
            ]
        }
    ]

You maybe make a workaround removing the brackets.

fileData = fileData.replaceAll("\\[]", "");

I hope this helps.

Eduardo Folly
  • 146
  • 2
  • 6
  • Man, I feel silly for not thinking of this. Thank you so much. If I could get rid of the brackets I would lol – Crushinz Apr 02 '18 at 04:12
  • @Crushinz This is a potentially destructive solution because a string can have a `[]` character sequence as well. Also, the replace pattern does not work for `[ ]` or something like that, and it's extremely context-free (what will it do for `property: []`?). You should write a custom type adapter to fix badly-designed JSON documents, and then read the file in as a stream, not as a string for performance and memory usage reasons. – Lyubomyr Shaydariv Apr 02 '18 at 09:51
  • @LyubomyrShaydariv You are right, but with the specifications, should be impossible to solve. Because the json format remains incorrect. Maybe a regex can be a little bit impacting. Sorry Mate. – Eduardo Folly Apr 07 '18 at 21:46
  • @EduardoFolly Please see how it can be accomplished in Gson: https://stackoverflow.com/a/49717234/166589 – Lyubomyr Shaydariv Apr 08 '18 at 11:15
0

Looks like gson is expecting an object but array is returned Try changing Example to an array as follows.

Example[] emps= gson.fromJson(yourJson, Example
[].class);

Also see related GSON throwing "Expected BEGIN_OBJECT but was BEGIN_ARRAY"?

nyulan
  • 311
  • 3
  • 12
0

You can always use a type adapter to adapt bad-designed but well-formed JSON documents. For example, the following type adapter fixes your case:

final class EmptyListFixTypeAdapterFactory
        implements TypeAdapterFactory {

    private static final TypeAdapterFactory instance = new EmptyListFixTypeAdapterFactory();

    private EmptyListFixTypeAdapterFactory() {
    }

    static TypeAdapterFactory get() {
        return instance;
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        // If it's not a list, then just let Gson pass through the rest of the type adapters chain
        if ( !List.class.isAssignableFrom(typeToken.getRawType()) ) {
            return null;
        }
        // Get the original List adapter - we'll use it below
        @SuppressWarnings("unchecked")
        final TypeAdapter<List<Object>> delegateTypeAdapter = (TypeAdapter<List<Object>>) gson.getDelegateAdapter(this, typeToken);
        // Wrap it
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) EmptyListFixTypeAdapter.get(delegateTypeAdapter);
        return typeAdapter;
    }

    private static final class EmptyListFixTypeAdapter<E>
            extends TypeAdapter<List<E>> {

        // JsonParser as of Gson 2.8.2 holds no state
        private static final JsonParser jsonParser = new JsonParser();

        private final TypeAdapter<List<E>> delegateTypeAdapter;

        private EmptyListFixTypeAdapter(final TypeAdapter<List<E>> delegateTypeAdapter) {
            this.delegateTypeAdapter = delegateTypeAdapter;
        }

        private static <E> TypeAdapter<List<E>> get(final TypeAdapter<List<E>> delegateTypeAdapter) {
            return new EmptyListFixTypeAdapter<>(delegateTypeAdapter)
                    .nullSafe(); // A convenient method to add null-checking automatically
        }

        @Override
        public void write(final JsonWriter out, final List<E> value)
                throws IOException {
            // In case if you need to produce document with this quirks
            if ( value.isEmpty() ) {
                out.beginArray();
                out.beginArray();
                out.endArray();
                out.endArray();
                return;
            }
            delegateTypeAdapter.write(out, value);
        }

        @Override
        public List<E> read(final JsonReader in) {
            final JsonElement jsonElement = jsonParser.parse(in);
            final JsonArray array = jsonElement.getAsJsonArray();
            // Is it [[]]?
            if ( array.size() == 1 ) {
                final JsonElement element = array.get(0);
                if ( element.isJsonArray() && ((JsonArray) element).size() == 0 ) {
                    // Yes, detected
                    return new ArrayList<>();
                }
            }
            // No, proceed with the delegate type adapter
            return delegateTypeAdapter.fromJsonTree(array);
        }

    }

}

Now suppose you have the following mappings:

final class Laureate {
    final List<Prize> prizes = new ArrayList<>();
}

final class Prize {
    final int year = Integer.valueOf(0);
    final String category = null;
    final List<Affiliation> affiliations = new ArrayList<>();
}

final class Affiliation {
    final String name = null;
    final String city = null;
    final String country = null;
}

And then:

private static final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(EmptyListFixTypeAdapterFactory.get())
        .create();

private static final Type laureatesType = new TypeToken<List<Laureate>>() {
}.getType();

public static void main(final String... args)
        throws IOException {
    try ( final JsonReader jsonReader = Resources.getPackageResourceJsonReader(Q49603826.class, "laureates.json") ) {
        gson.<List<Laureate>>fromJson(jsonReader, laureatesType)
                .stream()
                .flatMap(laureate -> laureate.prizes.stream())
                .peek(prize -> System.out.println("Prize: " + prize.year + " " + prize.category))
                .flatMap(prize -> prize.affiliations.stream())
                .peek(affiliation -> System.out.println("\tAffiliation: " + affiliation.name + " " + affiliation.city + " " + affiliation.country))
                .forEach(affiliation -> {
                });
    }
}

Output:

Prize: 1902 physics
........Affiliation: Leiden University Leiden the Netherlands
Prize: 1903 physics

Lyubomyr Shaydariv
  • 20,327
  • 12
  • 64
  • 105