diff options
| author | Tyler Ferrari <69283684+Sowgro@users.noreply.github.com> | 2025-11-16 02:03:16 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-11-16 02:03:16 -0500 |
| commit | 64dd072264dd59457cb195f23d17f03720b1cca0 (patch) | |
| tree | 3fd646ef33deaa338f26278f636ebc9245ed9105 | |
| parent | f79ab927050250c4b7e63a4fbd37034d4eeae8cd (diff) | |
| parent | 969b8071dd799dfaed939e10bf77b8a1ad42f9dc (diff) | |
| download | designproject-design-6-64dd072264dd59457cb195f23d17f03720b1cca0.tar.gz designproject-design-6-64dd072264dd59457cb195f23d17f03720b1cca0.tar.bz2 designproject-design-6-64dd072264dd59457cb195f23d17f03720b1cca0.zip | |
Merge pull request #19 from RIT-SWEN-262/import-export
Import export
Diffstat (limited to '')
| -rw-r--r-- | pom.xml | 9 | ||||
| -rw-r--r-- | src/main/java/design/controller/userinput/menus/ImportExportMenu.java | 81 | ||||
| -rw-r--r-- | src/main/java/design/controller/userinput/menus/MainMenu.java | 3 | ||||
| -rw-r--r-- | src/main/java/design/model/Golfer.java | 3 | ||||
| -rw-r--r-- | src/main/java/design/persistence/JSONLeagueDatabase.java | 53 | ||||
| -rw-r--r-- | src/main/java/design/persistence/JSONPersonalDatabase.java | 56 | ||||
| -rw-r--r-- | src/main/java/design/persistence/LeagueDatabase.java | 9 | ||||
| -rw-r--r-- | src/main/java/design/persistence/PersonalDatabase.java | 10 | ||||
| -rw-r--r-- | src/main/java/design/persistence/Serializers.java | 25 | ||||
| -rw-r--r-- | src/main/java/design/persistence/importexport/DataHandler.java | 11 | ||||
| -rw-r--r-- | src/main/java/design/persistence/importexport/DataSource.java | 16 | ||||
| -rw-r--r-- | src/main/java/design/persistence/importexport/JSONHandler.java | 35 | ||||
| -rw-r--r-- | src/main/java/design/persistence/importexport/XMLHandler.java | 38 | ||||
| -rw-r--r-- | src/test/java/design/model/GolferTest.java | 6 | ||||
| -rw-r--r-- | test.json | 160 | ||||
| -rw-r--r-- | test.xml | 144 |
16 files changed, 606 insertions, 53 deletions
@@ -42,6 +42,11 @@ <artifactId>jackson-datatype-jdk8</artifactId> <version>2.20.0</version> </dependency> + <dependency> + <groupId>com.fasterxml.jackson.dataformat</groupId> + <artifactId>jackson-dataformat-xml</artifactId> + <version>2.20.0</version> + </dependency> </dependencies> <build> <plugins> @@ -67,8 +72,8 @@ <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.11</version> <configuration> - <destfile>/target/coverage-reports/jacoco-unit.exec</destfile> - <datafile>/target/coverage-reports/jacoco-unit.exec</datafile> + <destFile>/target/coverage-reports/jacoco-unit.exec</destFile> + <destFile>/target/coverage-reports/jacoco-unit.exec</destFile> </configuration> <executions> <execution> diff --git a/src/main/java/design/controller/userinput/menus/ImportExportMenu.java b/src/main/java/design/controller/userinput/menus/ImportExportMenu.java new file mode 100644 index 0000000..21ffa6c --- /dev/null +++ b/src/main/java/design/controller/userinput/menus/ImportExportMenu.java @@ -0,0 +1,81 @@ +package design.controller.userinput.menus; + +import design.controller.userinput.Menu; +import design.controller.userinput.MenuOption; +import design.persistence.*; +import design.persistence.importexport.DataHandler; +import design.persistence.importexport.DataSource; +import design.persistence.importexport.JSONHandler; +import design.persistence.importexport.XMLHandler; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Scanner; + +public class ImportExportMenu extends Menu { + + @Override + public String getTitle() { + return "import export menu"; + } + + @Override + public List<MenuOption> getMenuOptions() { + List<MenuOption> opts = new java.util.ArrayList<>(); + + + opts.add(new MenuOption("return to main menu", () -> new MainMenu().present())); + + opts.add(new MenuOption("import leagues...", () -> promptForPath(true, false))); + opts.add(new MenuOption("export leagues...", () -> promptForPath(false, false))); + opts.add(new MenuOption("import profiles...", () -> promptForPath(true, true))); + opts.add(new MenuOption("export profiles...", () -> promptForPath(false, true))); + + return opts; + } + + private void promptForPath(boolean isImporting, boolean isPersonalProfile) + { + System.out.print("Enter file path: "); + Scanner sc = new Scanner(System.in); + String filePath = sc.nextLine(); + File file = new File(filePath); + + DataSource source; + if (isPersonalProfile) { + source = PersonalDatabase.instance(); + } else { + source = LeagueDatabase.instance(); + } + + DataHandler handler; + String ext = getExtension(filePath); + switch (ext) { + case "json" -> handler = new JSONHandler(source); + case "xml" -> handler = new XMLHandler(source); + default -> { + System.out.println("Unsupported file type: " + ext); + this.present(); + return; + } + } + + try { + if (isImporting) { + handler.importData(file); + } else { + handler.exportData(file); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + this.present(); + } + + private static String getExtension(String fileName) { + int i = fileName.lastIndexOf('.'); + return (i >= 0) ? fileName.substring(i + 1).toLowerCase() : "unknown"; + } +} diff --git a/src/main/java/design/controller/userinput/menus/MainMenu.java b/src/main/java/design/controller/userinput/menus/MainMenu.java index 75ad88f..0c42422 100644 --- a/src/main/java/design/controller/userinput/menus/MainMenu.java +++ b/src/main/java/design/controller/userinput/menus/MainMenu.java @@ -39,7 +39,8 @@ public class MainMenu extends Menu { new MenuOption("manage clubs...", () -> new ManageClubs().present()), new MenuOption("statistics...", () -> new StatisticsMenu().present()), new MenuOption("log round...", () -> new HolePlayMenu().present()), - new MenuOption("League play...", () -> new SelectLeague().present()) + new MenuOption("League play...", () -> new SelectLeague().present()), + new MenuOption("manage data...", () -> new ImportExportMenu().present()) ); } } diff --git a/src/main/java/design/model/Golfer.java b/src/main/java/design/model/Golfer.java index 3b4770d..4fb521e 100644 --- a/src/main/java/design/model/Golfer.java +++ b/src/main/java/design/model/Golfer.java @@ -1,6 +1,7 @@ package design.model; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import java.util.ArrayList; @@ -21,6 +22,8 @@ public class Golfer implements Originator { private final List<Invite> invites; private Team joinedTeam; + + @JsonCreator private Golfer(String username, int passwordHash, String fullName, List<Course> courses, List<Round> rounds, List<Club> clubs, List<Invite> invites, Team joinedTeam) { diff --git a/src/main/java/design/persistence/JSONLeagueDatabase.java b/src/main/java/design/persistence/JSONLeagueDatabase.java index 8a55065..977e162 100644 --- a/src/main/java/design/persistence/JSONLeagueDatabase.java +++ b/src/main/java/design/persistence/JSONLeagueDatabase.java @@ -1,14 +1,7 @@ package design.persistence; -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.core.StreamReadFeature; import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import design.model.Course; import design.model.Golfer; import design.model.League; @@ -38,18 +31,10 @@ public class JSONLeagueDatabase implements LeagueDatabase { public JSONLeagueDatabase(String filename) { this.file = new File(filename); this.cache = new HashMap<>(); - this.mapper = JsonMapper.builder().enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION).build(); + this.mapper = new ObjectMapper(); - SimpleModule module = new SimpleModule(); - module.addDeserializer(Course.class, new Serializers.CourseIdDeserializer()); - module.addSerializer(Course.class, new Serializers.CourseIdSerializer()); - module.addDeserializer(Golfer.class, new Serializers.GolferUsernameDeserializer()); - module.addSerializer(Golfer.class, new Serializers.GolferUsernameSerializer()); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - mapper.registerModule(module); - mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); - mapper.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES)); - mapper.registerModule(new JavaTimeModule()); + Serializers.configureMapper(mapper); + mapper.registerModule(this.getJacksonModule()); try { load(); @@ -99,4 +84,36 @@ public class JSONLeagueDatabase implements LeagueDatabase { cache.put(league.getId(), league); save(); } + + @Override + public void importData(Object rawData) throws IOException { + if (!(rawData instanceof League[] data)) { + throw new ClassCastException(); + } + cache.clear(); + for (League league : data) { + cache.put(league.getId(), league); + } + save(); + } + + @Override + public League[] exportData() { + return getLeagues(); + } + + @Override + public SimpleModule getJacksonModule() { + SimpleModule module = new SimpleModule(); + module.addDeserializer(Course.class, new Serializers.CourseIdDeserializer()); + module.addSerializer(Course.class, new Serializers.CourseIdSerializer()); + module.addDeserializer(Golfer.class, new Serializers.GolferUsernameDeserializer()); + module.addSerializer(Golfer.class, new Serializers.GolferUsernameSerializer()); + return module; + } + + @Override + public Class<?> getTargetClass() { + return League[].class; + } } diff --git a/src/main/java/design/persistence/JSONPersonalDatabase.java b/src/main/java/design/persistence/JSONPersonalDatabase.java index 30c280a..b92dddf 100644 --- a/src/main/java/design/persistence/JSONPersonalDatabase.java +++ b/src/main/java/design/persistence/JSONPersonalDatabase.java @@ -1,14 +1,7 @@ package design.persistence; -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.core.StreamReadFeature; import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import design.model.Course; import design.model.Golfer; import design.model.League; @@ -36,21 +29,10 @@ public class JSONPersonalDatabase implements PersonalDatabase { private JSONPersonalDatabase(String filename) { this.file = new File(filename); this.cache = new HashMap<>(); - this.mapper = JsonMapper.builder().enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION).build();; + this.mapper = new ObjectMapper(); - // TODO: Once the saved JSON matches the model, consider removing. - // TEMP: tolerate unknown props while the model stabilizes - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - - SimpleModule module = new SimpleModule(); - module.addDeserializer(Course.class, new Serializers.CourseIdDeserializer()); - module.addSerializer(Course.class, new Serializers.CourseIdSerializer()); - module.addSerializer(League.class, new Serializers.LeagueIDSerializer()); - module.addDeserializer(League.class, new Serializers.LeagueIDDeserializer()); - mapper.registerModule(module); - mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); - mapper.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES)); - mapper.registerModule(new JavaTimeModule()); + Serializers.configureMapper(mapper); + mapper.registerModule(this.getJacksonModule()); try { load(); @@ -100,4 +82,36 @@ public class JSONPersonalDatabase implements PersonalDatabase { cache.put(golfer.getUsername(), golfer); save(); } + + @Override + public void importData(Object rawData) throws IOException { + if (!(rawData instanceof Golfer[] data)) { + throw new ClassCastException(); + } + cache.clear(); + for (Golfer golfer : data) { + cache.put(golfer.getUsername(), golfer); + } + save(); + } + + @Override + public Golfer[] exportData() { + return getGolfers(); + } + + @Override + public SimpleModule getJacksonModule() { + SimpleModule module = new SimpleModule(); + module.addDeserializer(Course.class, new Serializers.CourseIdDeserializer()); + module.addSerializer(Course.class, new Serializers.CourseIdSerializer()); + module.addSerializer(League.class, new Serializers.LeagueIDSerializer()); + module.addDeserializer(League.class, new Serializers.LeagueIDDeserializer()); + return module; + } + + @Override + public Class<?> getTargetClass() { + return Golfer[].class; + } } diff --git a/src/main/java/design/persistence/LeagueDatabase.java b/src/main/java/design/persistence/LeagueDatabase.java index 953624d..ed6ddae 100644 --- a/src/main/java/design/persistence/LeagueDatabase.java +++ b/src/main/java/design/persistence/LeagueDatabase.java @@ -1,10 +1,11 @@ package design.persistence; import design.model.League; +import design.persistence.importexport.DataSource; import java.io.IOException; -public interface LeagueDatabase { +public interface LeagueDatabase extends DataSource { static LeagueDatabase instance() { return JSONLeagueDatabase.instance(); } @@ -18,4 +19,10 @@ public interface LeagueDatabase { void removeLeague(League league) throws IOException; void updateLeague(League league) throws IOException; + + @Override + void importData(Object data) throws IOException; + + @Override + Object exportData() throws IOException; } diff --git a/src/main/java/design/persistence/PersonalDatabase.java b/src/main/java/design/persistence/PersonalDatabase.java index adb865d..70dd37d 100644 --- a/src/main/java/design/persistence/PersonalDatabase.java +++ b/src/main/java/design/persistence/PersonalDatabase.java @@ -1,9 +1,11 @@ package design.persistence; import design.model.Golfer; +import design.persistence.importexport.DataSource; + import java.io.IOException; -public interface PersonalDatabase { +public interface PersonalDatabase extends DataSource { static PersonalDatabase instance() { return JSONPersonalDatabase.instance(); @@ -18,4 +20,10 @@ public interface PersonalDatabase { void removeGolfer(Golfer golfer) throws IOException; void updateGolfer(Golfer golfer) throws IOException; + + @Override + void importData(Object data) throws IOException; + + @Override + Object exportData() throws IOException; } diff --git a/src/main/java/design/persistence/Serializers.java b/src/main/java/design/persistence/Serializers.java index 3940b44..246dbf1 100644 --- a/src/main/java/design/persistence/Serializers.java +++ b/src/main/java/design/persistence/Serializers.java @@ -1,18 +1,20 @@ package design.persistence; +import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.util.DefaultIndenter; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import design.model.Course; import design.model.Golfer; import design.model.League; import java.io.IOException; +import java.time.LocalDateTime; +import java.util.List; public class Serializers { public static class CustomPrettyPrinter extends DefaultPrettyPrinter { @@ -76,4 +78,19 @@ public class Serializers { return personalDB.getGolfer(username); } } + + public static class DateTimeStringSerializer extends JsonSerializer<LocalDateTime> { + @Override + public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeString(value.toString()); + } + } + + public static void configureMapper(ObjectMapper mapper) { + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + mapper.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES)); + mapper.registerModule(new JavaTimeModule()); + mapper.configOverride(List.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY)); + } } diff --git a/src/main/java/design/persistence/importexport/DataHandler.java b/src/main/java/design/persistence/importexport/DataHandler.java new file mode 100644 index 0000000..59df9be --- /dev/null +++ b/src/main/java/design/persistence/importexport/DataHandler.java @@ -0,0 +1,11 @@ +package design.persistence.importexport; + +import java.io.File; +import java.io.IOException; + +public interface DataHandler { + + void importData(File file) throws IOException; + + void exportData(File file) throws IOException; +} diff --git a/src/main/java/design/persistence/importexport/DataSource.java b/src/main/java/design/persistence/importexport/DataSource.java new file mode 100644 index 0000000..e735c7e --- /dev/null +++ b/src/main/java/design/persistence/importexport/DataSource.java @@ -0,0 +1,16 @@ +package design.persistence.importexport; + +import com.fasterxml.jackson.databind.module.SimpleModule; + +import java.io.IOException; + +public interface DataSource { + + void importData(Object data) throws IOException; + + Object exportData() throws IOException; + + SimpleModule getJacksonModule(); + + Class<?> getTargetClass(); +}
\ No newline at end of file diff --git a/src/main/java/design/persistence/importexport/JSONHandler.java b/src/main/java/design/persistence/importexport/JSONHandler.java new file mode 100644 index 0000000..9c04281 --- /dev/null +++ b/src/main/java/design/persistence/importexport/JSONHandler.java @@ -0,0 +1,35 @@ +package design.persistence.importexport; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import design.persistence.Serializers; + +import java.io.File; +import java.io.IOException; + +public class JSONHandler implements DataHandler { + + private final DataSource dataSource; + private final ObjectMapper jsonMapper = new JsonMapper(); + + public JSONHandler(DataSource dataSource) { + this.dataSource = dataSource; + + Serializers.configureMapper(jsonMapper); + jsonMapper.registerModule(dataSource.getJacksonModule()); + } + + @Override + public void importData(File file) throws IOException { + Object data = jsonMapper.readValue(file, dataSource.getTargetClass()); + dataSource.importData(data); + } + + @Override + public void exportData(File file) throws IOException{ + Object data = dataSource.exportData(); + jsonMapper.writer(new Serializers.CustomPrettyPrinter()) + .writeValue(file, data); + } +} + + diff --git a/src/main/java/design/persistence/importexport/XMLHandler.java b/src/main/java/design/persistence/importexport/XMLHandler.java new file mode 100644 index 0000000..0a07d6e --- /dev/null +++ b/src/main/java/design/persistence/importexport/XMLHandler.java @@ -0,0 +1,38 @@ +package design.persistence.importexport; +import java.io.File; +import java.io.IOException; +import java.time.LocalDateTime; + +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import design.persistence.Serializers; + +public class XMLHandler implements DataHandler { + + private final DataSource dataSource; + private final XmlMapper xmlMapper = new XmlMapper(); + + public XMLHandler(DataSource dataSource) { + this.dataSource = dataSource; + + Serializers.configureMapper(xmlMapper); + SimpleModule module = dataSource.getJacksonModule(); + module.addSerializer(LocalDateTime.class, new Serializers.DateTimeStringSerializer()); + xmlMapper.registerModule(module); + } + + @Override + public void importData(File file) throws IOException { + Object data = xmlMapper.readValue(file, dataSource.getTargetClass()); + dataSource.importData(data); + } + + @Override + public void exportData(File file) throws IOException { + Object data = dataSource.exportData(); + xmlMapper.writerWithDefaultPrettyPrinter() + .writeValue(file, data); + } +} + + diff --git a/src/test/java/design/model/GolferTest.java b/src/test/java/design/model/GolferTest.java index 65229f3..a73997a 100644 --- a/src/test/java/design/model/GolferTest.java +++ b/src/test/java/design/model/GolferTest.java @@ -136,8 +136,4 @@ public class GolferTest { String expectedString = "John Doe (@jdoesgolf2)"; assertEquals(expectedString, testGolfer.toString()); } - - - -} - +}
\ No newline at end of file diff --git a/test.json b/test.json new file mode 100644 index 0000000..4e42e0d --- /dev/null +++ b/test.json @@ -0,0 +1,160 @@ +[ + { + "clubs": [], + "nextClubId": 1, + "username": "test", + "passwordHash": 3556498, + "fullName": "test", + "courses": [], + "rounds": [], + "invites": [] + }, + { + "clubs": [], + "nextClubId": 1, + "username": "GUYHERE", + "passwordHash": 3556498, + "fullName": "GUYHERE", + "courses": [], + "rounds": [], + "invites": [] + }, + { + "clubs": [ + { + "id": 1, + "manufacture": "Bobby", + "nickname": "swen 261", + "clubType": "DRIVER" + }, + { + "id": 2, + "manufacture": "Bobby", + "nickname": "swen 262", + "clubType": "PUTTER" + }, + { + "id": 3, + "manufacture": "sowclub", + "nickname": "man", + "clubType": "WOOD" + } + ], + "nextClubId": 4, + "username": "john_doe", + "passwordHash": 46792755, + "fullName": "John Doe", + "courses": [ + 2, + 1 + ], + "rounds": [ + { + "course": 1, + "dateTime": [ + 2025, + 10, + 7, + 13, + 54, + 37, + 429963500 + ], + "startingHole": { + "number": 1, + "par": 5 + }, + "plays": [ + { + "holeNumber": 1, + "swings": [ + { + "distance": 100, + "clubUsed": 1 + }, + { + "distance": 5, + "clubUsed": 2 + }, + { + "distance": 1, + "clubUsed": 2 + } + ], + "swingCount": 3, + "distance": 106 + }, + { + "holeNumber": 2, + "swings": [ + { + "distance": 1000, + "clubUsed": 1 + }, + { + "distance": 2, + "clubUsed": 1 + } + ], + "swingCount": 2, + "distance": 1002 + } + ], + "currentHoleIndex": 2, + "totalSwings": 5, + "totalDistance": 1108.0, + "currentHole": { + "number": 3, + "par": 4 + } + }, + { + "course": 1, + "dateTime": [ + 2025, + 10, + 8, + 20, + 19, + 38, + 968996400 + ], + "startingHole": { + "number": 9, + "par": 3 + }, + "plays": [ + { + "holeNumber": 9, + "swings": [ + { + "distance": 204, + "clubUsed": 1 + } + ], + "swingCount": 1, + "distance": 204 + } + ], + "currentHoleIndex": 9, + "totalSwings": 1, + "totalDistance": 204.0, + "currentHole": { + "number": 10, + "par": 3 + } + } + ], + "invites": [] + }, + { + "clubs": [], + "nextClubId": 1, + "username": "sowgro", + "passwordHash": -896456343, + "fullName": "sowgro", + "courses": [], + "rounds": [], + "invites": [] + } +]
\ No newline at end of file diff --git a/test.xml b/test.xml new file mode 100644 index 0000000..4701977 --- /dev/null +++ b/test.xml @@ -0,0 +1,144 @@ +<Golfers> + <item> + <clubs/> + <nextClubId>1</nextClubId> + <username>test</username> + <passwordHash>3556498</passwordHash> + <fullName>test</fullName> + <courses/> + <rounds/> + <invites/> + </item> + <item> + <clubs/> + <nextClubId>1</nextClubId> + <username>GUYHERE</username> + <passwordHash>3556498</passwordHash> + <fullName>GUYHERE</fullName> + <courses/> + <rounds/> + <invites/> + </item> + <item> + <clubs> + <clubs> + <id>1</id> + <manufacture>Bobby</manufacture> + <nickname>swen 261</nickname> + <clubType>DRIVER</clubType> + </clubs> + <clubs> + <id>2</id> + <manufacture>Bobby</manufacture> + <nickname>swen 262</nickname> + <clubType>PUTTER</clubType> + </clubs> + <clubs> + <id>3</id> + <manufacture>sowclub</manufacture> + <nickname>man</nickname> + <clubType>WOOD</clubType> + </clubs> + </clubs> + <nextClubId>4</nextClubId> + <username>john_doe</username> + <passwordHash>46792755</passwordHash> + <fullName>John Doe</fullName> + <courses> + <courses>2</courses> + <courses>1</courses> + </courses> + <rounds> + <rounds> + <course>1</course> + <dateTime>2025-10-07T13:54:37.429963500</dateTime> + <startingHole> + <number>1</number> + <par>5</par> + </startingHole> + <plays> + <plays> + <holeNumber>1</holeNumber> + <swings> + <swings> + <distance>100</distance> + <clubUsed>1</clubUsed> + </swings> + <swings> + <distance>5</distance> + <clubUsed>2</clubUsed> + </swings> + <swings> + <distance>1</distance> + <clubUsed>2</clubUsed> + </swings> + </swings> + <swingCount>3</swingCount> + <distance>106</distance> + </plays> + <plays> + <holeNumber>2</holeNumber> + <swings> + <swings> + <distance>1000</distance> + <clubUsed>1</clubUsed> + </swings> + <swings> + <distance>2</distance> + <clubUsed>1</clubUsed> + </swings> + </swings> + <swingCount>2</swingCount> + <distance>1002</distance> + </plays> + </plays> + <currentHoleIndex>2</currentHoleIndex> + <totalSwings>5</totalSwings> + <totalDistance>1108.0</totalDistance> + <currentHole> + <number>3</number> + <par>4</par> + </currentHole> + </rounds> + <rounds> + <course>1</course> + <dateTime>2025-10-08T20:19:38.968996400</dateTime> + <startingHole> + <number>9</number> + <par>3</par> + </startingHole> + <plays> + <plays> + <holeNumber>9</holeNumber> + <swings> + <swings> + <distance>204</distance> + <clubUsed>1</clubUsed> + </swings> + </swings> + <swingCount>1</swingCount> + <distance>204</distance> + </plays> + </plays> + <currentHoleIndex>9</currentHoleIndex> + <totalSwings>1</totalSwings> + <totalDistance>204.0</totalDistance> + <currentHole> + <number>10</number> + <par>3</par> + </currentHole> + </rounds> + </rounds> + <invites/> + </item> + <item> + <clubs/> + <nextClubId>1</nextClubId> + <username>sowgro</username> + <passwordHash>-896456343</passwordHash> + <fullName>sowgro</fullName> + <courses/> + <rounds/> + <invites/> + </item> +</Golfers> |
