15

How do I control the serialization of a JObject to string?

I have some APIs that return a JObject and I usually apply some changes and persist or return them. I want to avoid persisting null properties and apply some additional formatting, but JsonConvert seems to completely ignore my settings.

Here is the sample of the problem:

// startup.cs has the following
services.AddMvc().AddJsonOptions(o =>
{
    o.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
});

public class SampleController : Controller
{
    JsonSerializerSettings _settings = new JsonSerializerSettings
    {
        NullValueHandling = NullValueHandling.Ignore
    };

    [HttpPost]
    [Route("object")]
    public object PostObject([FromBody] SomeObject data)
    {
        return JsonConvert.SerializeObject(data, _settings);
    }

    [HttpPost]
    [Route("jobject")]
    public object PostJObject([FromBody] JObject data)
    {
        return JsonConvert.SerializeObject(data, _settings);
    }

    public class SomeObject
    {
        public string Foo { get; set; }
        public string Bar { get; set; }
    }
}

Posting { "Foo": "Foo", "Bar": null }:

  • /object returns {"Foo":"Foo"}
  • /jobject returns {"Foo":"Foo","Bar":null}

I want the JObject method to return the same output json as if I were using an object. How do I achieve this without creating helpers? Is there a way to serialize the JObject using the same settings?

Natan
  • 4,686
  • 5
  • 30
  • 48
  • Doesn't `JObject` have a `ToString` method that returns JSON? – Heretic Monkey Oct 25 '16 at 16:30
  • 2
    I don't think you can "edit" a `JObject` during serialization with `JsonConvert.SerializObject()`. A `JObject` has already been serialized so all `JsonConvert` is doing is emit it more-or-less verbatim. See [Newtonsoft Json.Net serialize JObject doesn't ignore nulls, even with the right settings](https://stackoverflow.com/questions/29258587). For a workaround see [JSON.NET serialize JObject while ignoring null properties](https://stackoverflow.com/questions/33027409), – dbc Oct 25 '16 at 17:12
  • @MikeMcCaughan JObject.ToString converts the object to json, but doesn't have any overload to accept the settings object. – Natan Oct 25 '16 at 18:32

3 Answers3

10

A solution that well integrates with NewtonSoft framework is to provide a custom JObject converter that honours the NamingStrategy.

JObject Custom Converter

public class JObjectNamingStrategyConverter : JsonConverter<JObject> {

private NamingStrategy NamingStrategy { get; }

public JObjectNamingStrategyConverter (NamingStrategy strategy) {
    if (strategy == null) {
        throw new ArgumentNullException (nameof (strategy));
    }
    NamingStrategy = strategy;
}

public override void WriteJson (JsonWriter writer, JObject value, JsonSerializer serializer) {
    writer.WriteStartObject ();
    foreach (JProperty property in value.Properties ()) {
        var name = NamingStrategy.GetPropertyName (property.Name, false);
        writer.WritePropertyName (name);
        serializer.Serialize (writer, property.Value);
    }
    writer.WriteEndObject ();
}

public override JObject ReadJson (JsonReader reader, Type objectType, JObject existingValue, bool hasExistingValue, JsonSerializer serializer) {
    throw new NotImplementedException ();
}
}

Usage

var snakeNameStrategy = new SnakeCaseNamingStrategy ();
var jsonSnakeSettings = new JsonSerializerSettings {
Formatting = Formatting.Indented,
Converters = new [] { new JObjectNamingStrategyConverter (snakeNameStrategy) },
   ContractResolver = new DefaultContractResolver {
       NamingStrategy = snakeNameStrategy
   },
};

var json = JsonConvert.SerializeObject (obj, jsonSnakeSettings);

You can find a working PoC on GitHub.

Lord of the Goo
  • 1,214
  • 15
  • 31
  • This seems to answer the question perfectly. Just implemented this in .net Core 3. Further to this answer though, if it's relevant for all JObject, you can simply add the converter at root level of the setup of NewtonsoftJson in the serializer settings converters. `MvcNewtonsoftJsonOptions.SerializerSettings.Converters.Add` – dan richardson Nov 21 '19 at 10:23
8

The only way I was able to do this is by first converting the JObject to a string, then deserializing that string into an ExpandoObject (don't deserialize to object because you'll get back a JObject). The ExpandoObject is like a dictionary, which will cause JsonConvert to actually invoke the configured name case strategy. I'm not sure why the author of Newtonsoft.Json didn't handle JObject types the same way as they seem to be doing for dictionary types, but at least this work around works.

Example:

// Construct a JObject.
var jObject = JObject.Parse("{ SomeName: \"Some value\" }");

// Deserialize the object into an ExpandoObject (don't use object, because you will get a JObject).
var payload = JsonConvert.DeserializeObject<ExpandoObject>(jObject.ToString());

// Now you can serialize the object using any serializer settings you like.
var json = JsonConvert.SerializeObject(payload, new JsonSerializerSettings
{
    ContractResolver = new DefaultContractResolver
    {
        NamingStrategy = new CamelCaseNamingStrategy
        {
            // Important! Make sure to set this to true, since an ExpandoObject is like a dictionary.
            ProcessDictionaryKeys = true,
        }
    }
}
);

Console.WriteLine(json); // Outputs: {"someName":"Some value"}

I picked-up the trick with the ExpandoObject here: JObject & CamelCase conversion with JSON.Net

Sipke Schoorstra
  • 3,074
  • 1
  • 20
  • 28
  • Gave this an upvote because it helped me solve a similar problem where serializing a JObject was outputting unusable arrays instead of values in our JSON log format. So Deserializing any JObject to an ExpandoObject and serializing that got me past my problem. – Montané Hamilton Aug 02 '21 at 01:27
-6

When a JObject is serialized its raw JSON is written. JsonSerializerSettings do not effect its written JSON.

James Newton-King
  • 48,174
  • 24
  • 109
  • 130
  • 7
    -1; this answer doesn't mean anything to me. What's the "raw JSON" of a `JObject`? The term isn't used in the `JObject` docs, and doesn't have an obvious meaning, especially given that a `JObject` can contain `JToken`s like dates and GUIDs that aren't part of the JSON spec and have multiple possible serialisations. – Mark Amery Jun 19 '17 at 14:46
  • 3
    @MarkAmery **You realize James wrote/maintains Json.NET, right?** ;^D I think he's just saying that `JObject` isn't considered a first-class object by Json.NET and that it'll ignore your serializer settings (maybe with the thought that, as a `JObject`, it's already been serialized). If you want to serialize your `JObject`, you have to first deserialize it into a true strong type. Explains what OP and I've seen, and jibes with Sipke's & Goo's solutions (& dbc's comment). – ruffin Sep 23 '19 at 21:08
  • 1
    @ruffin Yes, I realise that James is the project author. And no doubt the concept of a `JObject`'s "raw JSON" makes perfect sense *to him*, based upon the abstractions used inside the Json.NET codebase or perhaps just inside James's head. But that doesn't change the fact that it's basically nonsense to the rest of us. – Mark Amery Oct 08 '19 at 10:38
  • 1
    Whilst I appreciate the efforts of authoring this great lib, this is a very abrupt "answer". Whether this has been added more recently to 2016 or not, the question can be answered by adding a serializer converter for JObject to serialize as required - see other answer https://stackoverflow.com/a/51272107/110495 – dan richardson Nov 21 '19 at 10:26