1

I was trying to follow the Preserving Object References example for my application, but it doesn't seem to work in my test case.

There are three minimal classes:

  • Firm (top)
    • Employee
    • ClientGroup (references Employee in Advisors collection property)

The Employee is deserialized successfully, but the $ref references are ignored in the Advisors collection and an extra blank object is created in its place.

I searched all over for any information on the "Could not find member '$ref'" trace message, but haven't found anything. This question Deserializing $ref and $id suggested adding MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead to the settings. This removed the trace warning but the reference was still replaced by a new blank Employee.

Any help would be most appreciated.

Json:

[
  {
    "$id": "1",
    "Employees": [
      {
        "$id": "2",
        "FirstName": "Billy",
        "LastName": "Thornton",
        "MiddleName": "Bob"
      }
    ],
    "ClientGroups": [
      {
        "$id": "3",
        "Name": "Group A",
        "Advisors": [
          {
            "$ref": "2"
          }
        ]
      }
    ],
    "FullName": "The Firm"
  }
]

The deserialization code is:

// converters create objects for my ORM (XPO)
var converters = new JsonConverter[]
{
   new jsonConverter<Firm>(objectSpace),
   new jsonConverter<Employee>(objectSpace),
   new jsonConverter<ClientGroup>(objectSpace)
};

var settings = new JsonSerializerSettings() {
   Converters = converters,
   TraceWriter = traceWriter,
   Formatting = Formatting.Indented,
   PreserveReferencesHandling = PreserveReferencesHandling.Objects
};

var Firms = JsonConvert.DeserializeObject<List<Firm>>(
   File.ReadAllText($"{seedPathPrefix}{firmSeed}", ASCII),
   settings);

Trace.WriteLine($"Trace:\n{traceWriter}");
// required by ORM for object creation
private class jsonConverter<T> : CustomCreationConverter<T> {
   public jsonConverter(IObjectSpace objectSpace) { this.objectSpace = objectSpace; }

   private IObjectSpace objectSpace;

   public override T Create(Type objectType) {
      if (objectType.IsEnum) return default;
      return objectSpace.CreateObject<T>();
   }
}
jlear
  • 160
  • 1
  • 3
  • 14
  • 1
    If you use a custom `JsonConverter` then it needs to take care of **everything**, including handling of `"$ref"` and `"$id"` properties. See: [Custom object serialization vs PreserveReferencesHandling](https://stackoverflow.com/a/53716866). That look to be a duplicate, but if not, can you share a [mcve] that includes all necessary types? – dbc Aug 23 '22 at 16:13
  • Thank you. The link you provided may be the solution I need, although I was hoping to avoid custom handling (newbie). The ORM requires business objects to have constructors that take the objectSpace parameter. That's why there's CustomCreationConverters. Do you think there is a way around using custom converters, or should I just go all in? – jlear Aug 23 '22 at 18:49
  • 1
    Your question didn't ask about that. Do you need to pre-load the JSON into a `JObject` inside your custom converter for any reason? Or do you only need to call some custom-supplied construction method and for everything else Newtonsoft's standard deserialization works correctly? – dbc Aug 23 '22 at 19:14
  • 1
    If you only need to use a custom creation method and nothing else, I think the `DependencyInjectionContractResolver` from [this answer](https://stackoverflow.com/a/49366916/3744182) by [CodeFuller](https://stackoverflow.com/users/5740031/codefuller) to [How can I use dependency injection with .NET Core 2 API options?](https://stackoverflow.com/q/49362734/3744182) should do what you need. The custom `DefaultCreator()` will only get called when the `"$ref"` property does not resolve to an already-created object. Does that answer your question? Should I add it to the duplicate list? – dbc Aug 23 '22 at 19:18
  • Edit: "Your question didn't ask about that." Sorry, wasn't aware there were alternatives. "Or do you only need to call some custom-supplied construction method and for everything else Newtonsoft's standard deserialization works correctly?" This. I only need to call the custom constructor. I'll check out the question you referenced. Your two answers seem to provide possible solutions and probably should both be included. Thanks again. – jlear Aug 23 '22 at 19:56

0 Answers0