JSON.NET incompatibility with LLBLGen entities?

Posts   
 
    
torevor
User
Posts: 17
Joined: 08-Apr-2011
# Posted on: 15-Apr-2011 00:46:32   

JSON.Net LLBLGen 3.1 I am using Self Servicing for these testing purposes, I may or may not switch to Adapter later.

I have the following code:

MetricEntity newMetric = new MetricEntity(26);
string jsonOutput = Newtonsoft.Json.JsonConvert.SerializeObject(newMetric,Formatting.Indented);

The resulting JSON contains all sorts of LLBLGen specific properties, ie:

"_alwaysFetchXXXXX\":false,\"_alreadyFetchedXXXXXX\":false,

....which is fine, obviously I am expecting to have to do a fair amount of work to tell JSON.Net how to serialize my objects (via IContractResolver I imagine) so it includes only very specific elements...but what it didn't contain was any of the properties specific to the underlying table, ie:

public virtual System.Int32 MetricId
        {
            get { return (System.Int32)GetValue((int)MetricFieldIndex.MetricId, true); }
            set { SetValue((int)MetricFieldIndex.MetricId, value, true); }
        }

Is there something related to XML serialization in the base classes that gets invoked when JSON.Net performs serialization that is preventing the properties representing the database fields from being output? If so, is there an easy way to turn that off, or some other better approach? (Not using JSON.Net is likely not an option)

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 15-Apr-2011 05:51:15   
David Elizondo | LLBLGen Support Team
torevor
User
Posts: 17
Joined: 08-Apr-2011
# Posted on: 15-Apr-2011 17:44:31   

Hi David,

Yes, have read all of that....while I understand there is no direct support for JSON, I wonder if Frans or someone could possibly comment on whether an implementation of some .Net framework serialization Interface (for XML serialization, which is supported) might possibly be the reason why the properties might not be serialized by JSON.NET.

I don't expect any effort in terms of support of JSON.Net, but the very fact that by default, these properties are not coming out seems very unusual and would be worthwhile to note here on the forums if there is a technical explanation for this.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 15-Apr-2011 22:38:09   

I see. JSon.Net is serializing a lot of private members an no one field nor public properties. I haven't played with JSon.net as I use the of Brian Chance, or the MVC.net built-in json converter.

So, you can't switch your library. I will see if I can dig into the reason JSon.net isn't picking up the right properties. Maybe a JsonConverter is needed, as you suggested. I will look into this today and tomorrow and back to show the results.

David Elizondo | LLBLGen Support Team
torevor
User
Posts: 17
Joined: 08-Apr-2011
# Posted on: 15-Apr-2011 22:59:15   

My suspicion (wild guess) is the ISerializable interface implementation within the LLBLGen entity objects is where the problem lies. (Again, I am using self-servicing here, fwiw):

        /// <summary> ISerializable member. Does custom serialization so event handlers do not get serialized.</summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        [EditorBrowsable(EditorBrowsableState.Never)]
        protected override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("_originatingApplications", (!this.MarkedForDeletion?_originatingApplications:null));
            info.AddValue("_alwaysFetchOriginatingApplications", _alwaysFetchOriginatingApplications);
            info.AddValue("_alreadyFetchedOriginatingApplications", _alreadyFetchedOriginatingApplications);
            info.AddValue("_person", (!this.MarkedForDeletion?_person:null));
            info.AddValue("_personReturnsNewIfNotFound", _personReturnsNewIfNotFound);
            info.AddValue("_alwaysFetchPerson", _alwaysFetchPerson);
            info.AddValue("_alreadyFetchedPerson", _alreadyFetchedPerson);
            info.AddValue("_application", (!this.MarkedForDeletion?_application:null));
            info.AddValue("_applicationReturnsNewIfNotFound", _applicationReturnsNewIfNotFound);
            info.AddValue("_alwaysFetchApplication", _alwaysFetchApplication);
            info.AddValue("_alreadyFetchedApplication", _alreadyFetchedApplication);

            // __LLBLGENPRO_USER_CODE_REGION_START GetObjectInfo
            // __LLBLGENPRO_USER_CODE_REGION_END
            base.GetObjectData(info, context);
        }

Exactly what to do about it I don't know. simple_smile

Don't bother expending too much effort to figure this out in the short term...but longer term, I think it would probably wise for LLB to "play nice" with the more popular JSON frameworks out there (as other ORM frameworks do).

