Flutter ObjectBox: Is List<String> supported for property converters? - flutter-objectbox

The Flutter ObjectBox docs state:
ObjectBox (Java, Dart) has built-in support for String lists. ObjectBox for Java also has built-in support for String arrays.
However, using a List<String> in a converter triggers the following warning:
[WARNING] objectbox_generator:resolver on lib/model/diary_day.dart:
Skipping property 'doneExercises': type 'List' not supported, consider creating a relation for #Entity types (https://docs.objectbox.io/relations), or replace with getter/setter converting to a supported type (https://docs.objectbox.io/advanced/custom-types).
So the question now is, is this a bug, or am I misunderstanding the docs?
Just for reference, the converter looks like:
late List<DiaryExercise> doneExercises = [];
List<String> get dbDoneExercises {
List<String> retVal = [];
for (DiaryExercise thisExercise in doneExercises) {
retVal.add(jsonEncode(thisExercise.toJson()));
}
return retVal;
}
set dbDoneExercises(List<String> jsonStrings) {
for (String thisJson in jsonStrings)
DiaryExercise exercise = DiaryExercise.fromJson(jsonDecode(thisJson));
doneExercises.add(exercise);
}
}

Turned out, it's just a warning. A look into objectbox-model.json revealed the List<String> converter is used.

Related

Ignoring some default values for serialization

tl;dr; In Newtonsoft JSON.NET, how do you ignore default values for some types (enums), and not others (ints)?
My team is consuming a library that uses protocol buffers for their business entities. Every enumeration in this library/protobuf has a default value of 0, "ValueNotSet". My team is using Newtonsoft JSON.NET to serialize these entities. Here's a diluted example for a bakery's inventory:
public enum Flavor { ValueNotSet, Cherry, Blueberry, Cheese };
public class DanishInventory { public int QtyInStock; public Flavor; }
In order to conserve resources we do not want to serialize ValueNotSet (the real world scenario has many enumerations), but having zero cheese danishes in stock is valid and we do want to serialize zero. Because of this, we cannot use DefaultValueHandling = Ignore in settings.
I created a custom JsonConverter, but by the time WriteJson(...) is called the key is already in the JsonWriter. So if I write nothing the JSON is invalid, and I don't see an obvious method to back-track the writer to overwrite a key. So what is the best way to ignore default values for some types (e.g. enums), but not others (e.g. ints)?
Note that the enums are in a NuGet package and cannot be modified, e.g. by adding attributes.
You can use a variation of the DefaultValueContractResolver from this answer to Json.NET: How to make DefaultValueHandling only apply to certain types? to exclude all enum-valued properties with default values:
public class EnumDefaultValueContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.DefaultValueHandling == null)
{
if (property.PropertyType.IsEnum)
{
//For safety you could check here if the default value is named ValueNotSet and only set IgnoreAndPopulate in that case.
//var defaultValue = Enum.ToObject(property.PropertyType, 0);
//if (defaultValue.ToString() == "ValueNotSet")
//{
property.DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate; // Or DefaultValueHandling.Ignore if you prefer
//}
}
}
return property;
}
}
Then use it as follows:
var resolver = new EnumDefaultValueContractResolver();
var settings = new JsonSerializerSettings { ContractResolver = resolver };
var json = JsonConvert.SerializeObject(inventory, settings);
You may want to cache the contract resolver for best performance.
Demo fiddle here.
Many serializers, (including Json.NET I believe) support the ShouldSerialize*() pattern; if you don't mind doing this on a per-usage basis, you could do:
public class DanishInventory {
public int QtyInStock;
public Flavor;
public bool ShouldSerializeFlavor() => Flavor != 0;
}

Is it possible to configure Newtonsoft.Json to ignore propreties with [ScriptIgnore] attribute

I have a third-party class (lets call it Class1) which I need to serialize to JSON. If I try to do this as is, I either receive StackOverflowException or JsonSerializationException with message "Self referencing loop detected with type". I've tried to apply the following settings for the JsonConvert but it didn't help me to avoid StackOverflowException
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.None,
PreserveReferencesHandling = PreserveReferencesHandling.None,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
After decompiling the Class1 I found out that a lot of properties of the Class1 are marked with [ScriptIgnore] attribute which is an analogue of [JsonIgnore] and is used by System.Web.Script.Serialization.JavaScriptSerializer but I need to use Newtonsoft serializer.
As far as Class1 is a third-party class I can't add [JsonIgnore] attribute to the needed properties.
I know that I can develop my own implementation of IContractResolver, and handle the problematic properties there, but I'd like to avoid this option.
Maybe there is a way somehow configure Newtonsoft serializer to take into consideration [ScriptIgnore] attribute as well as [JsonIgnore]. And do this configuration like it is done with ReferenceLoopHandling?
I would appreciate for any ideas.
There's no configuration option for this. If you search on github, ScriptIgnoreAttribute doesn't even appear in the Json.NET source tree.
Even though you don't want to implement your own IContractResolver, this would be the straightforward solution and very easy. First, define the following subclass of DefaultContractResolver as follows:
public class ScriptIgnoreContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (!property.Ignored)
{
if (property.AttributeProvider.GetAttributes(true).Any(p => p.GetType().FullName == "System.Web.Script.Serialization.ScriptIgnoreAttribute"))
{
property.Ignored = true;
}
}
return property;
}
}
Then serialize as follows:
// Define a static member
static readonly IContractResolver myResolver = new ScriptIgnoreContractResolver();
// And use it in your serialization method
var settings = new JsonSerializerSettings
{
ContractResolver = myResolver,
};
var json = JsonConvert.SerializeObject(rootObject, settings);
You may want to cache the contract resolver for best performance.

