Gson a-t-il un moyen de lire des fichiers JSON non standard?

Au lieu d'un fichier typique comme:

[{obj1},{objN}]

J'ai un fichier comme celui-ci:

{obj1}
{objN}

Où il n'y a pas de crochets ni de virgules et chaque objet est séparé par un caractère de nouvelle ligne.

1
ekjcfn3902039 20 avril 2017 à 22:24

3 réponses

Meilleure réponse

Oui, c'est le cas. Gson prend en charge la lecture indulgente. Par exemple, le document JSON suivant (non-standard.json):

{
    "foo": 1
}
{
    "bar": 1
}

Vous pouvez utiliser la méthode de lecture suivante:

private static final Gson gson = new Gson();
private static final TypeAdapter<JsonElement> jsonElementTypeAdapter = gson.getAdapter(JsonElement.class);

public static void main(final String... args)
        throws IOException {
    try ( final Reader reader = getPackageResourceReader(Q43528208.class, "non-standard.json") ) {
        final JsonReader jsonReader = new JsonReader(reader);
        jsonReader.setLenient(true); // this makes it work
        while ( jsonReader.peek() != END_DOCUMENT ) {
            final JsonElement jsonElement = jsonElementTypeAdapter.read(jsonReader);
            System.out.println(jsonElement);
        }
    }
}

Production:

{"foo":1}  
{"bar":1}  

Je ne sais pas si vous pouvez écrire un désérialiseur robuste de cette façon.

Mise à jour

Afin de simplifier le support de Gson, nous pouvons implémenter quelques méthodes de lecture pratiques:

// A shortcut method for the below implementation: aggregates the whole result into a single list
private static <T> List<T> parseToListLenient(final JsonReader jsonReader, final IMapper<? super JsonReader, ? extends T> mapper)
        throws IOException {
    final List<T> list = new ArrayList<>();
    parseLenient(jsonReader, in -> list.add(mapper.map(in)));
    return list;
}

// A convenient strategy-accepting method to configure a JsonReader instance to make it lenient and do read
// The consumer defines the strategy what to do with the current JsonReader token
private static void parseLenient(final JsonReader jsonReader, final IConsumer<? super JsonReader> consumer)
        throws IOException {
    final boolean isLenient = jsonReader.isLenient();
    try {
        jsonReader.setLenient(true);
        while ( jsonReader.peek() != END_DOCUMENT ) {
            consumer.accept(jsonReader);
        }
    } finally {
        jsonReader.setLenient(isLenient);
    }
}

// Since Java 8 Consumer inteface does not allow checked exceptions to be rethrown
private interface IConsumer<T> {

    void accept(T value)
            throws IOException;

}

private interface IMapper<T, R> {

    R map(T value)
            throws IOException;

}

Ensuite, la lecture simple est vraiment simple, et nous pouvons simplement utiliser les méthodes ci-dessus:

final Gson gson = new Gson();
final TypeToken<Map<String, Integer>> typeToken = new TypeToken<Map<String, Integer>>() {
};
final TypeAdapter<Map<String, Integer>> typeAdapter = gson.getAdapter(typeToken);
try ( final JsonReader jsonReader = getPackageResourceJsonReader(Q43528208.class, "non-standard.json") ) {
    final List<Map<String, Integer>> maps = parseToListLenient(jsonReader, typeAdapter::read);
    System.out.println(maps);
}

La désérialisation via Gson directement nécessiterait une implémentation plus compliquée:

// This is just a marker not meant to be instantiated but to create a sort of "gateway" to dispatch types in Gson
@SuppressWarnings("unused")
private static final class LenientListMarker<T> {
    private LenientListMarker() {
        throw new AssertionError("must not be instantiated");
    }
}

private static void doDeserialize()
        throws IOException {
    final Gson gson = new GsonBuilder()
            .registerTypeAdapterFactory(new TypeAdapterFactory() {
                @Override
                public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
                    // Check if the given type is the lenient list marker class
                    if ( !LenientListMarker.class.isAssignableFrom(typeToken.getRawType()) ) {
                        // Not the case? Just delegate the job to Gson
                        return null;
                    }
                    final Type listElementType = getTypeParameter0(typeToken.getType());
                    final TypeAdapter<?> listElementAdapter = gson.getAdapter(TypeToken.get(listElementType));
                    @SuppressWarnings("unchecked")
                    final TypeToken<List<?>> listTypeToken = (TypeToken<List<?>>) TypeToken.getParameterized(List.class, listElementType);
                    final TypeAdapter<List<?>> listAdapter = gson.getAdapter(listTypeToken);
                    final TypeAdapter<List<?>> typeAdapter = new TypeAdapter<List<?>>() {
                        @Override
                        public void write(final JsonWriter out, final List<?> value)
                                throws IOException {
                            // Always write a well-formed list
                            listAdapter.write(out, value);
                        }

                        @Override
                        public List<?> read(final JsonReader in)
                                throws IOException {
                            // Delegate the job to the reading method - we only have to tell how to obtain the list values
                            return parseToListLenient(in, listElementAdapter::read);
                        }
                    };
                    @SuppressWarnings("unchecked")
                    final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) typeAdapter;
                    return castTypeAdapter;
                }

                // A simple method to resolve actual type parameter
                private Type getTypeParameter0(final Type type) {
                    if ( !(type instanceof ParameterizedType) ) {
                        // List or List<?>
                        return Object.class;
                    }
                    return ((ParameterizedType) type).getActualTypeArguments()[0];
                }
            })
            .create();
    // This type declares a marker specialization to be used during deserialization
    final Type type = new TypeToken<LenientListMarker<Map<String, Integer>>>() {
    }.getType();
    try ( final JsonReader jsonReader = getPackageResourceJsonReader(Q43528208.class, "non-standard.json") ) {
        // This is where we're a sort of cheating:
        // We tell Gson to deserialize LenientListMarker<Map<String, Integer>> but the type adapter above will return a list
        final List<Map<String, Integer>> maps = gson.fromJson(jsonReader, type);
        System.out.println(maps);
    }
}

La sortie est maintenant pour Map<String, Integer> s, pas pour JsonElement s:

[{foo = 1}, {bar = 1}]

Update 2

TypeToken.getParameterized solution de contournement:

@SuppressWarnings("unchecked")
final TypeToken<List<?>> listTypeToken = (TypeToken<List<?>>) TypeToken.get(new ParameterizedType() {
    @Override
    public Type getRawType() {
        return List.class;
    }

    @Override
    public Type[] getActualTypeArguments() {
        return new Type[]{ listElementType };
    }

    @Override
    public Type getOwnerType() {
        return null;
    }
});
1
Lyubomyr Shaydariv 21 avril 2017 à 13:25

Avec spark 2, nous pouvons ajouter la multiligne comme option de lecture.

spark.df.option("multiline","true").json("data.json")
0
Ismael Padilla 28 janv. 2020 à 22:55

Nous pouvons avoir un programme de plus pour introduire la virgule (,) et construire un JSON bien formé

0
Vishnu Prasath 20 avril 2017 à 19:43