<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Data on blog.iankulin.com</title><link>https://blog.iankulin.com/tags/data/</link><description>Recent content in Data on blog.iankulin.com</description><generator>Hugo</generator><language>en-AU</language><lastBuildDate>Tue, 25 Oct 2022 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.iankulin.com/tags/data/index.xml" rel="self" type="application/rss+xml"/><item><title>Updating stored JSON due to a struct change</title><link>https://blog.iankulin.com/updating-stored-json-due-to-a-struct-change/</link><pubDate>Tue, 25 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/updating-stored-json-due-to-a-struct-change/</guid><description>&lt;p&gt;I mentioned yesterday &amp;ldquo;&lt;em&gt;I could use a renamed old version of my struct to load the existing data, and convert it across to the new model.&lt;/em&gt;&amp;rdquo;. Since I&amp;rsquo;ve been testing the app on my phone, and using plausible data, it was going to be painful enough to lose it that I thought I should go through those steps.&lt;/p&gt;
&lt;p&gt;First, I make a copy of the old struct, and renamed it with the app version number that used it. No need to bring all the computed properties into this struct, just the bits that get encoded into the JSON.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;V01HabitItem&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Identifiable&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; Codable&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;Equatable&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; id &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; UUID&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; name&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; started &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Date&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; timesDone &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; lastDone&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Date
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; daysBetweenCompletions &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;1.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then I could go ahead and change the official struct to add the new properties it needs. Now when the JSON is attempted to be decoded into the new struct, it will fail, so we need to detect that and try with the old version of the struct.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;class&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;Habits&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; ObservableObject &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;Published &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; items &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;HabitItem&lt;span style="color:#eceff4"&gt;]()&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;didSet&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; encoded &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;try&lt;/span&gt;&lt;span style="color:#eceff4"&gt;?&lt;/span&gt; JSONEncoder&lt;span style="color:#eceff4"&gt;().&lt;/span&gt;encode&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; UserDefaults&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;standard&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;set&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;encoded&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; forKey&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Habits&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;else&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;JSON encoding fail&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;init&lt;/span&gt;&lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; savedItems &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; UserDefaults&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;standard&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;data&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;forKey&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Habits&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; decodedItems &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;try&lt;/span&gt;&lt;span style="color:#eceff4"&gt;?&lt;/span&gt; JSONDecoder&lt;span style="color:#eceff4"&gt;().&lt;/span&gt;decode&lt;span style="color:#eceff4"&gt;([&lt;/span&gt;HabitItem&lt;span style="color:#eceff4"&gt;].&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;self&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; from&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; savedItems&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; items &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; decodedItems
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;else&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;JSON decoding fail - trying v0.1&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; v01Items &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;V01HabitItem&lt;span style="color:#eceff4"&gt;]()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; decodedItems &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;try&lt;/span&gt;&lt;span style="color:#eceff4"&gt;?&lt;/span&gt; JSONDecoder&lt;span style="color:#eceff4"&gt;().&lt;/span&gt;decode&lt;span style="color:#eceff4"&gt;([&lt;/span&gt;V01HabitItem&lt;span style="color:#eceff4"&gt;].&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;self&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; from&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; savedItems&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; v01Items &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; decodedItems
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; v01Items&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;forEach &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; oldHabit &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; items&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;append&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;HabitItem&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; oldHabit&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;id&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; oldHabit&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; started&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; oldHabit&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;started&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; timesDone&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; oldHabit&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;timesDone&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lastDone&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; oldHabit&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;lastDone&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; daysBetweenCompletions&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; oldHabit&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;daysBetweenCompletions
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;else&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;JSON decoding fail&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; items &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once it&amp;rsquo;s decoded, we need to ForEach through and create instances of the new struct from the old ones.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s made my init() a bit messy, normally I&amp;rsquo;d move that out - maybe have a separate file with the old struct and a function for decoding it and copying into the new struct, but this is not a production app, it&amp;rsquo;s only on my phone and on two simulations on my macBook, so this code won&amp;rsquo;t stay in once I&amp;rsquo;ve updated each instance by running it once.&lt;/p&gt;
&lt;p&gt;If it works correctly, I should get the debug message and the data will still be there on the first run, then on a second run the message should not appear (since the new struct version will have been written to the file.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-22-at-7.41.44-pm.jpg" alt=""&gt;&lt;/p&gt;</description></item><item><title>Musings on Data</title><link>https://blog.iankulin.com/musings-on-data/</link><pubDate>Sun, 16 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/musings-on-data/</guid><description>&lt;p&gt;I&amp;rsquo;ve been feeling my enthusiasm for the online courses I started waning a little, additionally, I have enough progress under my belt that I could actually start working on some of the projects in my &amp;ldquo;App Ideas&amp;rdquo; notebook. I&amp;rsquo;m not sure if starting on them is a smart idea, or just a way of procrastinating. One thing most of those apps have in common, and that I haven&amp;rsquo;t substantively learned yet is persisting data. Their data requirements vary from a sort of specialised todo list only required on that device, to relational data that needs to be live synced across devices including macOS and needs to support rolling back transactions to resolve conflicts.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://blog.iankulin.com/iexpense-challenges/"&gt;iExpense&lt;/a&gt; app from the &lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/archiving-swift-objects-with-codable"&gt;100 Days course&lt;/a&gt; included encoding data to JSON then saving it in UserDefaults, but that&amp;rsquo;s the extent of my data expertise so far. And, that would be fine for a prototype version of a couple of the app ideas for now.&lt;/p&gt;
&lt;p&gt;I know these things exist:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Core Data (and, I imagine CloudKit for saving the SQLite in iCloud)&lt;/li&gt;
&lt;li&gt;FireBase - the Google database engine that seems popular for serious apps that need to share data&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s possible to just save files in the apps sandbox (which I don&amp;rsquo;t know how to do, but sounds a bit more legit that using UserDefaults)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In programming terms, I grew up on large relational databases - on disk. RAM used to be much more precious and capricious, and saving a file much slower - so I have some thinking adjustments to do. For my Todo type app that&amp;rsquo;s not likely to have more than 100 records, it&amp;rsquo;s probably legit to just encode the whole thing to JSON and write it to a file for every add/edit/delete.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not sure what I&amp;rsquo;ll do - the logical thing is to buckle down and work through the 100 days until I get to databases in the tutorials, but if working on apps is more engaging and leads to more programming that could be better. My jobby-job was particularly demanding this week, so perhaps the weekend will fix this problem for me.&lt;/p&gt;</description></item></channel></rss>