How to optimize realm.allobjects(class) in Android

I want to use Realm to replace SqLite in Android to store a list of classes, my code is very simple as below.
public class MyRealmObject extends RealmObject {
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
private String field;
...
}
List<MyObject> myObjects = new ArrayList();
Realm realm = Realm.getInstance(this);
for(MyRealmObject realm : realm.allobjects(MyRealmObject.class)) {
myObjects.add(new MyObject(realm));
}
realm.close();
return myObjects;
However, its performance is actually slower than a simple SqlLite table on my tested device, am I using it the wrong way? Is there any optimization tricks?
Why do you want to wrap all your RealmObjects in the MyObject class?. Especially copying the entire result set means you will loose the benefit of using Realm, namely that it doesn't copy data unless needed to.
RealmResults implements the List interface so you should be able to use the two interchangeably.
List<MyRealmObject> myObjects;
Realm realm = Realm.getInstance(this);
myObjects = realm.allObjects(MyRealmObject.class();
return myObjects;

How does JSON.NET serialize objects when using EF table-per-hierarchy?

I'm using Entity Framework Code First table-per-hierarchy in my ASP.NET Web API project. One of my models has a List that is of the type of the abstract base class of the hierarchy.
List<Dog> // (for example; with GoldenRetriever, BorderCollie, etc inheriting)
I'm trying to test POSTing some data to my API Controller using Fiddler. But I don't know how to represent the JSON when I do so. If I try something like:
"Dogs":
[
{"Name":"Bud", "Age":"3"}
]
I get the error:
"Could not create an instance of type Models.Dog. Type is an interface
or abstract class and cannot be instantiated."
Specifying the Discriminator in the JSON doesn't help me either. Anyone have any ideas? Thanks!
Edit: Solution
The trick is to use the $type property in the JSON string. For more information see this link suggested by m.t.bennett in the comments.
To enable using the $type property I needed to add the following to WebApiConfig.cs:
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling
= TypeNameHandling.Auto;
Then when posting the JSON to Fiddler, I added the $type property with the full object path:
{"$type":"Example.Models.Dogs.GoldenRetriever, Example.Models",
"Name":"Bud","Age":3}
For me to figure out this formatting I used Snixtor's suggestion to serialize an object and output the JSON string. Brilliant!
I'm not sure if this is the most elegant solution since it's JSON.NET specific, but it works!
I used a custom JsonConverter to handle the base-type deserialization.
public override bool CanConvert(Type objectType) {
return typeof(Mybase).Equals(objectType);
}
public override MyBase Deserialize(JsonReader reader, MyBase existingValue, JsonSerializer serializer) {
var ret = existingValue;
var jobj = JObject.ReadFrom(reader);
if (jobj["type"] == null || jobj["type"].Type != JTokenType.String)
throw new JsonSerializationException("Supplied JSON is missing the required 'type' member.");
var typeName = jobj["type"].Value<string>();
var t = this.GetType().Assembly.GetType("MyNamespace." + typeName);
if(t == null)
throw new JsonSerializationException(String.Format("Could not identify supplied type '{0}' as a known type.", typeName));
if (existingValue != null && !t.IsInstanceOfType(existingValue))
throw new JsonSerializationException(String.Format("Type Mismatch: existingValue {0} is not assignable from supplied Type {1}", existingValue.GetType().Name, t.Name));
ret = (ContactMethod)jobj.ToObject(t);
return ret;
}
And registered the JsonConverter durring application initialization:
JsonSerializerSettings settings;
settings.Converters.Add(new MyBaseConverter());

Entity Framework telling me an object is attached when it isn't - why?

I have an object I want to update in the database. I'm new to EF but have done a fair bit of reading. Clearly my approach is wrong, but I don't understand why. FYI the Context referenced throughout is an ObjectContext which is newly instantiated as this code begins and is disposed immediately after. Here is my Update method - the View is the object I want to update in the database and it has 4 ICollection properties whose changes I also wish to save to the database:
public void Update(View view)
{
var original = Read(view.Username, view.ViewId);
original.ViewName = view.ViewName;
ProcessChanges<CostCentre, short>(Context.CostCentres, original.CostCentres, view.CostCentres, "iFinanceEntities.CostCentres", "CostCentreId");
ProcessChanges<LedgerGroup, byte>(Context.LedgerGroups, original.LedgerGroups, view.LedgerGroups, "iFinanceEntities.LedgerGroups", "LedgerGroupId");
ProcessChanges<Division, byte>(Context.Divisions, original.Divisions, view.Divisions, "iFinanceEntities.Divisions", "DivisionId");
ProcessChanges<AnalysisCode, short>(Context.AnalysisCodes, original.AnalysisCodes, view.AnalysisCodes, "iFinanceEntities.AnalysisCodes", "AnalysisCodeId");
int test = Context.SaveChanges();
}
First I get the original from the database because I want to compare its collections with the new set of collections. This should ensure the correct sub-objects are added and removed. I compare each collection in turn using this ProcessChanges method:
private void ProcessChanges<TEntity, TKey>(ObjectSet<TEntity> contextObjects, ICollection<TEntity> originalCollection, ICollection<TEntity> changedCollection, string entitySetName, string pkColumnName)
where TEntity : class, ILookupEntity<TKey>
{
List<TKey> toAdd = changedCollection
.Select(c => c.LookupKey)
.Except(originalCollection.Select(o => o.LookupKey))
.ToList();
List<TKey> toRemove = originalCollection
.Select(o => o.LookupKey)
.Except(changedCollection.Select(c => c.LookupKey))
.ToList();
toAdd.ForEach(a =>
{
var o = changedCollection.Single(c => c.LookupKey.Equals(a));
AttachToOrGet<TEntity, TKey>(entitySetName, pkColumnName, ref o);
originalCollection.Add(o);
});
toRemove.ForEach(r =>
{
var o = originalCollection.Single(c => c.LookupKey.Equals(r));
originalCollection.Remove(o);
});
}
This compares the new collection to the old one and works out which objects to add and which to remove. Note that the collections all contain objects which implement ILookupEntity.
My problems occur on the line where I call AttachToOrGet. This method I got from elsewhere on stackoverflow. I'm using this because I was often getting a message saying that "An object with the same key already exists in the ObjectStateManager" when attaching a new subobject. Hopefully you'll understand my confusion around this when I post the code of this method below:
public void AttachToOrGet<TEntity, TKey>(string entitySetName, string pkColumnName, ref TEntity entity)
where TEntity : class, ILookupEntity<TKey>
{
ObjectStateEntry entry;
// Track whether we need to perform an attach
bool attach = false;
if (Context.ObjectStateManager.TryGetObjectStateEntry(new EntityKey(entitySetName, pkColumnName, entity.LookupKey), out entry))
//if (Context.ObjectStateManager.TryGetObjectStateEntry(Context.CreateEntityKey(entitySetName, entity), out entry))
{
// Re-attach if necessary
attach = entry.State == EntityState.Detached;
// Get the discovered entity to the ref
entity = (TEntity)entry.Entity;
}
else
{
// Attach for the first time
attach = true;
}
if (attach)
Context.AttachTo(entitySetName, entity);
}
Basically this is saying if the entity is not already attached then attach it. But my code is returning false on the Context.ObjectStateManager.TryGetObjectStateEntry line, but throwing an exception on the final line with the message "An object with the same key already exists in the ObjectStateManager". To me this is paradoxical.
As far as I'm concerned I'm trying to achieve something very simple. Something it would take 20 minutes to write a stored procedure for. A simple database update. Frankly I don't care what is attached and what isn't because I don't wish to track changes or create proxies or lazy load or do anything else EF offers me. I just want to take a very simple object and update the database using a minimal number of trips between servers. How is this so complicated? Please someone help me - I've spent a whole day on this!
Update
Here's my ILookupEntity class:
public interface ILookupEntity<TKey>
{
TKey LookupKey { get; }
string DisplayText { get; }
}
Here's how it is implemented in CostCentre:
public partial class CostCentre : IFinancialCode, ILookupEntity<short>
{
#region IFinancialCode Members
public short ID { get { return CostCentreId; } }
public string DisplayText { get { return string.Format("{0} - {1}", Code, Description); } }
#endregion
#region ILookupEntity Members
public short LookupKey
{
get { return ID; }
}
#endregion ILookupEntity Members
}
Well, I've worked through this and found a solution, but I can't say I understand it. The crucial ingredient came when I was performing a check after the comment by #Slauma. I wanted to check I was using the correct entity set name etc so I included the following lines near the top of my AttachToOrGet method:
var key = new EntityKey(entitySetName, pkColumnName, entity.LookupKey);
object temp;
if (!Context.TryGetObjectByKey(key, out temp))
throw new Exception(string.Format("No entity was found in {0} with key {1}", entitySetName, entity.LookupKey));
Bizarrely this alone resolved the problem. For some reason, once I'd called the TryGetObjectByKey then the ObjectStateManager.TryGetObjectStateEntry call actually started locating the attached entity. Miraculous. I'd love it if anyone can explain this.
By the way, I also needed to include the following code, but that's just because in my case the modelled entities are located in a separate assembly from the context itself.
Assembly assembly = typeof(CostCentre).Assembly;
Context.MetadataWorkspace.LoadFromAssembly(assembly);

Resources