package com.googlecode.totallylazy.json; import com.googlecode.totallylazy.Assert; import com.googlecode.totallylazy.Value; import com.googlecode.totallylazy.collections.Keyword; import com.googlecode.totallylazy.time.Dates; import org.junit.Test; import java.math.BigDecimal; 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.collections.PersistentList.constructors.list; import static com.googlecode.totallylazy.json.Json.json; import static com.googlecode.totallylazy.json.JsonRecord.create; import static com.googlecode.totallylazy.json.JsonRecord.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 JsonRecordTest { @Test public void doesNotSerialiseNullValues() throws Exception { assertThat(new User().toString(), is("{}")); } static class User extends JsonRecord { String name; BigDecimal age; } @Test public void canCreateARecordFromAMap() throws Exception { User user = create(User.class, map("name", "Dan", "age", new BigDecimal(1))); assertThat(user.name, is("Dan")); assertThat(user.age, is(new BigDecimal(1))); } @Test public void canParseARecordFromJson() throws Exception { User user = parse(User.class, json(map("name", "Dan", "age", 1))); assertThat(user.name, is("Dan")); assertThat(user.age, is(new BigDecimal(1))); } @Test public void canConvertToJsonAndBack() throws Exception { User user = new User() {{ name = "Dan"; age = new BigDecimal(1); }}; String json = user.toString(); assertThat(json, is("{\"name\":\"Dan\",\"age\":1}")); User parsed = parse(User.class, json); assertThat(parsed.name, is(user.name)); assertThat(parsed.age, is(user.age)); } @Test public void jsonRecordsAreMaps() throws Exception { User user = new User() {{ name = "Dan"; age = new BigDecimal(1); }}; Keyword<String> name = Keyword.keyword(); assertThat(name.call(user), is("Dan")); } static class UserDocument extends JsonRecord { User user; } @Test public void supportsNestedRecords() throws Exception { UserDocument doc = new UserDocument() {{ user = new User() {{ name = "Dan"; age = new BigDecimal(1); }}; }}; String json = doc.toString(); assertThat(json, is("{\"user\":{\"name\":\"Dan\",\"age\":1}}")); UserDocument parsed = parse(UserDocument.class, json); assertThat(parsed.user, is(doc.user)); } static class Users extends JsonRecord { List<User> users; } @Test public void supportsListsOfRecords() throws Exception { Users doc = new Users() {{ users = list(new User() {{ name = "Dan"; age = new BigDecimal(1); }}); }}; String json = doc.toString(); assertThat(json, is("{\"users\":[{\"name\":\"Dan\",\"age\":1}]}")); Users parsed = parse(Users.class, json); assertThat(parsed.users.size(), is(doc.users.size())); assertThat(parsed.users.get(0).name, is(doc.users.get(0).name)); assertThat(parsed.users.get(0).age, is(doc.users.get(0).age)); } @Test public void preservesUnknownAttributes() throws Exception { User user = create(User.class, map("name", "Dan", "age", new BigDecimal(1), "tel", "12345678890")); assertThat(user.name, is("Dan")); assertThat(user.age, is(new BigDecimal(1))); assertThat(user.get("tel"), is("12345678890")); String json = user.toString(); assertThat(json, is("{\"name\":\"Dan\",\"age\":1,\"tel\":\"12345678890\"}")); User parsed = parse(User.class, json); assertThat(parsed.name, is(user.name)); assertThat(parsed.age, is(user.age)); assertThat(parsed.get("tel"), is("12345678890")); } static class IntUser extends JsonRecord { int age; } static class IntegerUser extends JsonRecord { 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()); } static class longUser extends JsonRecord { long age; } static class LongUser extends JsonRecord { 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, } static class Trade extends JsonRecord { 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(); } } static class CustomTrade extends JsonRecord { 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; } } static class Cat extends JsonRecord { 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) { Assert.assertThat(e.getMessage(), (s) -> s.matches(".*Breed.*BigDecimal")); } } interface Count extends Value<Integer> { static Count count(Number value) { return value::intValue; } } static class Crowd extends JsonRecord { 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)); } static class Outer { private static class Inner extends JsonRecord { } } @Test public void canCreatePrivateJsonRecord() throws Exception { JsonRecord.create(Outer.Inner.class, map()); } static class Account extends JsonRecord { public 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)); } static class Ledger extends JsonRecord { public 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)); } }