Heterogenous Containers... JSON (Part 3)
/**
* A collection of pairs ordered based on the order of their {@link Element}s
*
* @see ElementBuilder
*/
public class PairSet implements Iterable<Pair<?>>
{
/**
* Create a new JSON object with the specified pairs
*
* @param pairs the pairs to the JSON object
* @return a JsonObject with the specified pairs
*/
public static final PairSet of(Pair<?>... pairs)
{
PairSet pairSet = new PairSet();
for (Pair<?> pair : pairs) {
pairSet.add(pair);
}
return pairSet;
}
/*
* Using TreeMap implementation for sorting
*/
private final Map<Element<?>, Pair<?>> pairs =
new TreeMap<Element<?>, Pair<?>>();
/**
* Adds a new pair value
* @param <T> the pair type
* @param pair the pair to add
*/
public <T> PairSet add(Pair<T> pair)
{
this.pairs.put(pair.element(), pair);
return this;
}
@Override
public Iterator<Pair<?>> iterator(){
return this.pairs.values().iterator();
}
/**
*
* @param <T>
* @param element the type of the pair
* @return the pair instance
* @see Element#type()
*/
public <T> T get(Element<T> element) {
return element.type().cast( this.pairs.get(element).value() );
}
public int size(){
return this.pairs.size();
}
}
/**
* A non-complex JSON object
*/
public class JsonObject
{
public static JsonObject of(Pair<?>... pairs) {
return new JsonObject( PairSet.of(pairs) );
}
private static final Element<Type> LAST_ELEMENT = Elements.TYPE;
private static final String LEFT_BRACE = "{";
private static final String RIGHT_BRACE = "}";
private static final String COMMA = ",";
private final PairSet pairs;
JsonObject(PairSet pairs)
{
this.pairs = pairs;
}
private boolean isLast(Pair<?> pair){
return LAST_ELEMENT.equals(pair.element());
}
private void appendEveryPairOf(StringBuilder builder, PairSet pairs)
{
for (Pair<?> pair : pairs)
{
builder.append( pair );
appendCommaIfNotLast(builder, pair);
}
}
private void appendCommaIfNotLast(StringBuilder builder, Pair<?> pair)
{
boolean isLast = isLast(pair);
if(!isLast){
builder.append( COMMA );
}
}
/**
*
* @param <T>
* @param element the element which is present in the JSON object
* @return its value
*/
public <T> T valueOf(Element<T> element){
return this.pairs.get(element);
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append(LEFT_BRACE);
appendEveryPairOf(builder, this.pairs);
builder.append(RIGHT_BRACE);
return builder.toString();
}
}
/**
*/
@RunWith(Theories.class)
public class PairSetTest
{
private static final Integer ID_VALUE = 1;
private static final Pair<Integer> ID_PAIR =
Pair.newPair(Elements.ID, ID_VALUE);
private static final String NAME_VALUE = "Kyle Bragger";
private static final Pair<String> NAME_PAIR =
Pair.newPair(Elements.NAME, NAME_VALUE);
private static final Date DATE_VALUE =
Calendar.getInstance().getTime();
private static final Pair<Date> DATE_PAIR =
Pair.newPair(Elements.DATE, DATE_VALUE);
private static final Type TYPE_VALUE = Type.DEVELOPER;
private static final Pair<Type> TYPE_PAIR =
Pair.newPair(Elements.TYPE, TYPE_VALUE);
private static final Pair<?>[] PAIRS =
new Pair<?>[]{
ID_PAIR,
NAME_PAIR,
DATE_PAIR,
TYPE_PAIR};
@DataPoints
public static final Element<?>[] ELEMENTS =
{Elements.ID, Elements.NAME, Elements.TYPE, Elements.DATE};
/**
* @param pairs
* @param element
*/
private <T> void assertNotNull(PairSet pairs, Element<T> element)
{
T value = pairs.get(element);
Assert.assertNotNull(value);
}
@Test
public void of() throws Exception
{
PairSet pairs = PairSet.of( PairSetTest.PAIRS );
Assert.assertEquals(4, pairs.size());
}
@Theory
public void get(Element<?> element) throws Exception
{
PairSet pairs = PairSet.of( PairSetTest.PAIRS );
assertNotNull(pairs, element);
}
}
/**
*/
public class JsonObjectTest
{
private static final Integer ID_VALUE = 1;
private static final Pair<Integer> ID_PAIR =
Pair.newPair(Elements.ID, ID_VALUE);
private static final String NAME_VALUE = "Kyle Bragger";
private static final Pair<String> NAME_PAIR =
Pair.newPair(Elements.NAME, NAME_VALUE);
private static final Date DATE_VALUE =
Calendar.getInstance().getTime();
private static final Pair<Date> DATE_PAIR =
Pair.newPair(Elements.DATE, DATE_VALUE);
private static final Type TYPE_VALUE = Type.DEVELOPER;
private static final Pair<Type> TYPE_PAIR =
Pair.newPair(Elements.TYPE, TYPE_VALUE);
private static final Pair<?>[] PAIRS =
new Pair<?>[]{
ID_PAIR,
NAME_PAIR,
DATE_PAIR,
TYPE_PAIR};
private static final String toString =
String.format({{"{%s"}},%s,%s,%s}", ID_PAIR, NAME_PAIR, DATE_PAIR, TYPE_PAIR);
@Test
public void string() throws Exception
{
JsonObject jsonObject = JsonObject.of( PAIRS );
Assert.assertEquals(toString, jsonObject.toString());
}
@Test
public void valueOfId() throws Exception
{
JsonObject jsonObject = JsonObject.of( PAIRS );
Assert.assertEquals(ID_VALUE, jsonObject.valueOf(Elements.ID));
}
@Test
public void valueOfName() throws Exception
{
JsonObject jsonObject = JsonObject.of( PAIRS );
Assert.assertEquals(NAME_VALUE, jsonObject.valueOf(Elements.NAME));
}
@Test
public void valueOfType() throws Exception
{
JsonObject jsonObject = JsonObject.of( PAIRS );
Assert.assertEquals(TYPE_VALUE, jsonObject.valueOf(Elements.TYPE));
}
@Test
public void valueOfDate() throws Exception
{
JsonObject jsonObject = JsonObject.of( PAIRS );
Assert.assertEquals(DATE_VALUE, jsonObject.valueOf(Elements.DATE));
}
}
This is the final post of the “How to think of JSON” series using heterogeneous containers.
In this example the PairSet plays the role of such a container backed by a TreeMap. The TreeMap implementation is used to keep our elements sorted based on their natural ordering which in this case merely helps us assert the JsonObject#toString method.
Note how the PairSet class also implements the Iterable
Iterator<T> iterator();
method which allows us to use the enhanced for loop.
for (Pair<?> pair : pairs)
{
builder.append( pair );
appendCommaIfNotLast(builder, pair);
}
Make sure you incorporate it in classes that represent collections of objects.
As it all comes together (Element, ElementFormat, Pair, PairSet) we end up with a JsonObject class which not only allows us to query for any JSON element to get its value back to the right type but get a properly formatted String representation.
Once acquainted feel free to leave your comments below on how heterogeneous containers think can aid your design.