io.github.stewseo.client.json.ObjectDeserializer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of yelp-fusion-client Show documentation
Show all versions of yelp-fusion-client Show documentation
java client to build api objects, handle http transport, and parse/deserialize/serialize json to/from json
package io.github.stewseo.client.json;
import io.github.stewseo.client.util.QuadConsumer;
import jakarta.json.stream.JsonParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.ObjIntConsumer;
import java.util.function.Supplier;
public class ObjectDeserializer implements JsonpDeserializer {
private static Logger logger = LoggerFactory.getLogger(ObjectDeserializer.class);
public abstract static class FieldDeserializer {
protected final String name;
public FieldDeserializer(String name) {
this.name = name;
}
public abstract EnumSet acceptedEvents();
public abstract void deserialize(JsonParser parser, JsonpMapper mapper, String fieldName, ObjectType object);
public abstract void deserialize(JsonParser parser, JsonpMapper mapper, String fieldName, ObjectType object, JsonParser.Event event);
}
public static class FieldObjectDeserializer extends FieldDeserializer {
private final BiConsumer setter;
private final JsonpDeserializer deserializer;
public FieldObjectDeserializer(
BiConsumer setter, JsonpDeserializer deserializer,
String name
) {
super(name);
this.setter = setter;
this.deserializer = deserializer;
}
public String name() {
return this.name;
}
@Override
public EnumSet acceptedEvents() {
return deserializer.acceptedEvents();
}
public void deserialize(JsonParser parser, JsonpMapper mapper, String fieldName, ObjectType object) {
FieldType fieldValue = deserializer.deserialize(parser, mapper);
setter.accept(object, fieldValue);
}
public void deserialize(JsonParser parser, JsonpMapper mapper, String fieldName, ObjectType object, JsonParser.Event event) {
JsonpUtils.ensureAccepts(deserializer, parser, event);
FieldType fieldValue = deserializer.deserialize(parser, mapper, event);
setter.accept(object, fieldValue);
}
}
private static final FieldDeserializer> IGNORED_FIELD = new FieldDeserializer<>("-") {
@Override
public void deserialize(JsonParser parser, JsonpMapper mapper, String fieldName, Object object) {
JsonpUtils.skipValue(parser);
}
@Override
public void deserialize(JsonParser parser, JsonpMapper mapper, String fieldName, Object object, JsonParser.Event event) {
JsonpUtils.skipValue(parser, event);
}
@Override
public EnumSet acceptedEvents() {
return EnumSet.allOf(JsonParser.Event.class);
}
};
//---------------------------------------------------------------------------------------------
private static final EnumSet EventSetObject = EnumSet.of(JsonParser.Event.START_OBJECT, JsonParser.Event.KEY_NAME);
private static final EnumSet EventSetObjectAndString = EnumSet.of(JsonParser.Event.START_OBJECT, JsonParser.Event.VALUE_STRING, JsonParser.Event.KEY_NAME);
private EnumSet acceptedEvents = EventSetObject; // May be changed in `shortcutProperty()`
private final Supplier constructor;
protected final Map> fieldDeserializers;
private FieldDeserializer singleKey;
private String typeProperty;
private String defaultType;
private FieldDeserializer shortcutProperty;
private QuadConsumer unknownFieldHandler;
public ObjectDeserializer(Supplier constructor) {
this.constructor = constructor;
this.fieldDeserializers = new HashMap<>();
}
public Set fieldNames() {
return Collections.unmodifiableSet(fieldDeserializers.keySet());
}
public String shortcutProperty() {
return this.shortcutProperty == null ? null : this.shortcutProperty.name;
}
@Override
public EnumSet nativeEvents() {
// May also return string if we have a shortcut property. This is needed to identify ambiguous unions.
return acceptedEvents;
}
@Override
public EnumSet acceptedEvents() {
return acceptedEvents;
}
public ObjectType deserialize(JsonParser parser, JsonpMapper mapper, JsonParser.Event event) {
return deserialize(constructor.get(), parser, mapper, event);
}
public ObjectType deserialize(ObjectType value, JsonParser parser, JsonpMapper mapper, JsonParser.Event event) {
if (event == JsonParser.Event.VALUE_NULL) {
return null;
}
String keyName = null;
String fieldName = null;
try {
if (singleKey != null) {
// There's a wrapping property whose name is the key value
if (event == JsonParser.Event.START_OBJECT) {
event = JsonpUtils.expectNextEvent(parser, JsonParser.Event.KEY_NAME);
}
singleKey.deserialize(parser, mapper, null, value, event);
event = parser.next();
}
if (shortcutProperty != null && event != JsonParser.Event.START_OBJECT && event != JsonParser.Event.KEY_NAME) {
// This is the shortcut property (should be a value event, this will be checked by its deserializer)
shortcutProperty.deserialize(parser, mapper, shortcutProperty.name, value, event);
} else if (typeProperty == null) {
if (event != JsonParser.Event.START_OBJECT && event != JsonParser.Event.KEY_NAME) {
// Report we're waiting for a start_object, since this is the most common beginning for object parser
JsonpUtils.expectEvent(parser, JsonParser.Event.START_OBJECT, event);
}
if (event == JsonParser.Event.START_OBJECT) {
event = parser.next();
}
// Regular object: read all properties until we reach the end of the object
while (event != JsonParser.Event.END_OBJECT) {
JsonpUtils.expectEvent(parser, JsonParser.Event.KEY_NAME, event);
fieldName = parser.getString();
FieldDeserializer fieldDeserializer = fieldDeserializers.get(fieldName);
if (fieldDeserializer == null) {
parseUnknownField(parser, mapper, fieldName, value);
} else {
fieldDeserializer.deserialize(parser, mapper, fieldName, value);
}
event = parser.next();
}
fieldName = null;
} else {
// Union variant: find the property to find the proper deserializer
// We cannot start with a key name here.
JsonpUtils.expectEvent(parser, JsonParser.Event.START_OBJECT, event);
Map.Entry unionInfo = JsonpUtils.lookAheadFieldValue(typeProperty, defaultType, parser, mapper);
String variant = unionInfo.getKey();
JsonParser innerParser = unionInfo.getValue();
FieldDeserializer fieldDeserializer = fieldDeserializers.get(variant);
if (fieldDeserializer == null) {
parseUnknownField(innerParser, mapper, variant, value);
} else {
fieldDeserializer.deserialize(innerParser, mapper, variant, value);
}
}
if (singleKey != null) {
JsonpUtils.expectNextEvent(parser, JsonParser.Event.END_OBJECT);
}
} catch (Exception e) {
// Add key name (for single key dicts) and field name if present
throw JsonpMappingException.from(e, value, fieldName, parser).prepend(value, keyName);
}
return value;
}
protected void parseUnknownField(JsonParser parser, JsonpMapper mapper, String fieldName, ObjectType object) {
if (this.unknownFieldHandler != null) {
this.unknownFieldHandler.accept(object, fieldName, parser, mapper);
} else if (mapper.ignoreUnknownFields()) {
JsonpUtils.skipValue(parser);
} else {
// Context is added by the caller
throw new JsonpMappingException("Unknown field '" + fieldName + "'", parser.getLocation());
}
}
public void setUnknownFieldHandler(QuadConsumer unknownFieldHandler) {
this.unknownFieldHandler = unknownFieldHandler;
}
@SuppressWarnings("unchecked")
public void ignore(String name) {
this.fieldDeserializers.put(name, (FieldDeserializer) IGNORED_FIELD);
}
public void shortcutProperty(String name) {
this.shortcutProperty = this.fieldDeserializers.get(name);
if (this.shortcutProperty == null) {
throw new NoSuchElementException("No deserializer was setup for '" + name + "'");
}
acceptedEvents = EnumSet.copyOf(acceptedEvents);
acceptedEvents.addAll(shortcutProperty.acceptedEvents());
acceptedEvents = EventSetObjectAndString;
}
//----- Object types
public void add(
BiConsumer setter,
JsonpDeserializer deserializer,
String name
) {
FieldObjectDeserializer fieldDeserializer =
new FieldObjectDeserializer<>(setter, deserializer, name);
this.fieldDeserializers.put(name, fieldDeserializer);
}
public void add(
BiConsumer setter,
JsonpDeserializer deserializer,
String name, String... aliases
) {
FieldObjectDeserializer fieldDeserializer =
new FieldObjectDeserializer<>(setter, deserializer, name);
this.fieldDeserializers.put(name, fieldDeserializer);
for (String alias: aliases) {
this.fieldDeserializers.put(alias, fieldDeserializer);
}
}
public void setKey(BiConsumer setter, JsonpDeserializer deserializer) {
this.singleKey = new FieldObjectDeserializer<>(setter, deserializer, null);
}
public void setTypeProperty(String name, String defaultType) {
this.typeProperty = name;
this.defaultType = defaultType;
if (this.unknownFieldHandler == null) {
this.unknownFieldHandler = (o, value, parser, mapper) -> {
// Context is added by the caller
throw new JsonpMappingException("Unknown '" + name + "' value: '" + value + "'", parser.getLocation());
};
}
}
//----- Primitive types
public void add(ObjIntConsumer setter, String name, String... deprecatedNames) {
add(setter::accept, JsonpDeserializer.integerDeserializer(), name, deprecatedNames);
}
public static class JsonpIntParser {
public int parse(JsonParser parser) {
JsonpUtils.expectNextEvent(parser, JsonParser.Event.VALUE_NUMBER);
return parser.getInt();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy