Skip to content

Add DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY #1341

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 25, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,17 @@ public enum DeserializationFeature implements ConfigFeature
*/
FAIL_ON_NULL_CREATOR_PROPERTIES(false),

/**
* Feature that determines what happens when a property annotated with
* {@link com.fasterxml.jackson.annotation.JsonTypeInfo.As#EXTERNAL_PROPERTY} is missing.
* This is disabled by default, so that no error is thrown when a subtype property is
* missing, unless the property is explicitly marked as `required`. If it is enabled, or
* the property is marked as 'required' then a {@link JsonMappingException} will be thrown.
*
* @since 2.8
*/
FAIL_ON_EXTERNAL_TYPE_ID_MISSING_PROPERTY(false),

/**
* Feature that determines whether Jackson code should catch
* and wrap {@link Exception}s (but never {@link Error}s!)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,13 @@ public Object complete(JsonParser p, DeserializationContext ctxt, Object bean)
}
} else if (_tokens[i] == null) {
SettableBeanProperty prop = _properties[i].getProperty();
ctxt.reportMappingException("Missing property '%s' for external type id '%s'",
prop.getName(), _properties[i].getTypePropertyName());

if(prop.isRequired() ||
ctxt.isEnabled(DeserializationFeature.FAIL_ON_EXTERNAL_TYPE_ID_MISSING_PROPERTY)) {
ctxt.reportMappingException("Missing property '%s' for external type id '%s'",
prop.getName(), _properties[i].getTypePropertyName());
}
return bean;
}
_deserializeAndSet(p, ctxt, bean, i, typeId);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
package com.fasterxml.jackson.databind.jsontype;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import java.io.IOException;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;

public class TestSubtypesExternalPropertyMissingProperty {
@Rule
public ExpectedException thrown = ExpectedException.none();

/**
* Base class - external property for Fruit subclasses.
*/
static class Box {
public String type;
@JsonTypeInfo(use = Id.NAME, include = As.EXTERNAL_PROPERTY, property = "type")
public Fruit fruit;

public Box() {
}

public Box(String type, Fruit fruit) {
this.type = type;
this.fruit = fruit;
}
}

/**
* Base class that requires the property to be present.
*/
static class ReqBox {
public String type;
@JsonTypeInfo(use = Id.NAME, include = As.EXTERNAL_PROPERTY, property = "type")
@JsonProperty(required = true)
public Fruit fruit;

public ReqBox() {
}

public ReqBox(String type, Fruit fruit) {
this.type = type;
this.fruit = fruit;
}
}

@JsonSubTypes({
@Type(value = Apple.class, name = "apple"),
@Type(value = Orange.class, name = "orange")
})
static abstract class Fruit {
public String name;

public Fruit() {
}

protected Fruit(String n) {
name = n;
}
}

static class Apple extends Fruit {
public int seedCount;

public Apple() {
}

public Apple(String name, int b) {
super(name);
seedCount = b;
}
}

static class Orange extends Fruit {
public String color;

public Orange() {
}

public Orange(String name, String c) {
super(name);
color = c;
}
}

private final ObjectMapper MAPPER = new ObjectMapper();

/*
/**********************************************************
/* Mock data
/**********************************************************
*/

private static final Orange orange = new Orange("Orange", "orange");
private static final Box orangeBox = new Box("orange", orange);
private static final String orangeBoxJson = "{\"type\":\"orange\",\"fruit\":{\"name\":\"Orange\",\"color\":\"orange\"}}";
private static final String orangeBoxNullJson = "{\"type\":\"orange\",\"fruit\":null}}";
private static final String orangeBoxEmptyJson = "{\"type\":\"orange\",\"fruit\":{}}}";
private static final String orangeBoxMissingJson = "{\"type\":\"orange\"}}";

private static final Apple apple = new Apple("Apple", 16);
private static Box appleBox = new Box("apple", apple);
private static final String appleBoxJson = "{\"type\":\"apple\",\"fruit\":{\"name\":\"Apple\",\"seedCount\":16}}";
private static final String appleBoxNullJson = "{\"type\":\"apple\",\"fruit\":null}";
private static final String appleBoxEmptyJson = "{\"type\":\"apple\",\"fruit\":{}}";
private static final String appleBoxMissingJson = "{\"type\":\"apple\"}";

/*
/**********************************************************
/* Unit tests
/**********************************************************
*/

/**
* Deserialization tests for external type id property present
*/
@Test
public void testDeserializationPresent() throws Exception {
MAPPER.disable(DeserializationFeature.FAIL_ON_EXTERNAL_TYPE_ID_MISSING_PROPERTY);
checkOrangeBox();
checkAppleBox();

MAPPER.enable(DeserializationFeature.FAIL_ON_EXTERNAL_TYPE_ID_MISSING_PROPERTY);
checkOrangeBox();
checkAppleBox();
}