torevor
User
Posts: 17
Joined: 08-Apr-2011
# Posted on: 15-Apr-2011 23:12:55   

Maybe it's fair to say that a person "shouldn't" be JSON serializing Self-Serviced LLBLGen entities....but then, the question is, what "should" a person (who perhaps happens to be well into a project based on self-servicing), do? Or, what should they do even if they are using Adapter, that certainly isn't clear either.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39888
Joined: 17-Aug-2003
# Posted on: 18-Apr-2011 09:48:53   

What do you mean with:

but what it didn't contain was any of the properties specific to the underlying table

You don't get any properties / values?

Isn't that something JSon.net has to solve? I mean, entities are serializable using remoting and XML, we implement all interfaces necessary for that, and if some random JSon library needs yet another interface or worse, can't deal with code which implements a given interface... we can't fix that.

The properties of the class, they're just there as properties.

What I think happens (but wild guess) is that the JSon serializer simply uses the ISerializable interface if implemented and this indeed adds the values as local members, not the properties. The values you're after are in the fields inside the entity.

As the documentation of JSon.net suggests, if the class to serialize is different from what you want to get out of json, you should use linq to json (I have no idea how that works, I just repeat docs wink ).

I don't know whether JSon.net can serialize from xml or not. If so, adapter has with its compact25 format the format you want as it mimics what you're after.

Frans Bouma | Lead developer LLBLGen Pro
torevor
User
Posts: 17
Joined: 08-Apr-2011
# Posted on: 18-Apr-2011 19:05:56   

Otis wrote:

What do you mean with:

but what it didn't contain was any of the properties specific to the underlying table

You don't get any properties / values?

Correct. So, if I have Name, Address, TelephoneNumber, etc as properties, they will not come out.

Otis wrote:

Isn't that something JSon.net has to solve? I mean, entities are serializable using remoting and XML, we implement all interfaces necessary for that, and if some random JSon library needs yet another interface or worse, can't deal with code which implements a given interface... we can't fix that.

The properties of the class, they're just there as properties.

That's the confusing part, they are just there as properties, so why they aren't coming out is obviously controlled within json.net. As I posted above what I noticed was the elements that are coming out seem to appear in:

/// <summary> ISerializable member. Does custom serialization so event handlers do not get serialized.</summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        [EditorBrowsable(EditorBrowsableState.Never)]
        protected override void GetObjectData(SerializationInfo info, StreamingContext context)

It seems like json.net is doing the exact opposite of what GetObjectData() is intended (based on the function comment within LLB source) to do.
But then GetObjectData() is supposed to: "Populates a SerializationInfo with the data needed to serialize the target object."

