All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fr.insee.vtl.jackson.DatasetDeserializer Maven / Gradle / Ivy

The newest version!
package fr.insee.vtl.jackson;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.type.CollectionLikeType;
import com.fasterxml.jackson.databind.util.TokenBuffer;
import fr.insee.vtl.model.Dataset;
import fr.insee.vtl.model.InMemoryDataset;
import fr.insee.vtl.model.Structured;
import fr.insee.vtl.model.utils.Java8Helpers;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import static com.fasterxml.jackson.databind.JsonMappingException.from;

/**
 * DatasetDeserializer is a JSON deserializer specialized for datasets.
 */
public class DatasetDeserializer extends StdDeserializer {

    private static final Set STRUCTURE_NAMES = Java8Helpers.setOf("structure", "dataStructure");
    private static final Set DATAPOINT_NAMES = Java8Helpers.setOf("data", "dataPoints");

    /**
     * Base constructor.
     */
    protected DatasetDeserializer() {
        super(Dataset.class);
    }

    /**
     * Deserializes a JSON dataset into a Dataset object.
     *
     * @param p    The base JSON parser.
     * @param ctxt A deserialization context.
     * @return The deserialized dataset.
     * @throws IOException In case of problem while processing the JSON dataset.
     */
    @Override
    public Dataset deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {

        // Json is an object.
        if (p.currentToken() != JsonToken.START_OBJECT) {
            ctxt.handleUnexpectedToken(Dataset.class, p);
        }

        List structure = null;
        List> dataPoints = null;

        while (p.nextToken() != JsonToken.END_OBJECT) {
            if (STRUCTURE_NAMES.contains(p.currentName())) {
                structure = deserializeStructure(p, ctxt);
                if (dataPoints != null) {
                    convertDataPoints(p, dataPoints, structure);
                }
            } else if (DATAPOINT_NAMES.contains(p.currentName())) {
                if (structure != null) {
                    dataPoints = deserializeDataPoints(p, ctxt, structure);
                } else {
                    dataPoints = deserializeUncheckedDataPoint(p, ctxt);
                }
            }
        }

        return new InMemoryDataset(dataPoints, structure);

    }

    private void convertDataPoints(JsonParser p, List> objects, List components) throws IOException {
        // Create a list of functions for each type. This require the structure
        // to be before the data.
        List deserializers = components.stream()
                .map(PointDeserializer::new)
                .collect(Collectors.toList());

        for (List object : objects) {
            for (int i = 0; i < object.size(); i++) {
                Object converted = deserializers.get(i).convert(p, object.get(i));
                object.set(i, converted);
            }
        }
    }

    private List> deserializeDataPoints(JsonParser p, DeserializationContext ctxt, List components) throws IOException {
        String fieldName = p.currentName();
        if (!DATAPOINT_NAMES.contains(fieldName)) {
            ctxt.handleUnexpectedToken(Dataset.class, p);
        }

        // row != array.
        JsonToken token = p.nextToken();
        if (token != JsonToken.START_ARRAY) {
            ctxt.handleUnexpectedToken(Dataset.class, p);
        }

        // Create a list of functions for each type. This require the structure
        // to be before the data.
        List deserializers = components.stream()
                .map(PointDeserializer::new)
                .collect(Collectors.toList());

        List> dataPoints = new ArrayList<>();
        while (p.nextToken() == JsonToken.START_ARRAY) {
            List row = new ArrayList<>();
            for (PointDeserializer deserializer : deserializers) {
                p.nextValue();
                row.add(deserializer.deserialize(p, ctxt));
            }

            // row > component size.
            if (p.nextToken() != JsonToken.END_ARRAY) {
                ctxt.handleUnexpectedToken(Dataset.class, p);
            }
            dataPoints.add(row);
        }

        return dataPoints;
    }

    private List> deserializeUncheckedDataPoint(JsonParser p, DeserializationContext ctxt) throws IOException {
        String fieldName = p.currentName();
        if (!DATAPOINT_NAMES.contains(fieldName)) {
            ctxt.handleUnexpectedToken(Dataset.class, p);
        }
        p.nextToken();

        CollectionLikeType listOfComponentType = ctxt.getTypeFactory().constructCollectionLikeType(List.class, List.class);
        return ctxt.readValue(p, listOfComponentType);

    }

    private List deserializeStructure(JsonParser p, DeserializationContext ctxt) throws IOException {
        String fieldName = p.currentName();
        if (!STRUCTURE_NAMES.contains(fieldName)) {
            ctxt.handleUnexpectedToken(Dataset.class, p);
        }

        p.nextToken();

        CollectionLikeType listOfComponentType = ctxt.getTypeFactory().constructCollectionLikeType(List.class, Dataset.Component.class);
        return ctxt.readValue(p, listOfComponentType);
    }

    private static class PointDeserializer {

        private final Structured.Component component;

        PointDeserializer(Structured.Component component) {
            this.component = Objects.requireNonNull(component);
        }

        Object convert(JsonParser p, Object object) throws IOException {
            return convert(p.getCodec(), object, component.getType());
        }

        Object convert(ObjectCodec codec, Object node, Class type) throws IOException {
            TokenBuffer buf = new TokenBuffer(codec, false);
            codec.writeValue(buf, node);
            return buf.asParser().readValueAs(type);
        }

        Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            try {
                if (p.currentToken() == JsonToken.VALUE_NULL) {
                    return null;
                } else {
                    return ctxt.readValue(p, component.getType());
                }
            } catch (IOException ioe) {
                throw from(
                        p,
                        String.format("failed to deserialize column %s", component.getName()),
                        ioe
                );
            }
        }
    }
}