Skip to content

Commit

Permalink
Dominic: serialisation to json
Browse files Browse the repository at this point in the history
  • Loading branch information
Dominic Fox committed Apr 4, 2014
1 parent db5d3f1 commit 3f033cb
Show file tree
Hide file tree
Showing 23 changed files with 485 additions and 64 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions octarine-core/octarine-core.iml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<orderEntry type="library" name="Maven: junit:junit-dep:4.8.2" level="project" />
<orderEntry type="library" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />
<orderEntry type="library" name="Maven: org.pcollections:pcollections:2.1.2" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.3.1" level="project" />
</component>
</module>

5 changes: 5 additions & 0 deletions octarine-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
<artifactId>pcollections</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.3.1</version>
</dependency>
</dependencies>


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.codepoetics.octarine.json;

import com.codepoetics.octarine.records.Key;
import com.codepoetics.octarine.records.Record;
import com.codepoetics.octarine.records.Serialiser;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;

import java.io.IOException;
import java.io.Writer;
import java.util.function.BiConsumer;

public interface JsonSerialiser extends Serialiser<JsonGenerator> {

@Override default void toWriter(Record record, Writer writer) throws IOException {
JsonGenerator jsonWriter = new JsonFactory().createGenerator(writer);
try {
writeRecord(record, jsonWriter);
} catch (JsonWritingException e) {
e.throwCause();
}
jsonWriter.flush();
}

@Override default void startRecord(JsonGenerator writer) {
try {
writer.writeStartObject();
} catch (IOException e) {
throw new JsonWritingException(e);
}
}

@Override default void nextProjection(JsonGenerator writer) {
// Do nothing
}

@Override default void endRecord(JsonGenerator writer) {
try {
writer.writeEndObject();
} catch (IOException e) {
throw new JsonWritingException(e);
}
}

@Override default void startList(JsonGenerator writer) {
try {
writer.writeStartArray();
} catch (IOException e) {
throw new JsonWritingException(e);
}
}

@Override default void nextValue(JsonGenerator writer) {
// Do nothing
}

@Override default void endList(JsonGenerator writer) {
try {
writer.writeEndArray();
} catch (IOException e) {
throw new JsonWritingException(e);
}
}

@Override default void writeKeyName(Key<?> key, String keyName, JsonGenerator writer) {
try {
writer.writeFieldName(keyName);
} catch (IOException e) {
throw new JsonWritingException(e);
}
}

static BiConsumer<String, JsonGenerator> asString =
(s, j) -> {
try {
j.writeString(s);
} catch (IOException e) {
throw new JsonWritingException(e);
}
};

static BiConsumer<Boolean, JsonGenerator> asBoolean =
(b, j) -> {
try {
j.writeBoolean(b);
} catch (IOException e) {
throw new JsonWritingException(e);
}
};

static BiConsumer<Integer, JsonGenerator> asInteger =
(i, j) -> {
try {
j.writeNumber(i);
} catch (IOException e) {
throw new JsonWritingException(e);
}
};


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.codepoetics.octarine.json;


import java.io.IOException;

public class JsonWritingException extends RuntimeException {

public JsonWritingException(IOException cause) {
super(cause);
}

void throwCause() throws IOException {
throw (IOException) getCause();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.codepoetics.octarine.records;

import com.codepoetics.octarine.lenses.Lens;
import com.codepoetics.octarine.lenses.OptionalLens;

import java.util.Optional;
Expand Down Expand Up @@ -56,4 +57,5 @@ default Value of(T value) {
@Override public Object value() { return value; }
};
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.codepoetics.octarine.records;

import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;

public interface Projections<T> extends Function<BiConsumer<Record, T>, Projections<T>>, Consumer<BiConsumer<Record, T>> {
default Projections<T> apply(BiConsumer<Record, T> projection) {
accept(projection);
return this;
}

default <V> Projections<T> add(Key<V> key, BiConsumer<? super V, T> valueSerialiser) {
return add(key, key.name(), valueSerialiser);
}

default <V> Projections<T> add(Key<V> key, String keyName, BiConsumer<? super V, T> valueSerialiser) {
return apply((r, t) -> {
Optional<V> value = key.from(r);
if (value.isPresent()) {
writeKeyName(key, keyName, t);
valueSerialiser.accept(value.get(), t);
}
});
}

void writeKeyName(Key<?> key, String keyName, T writer);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package com.codepoetics.octarine.records;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;

public interface Serialiser<T> extends BiConsumer<Record, T> {

default String toString(Record record) {
StringWriter writer = new StringWriter();
try {
toWriter(record, writer);
} catch (IOException e) {
throw new RuntimeException(e);
}
writer.flush();
return writer.toString();
}

void toWriter(Record record, Writer writer) throws IOException;

void projecting(Projections<T> projections);

void startRecord(T writer);
void endRecord(T writer);
void nextProjection(T writer);

default void accept(Record record, T writer) {
writeRecord(record, writer);
}

default void writeRecord(Record record, T writer) {
startRecord(writer);
projecting(new Projections<T>() {
private boolean first = true;
@Override
public void accept(BiConsumer<Record, T> projector) {
if (first) { first = false; } else { nextProjection(writer); }
projector.accept(record, writer);
}
@Override
public void writeKeyName(Key<?> key, String keyName, T writer) {
Serialiser.this.writeKeyName(key, keyName, writer);
}
});
endRecord(writer);
}

void writeKeyName(Key<?> key, String keyName, T writer);

void startList(T writer);
void endList(T writer);
void nextValue(T writer);

default <V> void writeList(List<V> values, BiConsumer<V, T> valueSerialiser, T writer) {
startList(writer);
values.forEach(new Consumer<V>() {
private boolean first = true;
@Override
public void accept(V v) {
if (first) { first = false; } else { nextValue(writer); }
writeValue(v, valueSerialiser, writer);
}
});
endList(writer);
}

default <V> void writeValue(V value, BiConsumer<V, T> valueSerialiser, T writer) {
valueSerialiser.accept(value, writer);
}

default <V> BiConsumer<List<V>, T> asList(BiConsumer<V, T> valueSerialiser) {
return (vs, t) -> {
Serialiser.this.<V>writeList(vs, valueSerialiser, t);
};
}

default <V1, V2, T> BiConsumer<V1, T> map(Function<V1, V2> f, BiConsumer<V2, T> consumer) {
return (v1, t) -> consumer.accept(f.apply(v1), t);
}

interface Bound<T> {
void writeRecord(Record record);
<V> void writeList(List<V> values, BiConsumer<V, T> valueSerialiser);
<V> void writeValue(V value, BiConsumer<V, T> valueSerialiser);
}

default Bound<T> using(T writer) {
return new Bound<T>() {

@Override
public void writeRecord(Record record) {
Serialiser.this.writeRecord(record, writer);
}

@Override
public <V> void writeList(List<V> values, BiConsumer<V, T> valueSerialiser) {
Serialiser.this.writeList(values, valueSerialiser, writer);
}

@Override
public <V> void writeValue(V value, BiConsumer<V, T> valueSerialiser) {
Serialiser.this.writeValue(value, valueSerialiser, writer);
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

public class RecordTest {


private static final Key<String> displayName = Key.named("displayName");
private static final Key<String> name = Key.named("name", displayName.of("person_address"));
private static final Key<Integer> age = Key.named("age");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.codepoetics.octarine.records;

import com.codepoetics.octarine.json.JsonSerialiser;
import com.codepoetics.octarine.records.example.Address;
import com.codepoetics.octarine.records.example.Person;
import com.fasterxml.jackson.core.JsonGenerator;
import org.junit.Test;
import org.pcollections.TreePVector;

import java.awt.*;
import java.io.IOException;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;

public class SerialisationTest {

public static JsonSerialiser addressSerialiser = new JsonSerialiser() {
@Override
public void projecting(Projections<JsonGenerator> projections) {
projections.add(Address.addressLines, asList(asString));
}
};

public static JsonSerialiser personSerialiser = new JsonSerialiser() {
@Override
public void projecting(Projections<JsonGenerator> projections) {
projections.add(Person.name, asString)
.add(Person.age, asInteger)
.add(Person.favouriteColour, map(Color::toString, asString))
.add(Person.address, addressSerialiser);
}
};

@Test public void
writes_person_as_json() throws IOException {
String json = personSerialiser.toString(Person.schema.validate(
Person.name.of("Dominic"),
Person.age.of(39),
Person.favouriteColour.of(Color.RED),
Person.address.of(Address.addressLines.of("13 Rue Morgue", "PO3 1TP"))).get());

assertThat(json, equalTo("{\"name\":\"Dominic\",\"age\":39,\"favourite colour\":\"java.awt.Color[r=255,g=0,b=0]\",\"address\":{\"addressLines\":[\"13 Rue Morgue\",\"PO3 1TP\"]}}"));
}
}
Loading

0 comments on commit 3f033cb

Please sign in to comment.