CyclicTypeSerTest.java
package com.fasterxml.jackson.databind.ser;
import java.util.*;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
import static org.junit.jupiter.api.Assertions.*;
/**
* Simple unit tests to verify that it is possible to handle
* potentially cyclic structures, as long as object graph itself
* is not cyclic. This is the case for directed hierarchies like
* trees and DAGs.
*/
public class CyclicTypeSerTest
extends DatabindTestUtil
{
static class Bean
{
Bean _next;
final String _name;
public Bean(Bean next, String name) {
_next = next;
_name = name;
}
public Bean getNext() { return _next; }
public String getName() { return _name; }
public void assignNext(Bean n) { _next = n; }
}
@JsonPropertyOrder({ "id", "parent" })
static class Selfie2501 {
public int id;
public Selfie2501 parent;
public Selfie2501(int id) { this.id = id; }
}
@JsonFormat(shape = JsonFormat.Shape.ARRAY)
static class Selfie2501AsArray extends Selfie2501 {
public Selfie2501AsArray(int id) { super(id); }
}
/*
/**********************************************************
/* Types
/**********************************************************
*/
private final ObjectMapper MAPPER = newJsonMapper();
@Test
public void testLinkedButNotCyclic() throws Exception
{
Bean last = new Bean(null, "last");
Bean first = new Bean(last, "first");
Map<String,Object> map = writeAndMap(MAPPER, first);
assertEquals(2, map.size());
assertEquals("first", map.get("name"));
@SuppressWarnings("unchecked")
Map<String,Object> map2 = (Map<String,Object>) map.get("next");
assertNotNull(map2);
assertEquals(2, map2.size());
assertEquals("last", map2.get("name"));
assertNull(map2.get("next"));
}
@Test
public void testSimpleDirectSelfReference() throws Exception
{
Bean selfRef = new Bean(null, "self-refs");
Bean first = new Bean(selfRef, "first");
selfRef.assignNext(selfRef);
Bean[] wrapper = new Bean[] { first };
try {
writeAndMap(MAPPER, wrapper);
} catch (InvalidDefinitionException e) {
verifyException(e, "Direct self-reference leading to cycle");
}
}
// [databind#2501]: Should be possible to replace null cyclic ref
@Test
public void testReplacedCycle() throws Exception
{
Selfie2501 self1 = new Selfie2501(1);
self1.parent = self1;
ObjectWriter w = MAPPER.writer()
.without(SerializationFeature.FAIL_ON_SELF_REFERENCES)
.with(SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL)
;
assertEquals(a2q("{'id':1,'parent':null}"), w.writeValueAsString(self1));
// Also consider a variant of cyclic POJO in container
Selfie2501AsArray self2 = new Selfie2501AsArray(2);
self2.parent = self2;
assertEquals(a2q("[2,null]"), w.writeValueAsString(self2));
}
}