/**
* Deserialization tests for external type id property null
*/
@Test
public void testDeserializationNull() throws Exception {
MAPPER.disable(DeserializationFeature.FAIL_ON_EXTERNAL_TYPE_ID_MISSING_PROPERTY);
checkOrangeBoxNull(orangeBoxNullJson);
checkAppleBoxNull(appleBoxNullJson);

MAPPER.enable(DeserializationFeature.FAIL_ON_EXTERNAL_TYPE_ID_MISSING_PROPERTY);
checkOrangeBoxNull(orangeBoxNullJson);
checkAppleBoxNull(appleBoxNullJson);
}

/**
* Deserialization tests for external type id property empty
*/
@Test
public void testDeserializationEmpty() throws Exception {
MAPPER.disable(DeserializationFeature.FAIL_ON_EXTERNAL_TYPE_ID_MISSING_PROPERTY);
checkOrangeBoxEmpty(orangeBoxEmptyJson);
checkAppleBoxEmpty(appleBoxEmptyJson);

MAPPER.enable(DeserializationFeature.FAIL_ON_EXTERNAL_TYPE_ID_MISSING_PROPERTY);
checkOrangeBoxEmpty(orangeBoxEmptyJson);
checkAppleBoxEmpty(appleBoxEmptyJson);
}

/**
* Deserialization tests for external type id property missing
*/
@Test
public void testDeserializationMissing() throws Exception {
MAPPER.disable(DeserializationFeature.FAIL_ON_EXTERNAL_TYPE_ID_MISSING_PROPERTY);
checkOrangeBoxNull(orangeBoxMissingJson);
checkAppleBoxNull(appleBoxMissingJson);

MAPPER.enable(DeserializationFeature.FAIL_ON_EXTERNAL_TYPE_ID_MISSING_PROPERTY);
checkBoxJsonMappingException(orangeBoxMissingJson);
checkBoxJsonMappingException(appleBoxMissingJson);
}

/**
* Deserialization tests for external type id required property missing
*/
@Test
public void testDeserializationMissingRequired() throws Exception {
MAPPER.disable(DeserializationFeature.FAIL_ON_EXTERNAL_TYPE_ID_MISSING_PROPERTY);
checkReqBoxJsonMappingException(orangeBoxMissingJson);
checkReqBoxJsonMappingException(appleBoxMissingJson);

MAPPER.enable(DeserializationFeature.FAIL_ON_EXTERNAL_TYPE_ID_MISSING_PROPERTY);
checkReqBoxJsonMappingException(orangeBoxMissingJson);
checkReqBoxJsonMappingException(appleBoxMissingJson);
}

private void checkOrangeBox() throws Exception {
Box deserOrangeBox = MAPPER.readValue(orangeBoxJson, Box.class);
assertEquals(orangeBox.type, deserOrangeBox.type);

Fruit deserOrange = deserOrangeBox.fruit;
assertSame(Orange.class, deserOrange.getClass());
assertEquals(orange.name, deserOrange.name);
assertEquals(orange.color, ((Orange) deserOrange).color);
}

private void checkAppleBox() throws Exception {
Box deserAppleBox = MAPPER.readValue(appleBoxJson, Box.class);
assertEquals(appleBox.type, deserAppleBox.type);

Fruit deserApple = deserAppleBox.fruit;
assertSame(Apple.class, deserApple.getClass());
assertEquals(apple.name, deserApple.name);
assertEquals(apple.seedCount, ((Apple) deserApple).seedCount);
}

private void checkOrangeBoxEmpty(String json) throws Exception {
Box deserOrangeBox = MAPPER.readValue(json, Box.class);
assertEquals(orangeBox.type, deserOrangeBox.type);

Fruit deserOrange = deserOrangeBox.fruit;
assertSame(Orange.class, deserOrange.getClass());
assertNull(deserOrange.name);
assertNull(((Orange) deserOrange).color);
}

private void checkAppleBoxEmpty(String json) throws Exception {
Box deserAppleBox = MAPPER.readValue(json, Box.class);
assertEquals(appleBox.type, deserAppleBox.type);

Fruit deserApple = deserAppleBox.fruit;
assertSame(Apple.class, deserApple.getClass());
assertNull(deserApple.name);
assertEquals(0, ((Apple) deserApple).seedCount);
}

private void checkOrangeBoxNull(String json) throws Exception {
Box deserOrangeBox = MAPPER.readValue(json, Box.class);
assertEquals(orangeBox.type, deserOrangeBox.type);
assertNull(deserOrangeBox.fruit);
}

private void checkAppleBoxNull(String json) throws Exception {
Box deserAppleBox = MAPPER.readValue(json, Box.class);
assertEquals(appleBox.type, deserAppleBox.type);
assertNull(deserAppleBox.fruit);
}

private void checkBoxJsonMappingException(String json) throws Exception {
thrown.expect(JsonMappingException.class);
thrown.expectMessage("Missing property 'fruit' for external type id 'type'");
MAPPER.readValue(json, Box.class);
}

private void checkReqBoxJsonMappingException(String json) throws Exception {
thrown.expect(JsonMappingException.class);
thrown.expectMessage("Missing property 'fruit' for external type id 'type'");
MAPPER.readValue(json, ReqBox.class);
}
}