2

I have a WCF REST service which has a resource which contains several typed fields, and then a field which can be an array of objects. I want the field on our service to serialize this field as if it were a string. Example:

[DataContract]
public class User
{
   [DataMember]
   public long ID;

   [DataMember]
   public string Logon;

   [DataMember]
   public string Features; 
}

When users of our API POST a new User object, I'd like them to be able to do use something like this as the body:

{
    "ID" : 123434,
    "Logon" : "MyLogon",
    "Features" : [ 
           { "type": "bigFeature", "size": 234, "display":true },
           { "type": "smFeature", "windowCount": 234, "enableTallness": true}
     ]
 }

instead of

{
    "ID" : 123434,
    "Logon" : "MyLogon",
    "Features" : "[ 
           { \"type\": \"bigFeature\", \"size\": 234, \"display\":true },
           { \"type\": \"smFeature\", \"windowCount\": 234, \"enableTallness\": true}
     ]"
 }

On the service side, I'm going to be saving the "Features" array as JSON text blog in the database, and when I return the Object on GET calls, I'd like it to round trip properly.

dbc
  • 104,963
  • 20
  • 228
  • 340
bpeikes
  • 3,495
  • 9
  • 42
  • 80
  • So you want to take your JSON string and then be able to serialize it/deserialize it via a function call? – Pseudonym Jun 04 '15 at 20:43
  • No, I wan to use DataContract and DataMember to define the data, but I'd like one of the fields to actually be a JSON Array, which would allow it to contain any JSON Object. That would then allow me to convert back and forth. – bpeikes Jun 04 '15 at 20:46
  • What serializer are you using -- `DataContractJsonSerializer` or Json.NET? – dbc Jun 04 '15 at 20:50
  • `DataContractJsonSerializer`, I've considered switching the the Json.NET one, but I remember having issues with it. Were you thinking that this might be possible with the Json.NET serializer? – bpeikes Jun 04 '15 at 21:07
  • 1
    It would be easier with Json.NET. `DataContractJsonSerializer` shares a code base with `DataContractSerializer` for XML (both inherit from `XmlObjectSerializer`) and doesn't give access to the "raw" JSON as far as I know. – dbc Jun 04 '15 at 21:50
  • How would you do it if you switched to Json.Net as the serializer? – bpeikes Jun 04 '15 at 21:52

1 Answers1

2

If you were willing to switch to Json.NET, you could serialize your Features string as a private JToken proxy property:

[DataContract]
public class User
{
    [DataMember]
    public long ID;

    [DataMember]
    public string Logon;

    string _features = null;

    [IgnoreDataMember]
    public string Features
    {
        get
        {
            return _features;
        }
        set
        {
            if (value == null)
                _features = null;
            else
            {
                JToken.Parse(value); // Throws an exception on invalid JSON.
                _features = value;
            }
        }
    }

    [DataMember(Name="Features")]
    JToken FeaturesJson
    {
        get
        {
            if (Features == null)
                return null;
            return JToken.Parse(Features);
        }
        set
        {
            if (value == null)
                Features = null;
            else
                Features = value.ToString(Formatting.Indented); // Or Formatting.None, if you prefer.
        }
    }
}

Note that, in order to serialize the Features string without escaping, it must be valid JSON, otherwise your outer JSON will be corrupt. I enforce this in the setter. You could use JArray instead of JToken to enforce the requirement that the string represent a JSON array, if you prefer.

Note that the string formatting isn't preserved during serialization.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • I tried using a JToken as the DataMember, but I get a Deserialization exception: "Type 'Newtonsoft.Json.Linq.JToken' is a recursive collection data contract which is not supported. Consider modifying the definition of collection 'Newtonsoft.Json.Linq.JToken' to remove references to itself.", I think I need to switch from using the DataContractJsonSerializer to Newtonsoft, but I haven't found a good example of how to do that. – bpeikes Jun 04 '15 at 22:10
  • @bpeikes - Yes, you would need to switch. See http://stackoverflow.com/questions/3118504/how-to-set-json-net-as-the-default-serializer-for-wcf-rest-service or http://stackoverflow.com/questions/11003016/c-sharp-wcf-rest-how-do-you-use-json-net-serializer-instead-of-the-default-dat or https://code.msdn.microsoft.com/windowsdesktop/Supporting-different-data-b0351c9a. – dbc Jun 04 '15 at 22:15
  • The first article requires changing all of your DataContracts to return Message or Stream, which I'm not fond of. The second one points to this article, which I saw a while back, but the code never even compiled correctly: http://blogs.msdn.com/b/carlosfigueira/archive/2011/05/03/wcf-extensibility-message-formatters.aspx – bpeikes Jun 04 '15 at 22:36
  • Looks like using the code example in carlogsfigureira blog and the msdn example fail to note that they don't work with UriTemplates, which I use extensively. Have you gotten the code to work with UriTemplates? It works well with my POST requests, but my GET and DELETE requests need them. – bpeikes Jun 04 '15 at 23:41
  • @bpeikes - no, I haven't, you might need to ask another question for that. Just how complex are the `Feature` objects? Are they key/value pairs, or can they be anything? If are you using .Net 4.5 and have [`UseSimpleDictionaryFormat`](https://msdn.microsoft.com/en-us/library/system.runtime.serialization.json.datacontractjsonserializersettings.usesimpledictionaryformat%28v=vs.110%29.aspx) you might represent your features as a `List>`. – dbc Jun 05 '15 at 00:10
  • 1
    They could be any valid json. Custom serialization with a SimpleDictionary won't suffice. – bpeikes Jun 05 '15 at 18:13