desautelsj
desautelsj

Reputation: 3725

Configure Json.net to URL encode certain field

Say I have a model class like this:

public class MyModelClass
{
    [JsonProperty("first_field"]
    public string FirstField { get; set; }

    [JsonProperty("second_field"]
    public string SecondField { get; set; }

    public MyModelClass(string first, string second)
    {
        FirstField = first;
        SecondField = second;
    }
}

and let's say I have an instance of this type:

var myObject = new MyModelClass("blablabla", "<>@%#^^@!%");

When I convert this object into a Json string using Json.net, I get something like:

{ 'first_field': 'blablabla', 'second_field': '<>@%#^^@!%' }

Is there a way to configure Json.net so that the content of 'SecondField' is URL encoded? Do I need to write my own custom converter or is there a simpler way?

Upvotes: 4

Views: 3894

Answers (3)

desautelsj
desautelsj

Reputation: 3725

The answer provided by @brian-rogers is excellent, but I wanted to present an alternative that, I believe, is simpler:

public class UrlEncodingConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(string);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value == null) return;
        var encodedValue = System.Web.HttpUtility.UrlEncode((string)value);
        writer.WriteValue(encodedValue);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.Value == null) return null;
        var decodedValue = System.Web.HttpUtility.UrlDecode(reader.Value.ToString());
        return decodedValue;
    }
}

which can be used like so:

public class MyModelClass
{
    [JsonProperty("first_field")]
    public string FirstField { get; set; }

    [JsonProperty("second_field")]
    [JsonConverter(typeof(UrlEncodingConverter))]
    public string SecondField { get; set; }
}

Upvotes: 1

Brian Rogers
Brian Rogers

Reputation: 129797

If it's just one field in one class, you could simply add a read-only property to your class to handle the encoding, and annotate it such that it will take the place of the original property during serialization:

    public class MyModelClass
    {
        ...

        [JsonIgnore]
        public string SecondField { get; set; }

        [JsonProperty("second_field")]
        private string UrlEncodedSecondField
        {
            get { return System.Web.HttpUtility.UrlEncode(SecondField); }
        }

        ...
    }

Demo fiddle: https://dotnetfiddle.net/MkVBVH


If you need this for multiple fields across multiple classes, you could use a solution similar to the one in Selectively escape HTML in strings during deserialization, with a couple of minor changes:

  1. Create a custom UrlEncode attribute and have the resolver look for properties with that attribute rather than the absence of an AllowHtml attribute.
  2. Change the HtmlEncodingValueProvider to a UrlEncodingValueProvider and have it apply the encoding in GetValue rather than SetValue (so that it does the encoding on serialization rather than deserialization).

Here is what the resulting code would look like:

public class UrlEncodeAttribute : Attribute { }

public class CustomResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);

        // Find all string properties that have a [UrlEncode] attribute applied
        // and attach an UrlEncodingValueProvider instance to them
        foreach (JsonProperty prop in props.Where(p => p.PropertyType == typeof(string)))
        {
            PropertyInfo pi = type.GetProperty(prop.UnderlyingName);
            if (pi != null && pi.GetCustomAttribute(typeof(UrlEncodeAttribute), true) != null)
            {
                prop.ValueProvider = new UrlEncodingValueProvider(pi);
            }
        }

        return props;
    }

    protected class UrlEncodingValueProvider : IValueProvider
    {
        PropertyInfo targetProperty;

        public UrlEncodingValueProvider(PropertyInfo targetProperty)
        {
            this.targetProperty = targetProperty;
        }

        // SetValue gets called by Json.Net during deserialization.
        // The value parameter has the original value read from the JSON;
        // target is the object on which to set the value.
        public void SetValue(object target, object value)
        {
            targetProperty.SetValue(target, (string)value);
        }

        // GetValue is called by Json.Net during serialization.
        // The target parameter has the object from which to read the string;
        // the return value is the string that gets written to the JSON
        public object GetValue(object target)
        {
            string value = (string)targetProperty.GetValue(target);
            return System.Web.HttpUtility.UrlEncode(value);
        }
    }
}

To use the custom resolver, first decorate any properties that you want to be URL encoded with the new [UrlEncode] attribute:

public class MyModelClass
{
    [JsonProperty("first_field")]
    public string FirstField { get; set; }

    [UrlEncode]
    [JsonProperty("second_field")]
    public string SecondField { get; set; }

    ...
}

Then, serialize your model like this:

var myObject = new MyModelClass("blablabla", "<>@%#^^@!%");

var settings = new JsonSerializerSettings
{
    ContractResolver = new CustomResolver(),
    Formatting = Formatting.Indented
};

string json = JsonConvert.SerializeObject(myObject, settings);

Demo fiddle: https://dotnetfiddle.net/iOOzFr

Upvotes: 6

jan.q
jan.q

Reputation: 21

Try this:

var myObject = new MyModelClass("blablabla", WebUtility.UrlEncode("<>@%#^^@!%"));

You need to add System.Net to your Using-directives

Upvotes: -1

Related Questions