package com.googlecode.totallylazy.json; import com.googlecode.totallylazy.Value; import com.googlecode.totallylazy.collections.PersistentMap; import com.googlecode.totallylazy.time.Dates; import org.junit.Test; import java.util.Date; import java.util.List; import java.util.NoSuchElementException; import static com.googlecode.totallylazy.Assert.assertThat; import static com.googlecode.totallylazy.Assert.fail; import static com.googlecode.totallylazy.Maps.map; import static com.googlecode.totallylazy.json.Json.json; import static com.googlecode.totallylazy.json.PersistentJsonRecord.create; import static com.googlecode.totallylazy.json.PersistentJsonRecord.map; import static com.googlecode.totallylazy.json.PersistentJsonRecord.modify; import static com.googlecode.totallylazy.json.PersistentJsonRecord.parse; import static com.googlecode.totallylazy.predicates.Predicates.is; import static com.googlecode.totallylazy.predicates.Predicates.nullValue; import static com.googlecode.totallylazy.time.Dates.date; public class PersistentJsonRecordTest { @Test public void doesNotSerialiseNullValues() throws Exception { assertThat(json(map(new User() { public String name() { return null; } public int age() { return 0; } })), is("{\"age\":0}")); } interface User { String name(); int age(); } @Test public void canCreateARecordFromAMap() throws Exception { User user = create(User.class, map("name", "Dan")); assertThat(user.name(), is("Dan")); } @Test public void canParseARecordFromJson() throws Exception { User user = parse(User.class, json(map("name", "Dan"))); assertThat(user.name(), is("Dan")); } @Test public void canConvertToJsonAndBack() throws Exception { String json = "{\"name\":\"Dan\"}"; User parsed = parse(User.class, json); assertThat(parsed.name(), is("Dan")); assertThat(parsed.toString(), is(json)); } @Test public void canBeConvertedToAMap() throws Exception { User user = new User() { public String name() { return "Dan"; } public int age() { return 12; } }; PersistentMap<String, Object> map = map(user); assertThat(map.get("name"), is("Dan")); } @Test public void canPersistNewValues() throws Exception { User dan = create(User.class, map("name", "Dan")); User matt = modify(dan, User::name, "Matt"); assertThat(dan.name(), is("Dan")); assertThat(matt.name(), is("Matt")); } interface UserDocument { User user(); } @Test public void supportsNestedRecords() throws Exception { UserDocument parsed = parse(UserDocument.class, "{\"user\":{\"name\":\"Dan\"}}"); assertThat(parsed.user().name(), is("Dan")); } interface Users { List<User> users(); } @Test public void supportsListsOfRecords() throws Exception { Users parsed = parse(Users.class, "{\"users\":[{\"name\":\"Dan\"}]}"); assertThat(parsed.users().size(), is(1)); assertThat(parsed.users().get(0).name(), is("Dan")); } @Test public void preservesUnknownAttributes() throws Exception { User user = create(User.class, map("name", "Dan","tel", "12345678890")); assertThat(user.name(), is("Dan")); assertThat(map(user).get("tel"), is("12345678890")); String json = user.toString(); assertThat(json, is("{\"name\":\"Dan\",\"tel\":\"12345678890\"}")); User parsed = parse(User.class, json); assertThat(parsed.name(), is(user.name())); assertThat(map(parsed).get("tel"), is("12345678890")); } interface IntUser { int age(); } interface IntegerUser { Integer age(); } @Test public void canCoerceIntegerTypes() throws Exception { assertThat(parse(IntUser.class, "{\"age\":1}").age(), is(1)); assertThat(parse(IntegerUser.class, "{\"age\":1}").age(), is(1)); assertThat(parse(IntegerUser.class, "{\"age\":null}").age(), nullValue()); } interface longUser { long age(); } interface LongUser { Long age(); } @Test public void canCoerceLongsTypes() throws Exception { assertThat(parse(longUser.class, "{\"age\":1}").age(), is(1L)); assertThat(parse(LongUser.class, "{\"age\":1}").age(), is(1L)); assertThat(parse(LongUser.class, "{\"age\":null}").age(), nullValue()); } enum Position { Long, } interface Trade { Position position(); } @Test public void supportsEnums() throws Exception { assertThat(parse(Trade.class, "{\"position\":\"Long\"}").position(), is(Position.Long)); } enum CustomPosition { Short; static CustomPosition customPosition(String value) { if (value.equalsIgnoreCase("short")) return CustomPosition.Short; throw new UnsupportedOperationException(); } } interface CustomTrade { CustomPosition position(); } @Test public void supportsCustomEnumsFactoryMethods() throws Exception { assertThat(parse(CustomTrade.class, "{\"position\":\"short\"}").position(), is(CustomPosition.Short)); } interface Breed extends Value<String> { static Breed breed(String value) { return () -> value; } } interface Cat { Breed breed(); } @Test public void supportsSimpleTypes() throws Exception { assertThat(parse(Cat.class, "{\"breed\":\"Tabby\"}").breed().value(), is("Tabby")); } @Test public void throwsUsefulErrorsWhenUnableToCoerceIntoType() throws Exception { try { parse(Cat.class, "{\"breed\":1}"); fail("Expected exception"); } catch (NoSuchElementException e) { assertThat(e.getMessage(), (s) -> s.matches(".*Breed.*BigDecimal")); } } interface Count extends Value<Integer> { static Count count(Number value) { return value::intValue; } } interface Crowd { Count count(); } @Test public void supportsConstructorMethodsThatTakeSuperclasses() throws Exception { // 1 is a BigDecimal, but Count only has constructor method for Number String someJson = "{\"count\":1}"; assertThat(parse(Crowd.class, someJson).count().value(), is(1)); } interface Account { Date created_at(); } @Test public void supportsDates() throws Exception { Date date = date(2001, 1, 1); assertThat(parse(Account.class, "{\"created_at\":\"" + Dates.RFC3339withMilliseconds().format(date) + "\"}").created_at(), is(date)); } interface Ledger { List<Date> times(); } @Test public void supportsListsOfCoerceables() throws Exception { Date date = date(2001, 1, 1); assertThat(parse(Ledger.class, "{\"times\":[\"" + Dates.RFC3339withMilliseconds().format(date) + "\"]}").times().get(0), is(date)); } }