So based on that, it would seem to me that json.net is in fact behaving properly, unless I am missing something? (So, if there is NO GetObjectData() implementation, then by default ALL public properties would be serialized; if there is a GetObjectData() implementation, then it will use that instead, which is why we don't see the public properties coming out.) (...unless the properties are in fact added in GetObjectData() via base.GetObjectData(info, context); )

I don't know the right answer as to where its going wrong or who is doing things right or wrong. All I know is it would be nice if this "just worked". JSON is here to stay, passing objects down to (and back from) the javascript layer is incredibly commonplace nowadays, and JSON.Net is quite a popular library. I don't know how to find a simple solution to this requirement with LLBLGen. We are going to take a look at NHibernate to see what they do, I'll let you know if I find out anything interesting.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39888
Joined: 17-Aug-2003
# Posted on: 19-Apr-2011 09:26:59   

GetObjectData is part of ISerializable. It's likely that JSON.net calls GetObjectData when ISerializable is implemented and for every element in the returned info object it generates an element in the json element.

The important part of GetObjectData() is that it calls its base class version as well. This will add the fields data and the change tracking data.

The reason why it is done this way is also because it preserves the state of the entity object. I.e. if you change 2 fields and then serialize it, the deserialized version looks just like it's source: it has 2 fields changed and they're marked as such. If properties would have been serialized/deserialized, it would look like a new entity and all properties would be marked as changed, something you don't want to have in any case.

Additionally, it will traverse contained collections and references to entities without triggering lazy loading. This too is something you won't get with normal serialization, so the current implementation is very valuable.

The fields and their data has to be present in the json as it's present in the data returned by GetObjectData. If not, I don't know what's wrong, but it is present in the data returned by GetObjectData.

The problem is that JSON.net uses ISerializable to obtain its data to serialize, but doesn't consider that the data returned by GetObjectData might not be structured by public property names, but by internal elements. It IMHO assumes the data is generated by the default serializer which is called when you mark a class with [Serializable]: it will reflect over the public properties and serialize them as such.

I'm sure json.net is here to stay, or at least they think it will, and I'm sure it's useful. But it also should make more effort in what is to be expected when the data from GetObjectData comes back: it's a method to serialize inner data to be used to deserialize an exact copy of it later on. This data might look different than the external interface of the object suggests.

Btw, if other 'o/r mappers' support this, it's because they don't have a solution to the roundtrip problem: e.g. entity framework isn't capable of determining whether a deserialized version is new or an existing entity which was deserialized, because the properties are serialized and when you deserialize that data into a new instance of the entity class, it looks just the same as when you new-ed up an entity object and set each property individually.

So, bottom line, if you want to use JSON.net and it uses ISerializable to serialize its data, the data you get out of JSON.net will show the inner data as serialized by the ISerializable interface, because that's what it is suppose to do. What should be done for what you want is what adapter's XML serialization logic does (for compact25). It emits xml as you'd expect and appends a changetracker block with packed data for the state.

Unfortunately, it emits xml, not json and .NET requires IXmlSerializable to be implemented on the object to serialize, not externally. In theory, if JSON.net implements an XMLWriter/Reader combination which act as if they're xml readers/writers but technically emit JSON, by handling the StartElement etc. methods, it would work automatically. But I don't know if it does.

Frans Bouma | Lead developer LLBLGen Pro
howez
User
Posts: 28
Joined: 12-May-2007
# Posted on: 19-Apr-2011 21:05:38   

After reading this thread I have to give my two cents....

Your only option here is to use a Data Transformation Object (DTO). I am using them for all of our ASP.NET 4.0 Rest services. We take a nice robust LLBLGEN object and project it into a PONO (Plain Old ..NET Object) so that it can be serialized to JSON. If you ever looked at the xml that is generated for a LLBLGEN object you will see that there is no way it can be serialized directly to JSON. It is too complex.

We have to do this all over the place. For WF, WCF, Silverlight etc... All of those technologies expect PONOs. Anything more complex it simply cannot handle it.

The solution is easy, use DTOs. I am not epecially happy about the extra step, but in reality it is a small price to pay.

That is why a WCF services layer is completely the wrong direction to go. Especailly using LlBLGEN. There would be an enormous amount of DTOs that are simply there to support a technology (WCF), but in reality the power of LLBLGEN is to expose the complexity of the entities and use it to your advantage not dumb it down, transport it, and then project that 'flattened' data back into a LLBLGEN entity. Believe me once we started to look at this in a production environment it became very clear that WCF should have been named WTF.

My advice is to keep your transport light. JSON is great and I am using it for the REST services, Web Applications (JQuery), and mobile applications (JQuery Mobile/ Phonegap). Dumb the LLBLGEN entities down to make them JSON friendly. Trying to use it any other way is just plain Dumb.

Best...

MTrinder
User
Posts: 1461
Joined: 08-Oct-2008
# Posted on: 19-Apr-2011 21:45:04   

howez advice is fundamentally sound - It is often much easier to transport slimmed down versions of the rich LLBLGen entities rather that the LLBLGen entities themselves.

Matt

DarkRoast
User
Posts: 42
Joined: 14-Jul-2010
# Posted on: 19-Apr-2011 23:13:02   

An option which avoids coding DTOs for each entity or collection is to use the LLBLGen factory classes along with the JsonTextWriter and then iterate the fields collection.


object entityCollection = FactoryClasses.GeneralEntityCollectionFactory.Create(entityType);
    entityCollection.GetMulti(null);

    StringBuilder stringBuilder = new StringBuilder();

    using (StringWriter stringWriter = new StringWriter(stringBuilder)) {
        Newtonsoft.Json.JsonTextWriter jsonWriter = new Newtonsoft.Json.JsonTextWriter(stringWriter);

        jsonWriter.WriteStartObject();
        jsonWriter.WritePropertyName("entities");
        jsonWriter.WriteStartArray();

        foreach (IEntity entity in entityCollection) {
            jsonWriter.WriteStartObject();

            foreach (EntityField field in entity.Fields) {
                jsonWriter.WritePropertyName(field.Name);
                jsonWriter.WriteValue(field.CurrentValue);
            }

            jsonWriter.WriteEndObject();
        }

        jsonWriter.WriteEndArray();
        jsonWriter.WriteEndObject();

    }

    return stringBuilder.ToString;



torevor
User
Posts: 17
Joined: 08-Apr-2011
# Posted on: 19-Apr-2011 23:24:13   

DarkRoast wrote:

An option which avoids coding DTOs for each entity or collection is to use the LLBLGen factory classes along with the JsonTextWriter and then iterate the fields collection.

That's a nice approach, thanks!

Do you know, when one then proceeded to deserialize and persist the objects returned from the client, what LLB function could one use to create a null LLBGen entity object of the correct type (using the name embedded in the json) which could then be re-populated with the updated values?

DarkRoast
User
Posts: 42
Joined: 14-Jul-2010
# Posted on: 20-Apr-2011 01:04:28   

Here are some ideas (untested code)

To create an entity object from a string you can use something like this:


string entityname = HttpContext.Current.Request("entityName");
EntityType entityType = (EntityType)Parse(typeof(EntityType), entityname, true);

IEntityCollection iEntityCollection = FactoryClasses.GeneralEntityCollectionFactory.Create(entityType);

IEntity entity = iEntityCollection.EntityFactoryToUse.Create();

Assuming a JSON string that looks like this " [{Id:1, FirstName:"Bob"}, {Id:2, FirstName:"Tom"}] "

You could deserialize like this:


List<Dictionary<string, string>> entities = JsonConvert.DeserializeObject<List<Dictionary<string, string>>>(HttpContext.Current.Request("entities"));



foreach (Dictionary<string, string> entityItem in entities) {

// For creates: call iEntityCollection.EntityFactoryToUse.Create() for each entity and assign values

   entity = iEntityCollection.EntityFactoryToUse.Create();   


//For updates: obtain the primary key value and filter the collection to get a single entity; then assign values

   object primaryKeyField = entity.PrimaryKeyFields(0);
   string primaryKeyFieldName = primaryKeyField.Name;
   object primaryKeyFieldValue = entityItem.Item(primaryKeyFieldName);

   iEntityCollection.GetMulti(new PredicateExpression(new FieldCompareValuePredicate(primaryKeyField, ComparisonOperator.Equal, primaryKeyFieldValue)));

   entity = iEntityCollection(0);



    foreach (KeyValuePair<string, string> fieldItem in entityItem) {

                    entity.SetNewFieldValue(fieldItem.Key, fieldItem.Value)
    }

entity.Save()
}


Otis avatar
Otis
LLBLGen Pro Team
Posts: 39888
Joined: 17-Aug-2003
# Posted on: 20-Apr-2011 08:55:49   

DarkRoast's approach is similar as the one we took in XML serialization.

I agree with Howez. WHen you choose JSON to transport entities, you're falling back onto the option 'transfer data', not the rest. No change tracking, no nested graphs, just the data. DTOs can help there (you can generate some helper classes which easily transfer your entity objects into a DTO and back).

Btw, the xml serialization for adapter is close to what you'd do by hand:

<CustomerEntity ObjectID="0c40e3c2-1914-47a5-8547-8cae5a994d36" Format="Compact25">
    <Address>Hauptstr. 29 > 3</Address>
    <City>Bern</City>
    <CompanyName>Wha Ing</CompanyName>
    <ContactName>Yang Wang</ContactName>
    <ContactTitle>Owner</ContactTitle>
    <Country>Switserland</Country>
    <CustomerId>CHOPS</CustomerId>
    <Fax>1313-chan</Fax>
    <Phone>555-chang</Phone>
    <PostalCode>3012</PostalCode>
    <Orders>
        <OrderEntity ObjectID="09647586-b062-4c4c-b970-bb71aab42caf">
        <CustomerId>CHOPS</CustomerId>
        <EmployeeId>1</EmployeeId>
        <Freight>31.4300</Freight>
        <OrderDate>1997-11-19T00:00:00</OrderDate>
        <OrderId>10746</OrderId>
        ...
        

this format is compact25 and also contains change tracking info at the end of each entity.

When passing JSON data back to the service, be aware that when you re-create an entity you don't know whether it's new or an existing entity and it was changed. This requires you to fetch the entity, then alter it and then save it again. (or when you know it's a new entity, you can skip the fetch part).

This can be a bit cumbersome, but it's because there's no change tracking info in the JSON blob. So it might be a good idea to at least insert extra info whether the entity is new or not.

Frans Bouma | Lead developer LLBLGen Pro