问题
When deserializing a Json object into a .Net type
, if the field names don't match up I found you can decorate your type
's properties with [JsonProperty(PropertyName = "name")]
This is fine and dandy for a couple of properties that don't match up, but is there a way to set a convention or rule?
Json
{
"Job": [
{
"Job #": "1",
"Job Type": "A",
}
]
}
C#
[JsonProperty(PropertyName = "Job Type")]
public string JobType { get; set; }
[JsonProperty(PropertyName = "Job #")]
public string JobNumber { get; set; }
I have many fields using similar names, what I would like to figure out, is there a way to tell to set a rule to always remove spaces (EG: Job Type -> JobType
) and replace #
with Number
(eg: Job # -> JobNumber
)?
It looks like a custom ContractResolver
might be the only solution, but I can't seem to figure out how to go about using it to pluck out spaces and replace "#" with "Number". Does anyone have a reference example?
Or, I'm hoping there's a nice simple solution that I've overlooked.
P.S. Also accepting suggestions for a better title.
回答1:
Assuming you are working with Json.NET 9.0.1 or later, this can be done with a custom NamingStrategy. For instance, here's one based on SnakeCaseNamingStrategy and StringUtils.ToSnakeCase() by James Newton-King:
public class CustomNamingStrategy : NamingStrategy
{
public CustomNamingStrategy(bool processDictionaryKeys, bool overrideSpecifiedNames)
{
ProcessDictionaryKeys = processDictionaryKeys;
OverrideSpecifiedNames = overrideSpecifiedNames;
}
public CustomNamingStrategy(bool processDictionaryKeys, bool overrideSpecifiedNames, bool processExtensionDataNames)
: this(processDictionaryKeys, overrideSpecifiedNames)
{
ProcessExtensionDataNames = processExtensionDataNames;
}
public CustomNamingStrategy()
{
}
protected override string ResolvePropertyName(string name)
{
return SpaceWords(name);
}
enum WordState
{
Start,
Lower,
Upper,
NewWord
}
static string SpaceWords(string s)
{
// Adapted from StringUtils.ToSnakeCase()
// https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Utilities/StringUtils.cs#L191
//
// Copyright (c) 2007 James Newton-King
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
char wordBreakChar = ' ';
if (string.IsNullOrEmpty(s))
{
return s;
}
StringBuilder sb = new StringBuilder();
WordState state = WordState.Start;
for (int i = 0; i < s.Length; i++)
{
if (s[i] == ' ')
{
if (state != WordState.Start)
{
state = WordState.NewWord;
}
}
else if (char.IsUpper(s[i]))
{
switch (state)
{
case WordState.Upper:
bool hasNext = (i + 1 < s.Length);
if (i > 0 && hasNext)
{
char nextChar = s[i + 1];
if (!char.IsUpper(nextChar) && nextChar != ' ')
{
sb.Append(wordBreakChar);
}
}
break;
case WordState.Lower:
case WordState.NewWord:
sb.Append(wordBreakChar);
break;
}
sb.Append(s[i]);
state = WordState.Upper;
}
else if (s[i] == wordBreakChar)
{
sb.Append(wordBreakChar);
state = WordState.Start;
}
else
{
if (state == WordState.NewWord)
{
sb.Append(wordBreakChar);
}
sb.Append(s[i]);
state = WordState.Lower;
}
}
sb.Replace("Number", "#");
return sb.ToString();
}
}
Then you can apply it to your type as follows:
[JsonObject(NamingStrategyType = typeof(CustomNamingStrategy))]
public class RootObject
{
public string JobType { get; set; }
public string JobNumber { get; set; }
public int JobItemCount { get; set; }
public string ISOCode { get; set; }
public string SourceXML { get; set; }
}
And the JSON generated will be as follows:
{
"Job Type": "job type",
"Job #": "01010101",
"Job Item Count": 3,
"ISO Code": "ISO 9000",
"Source XML": "c:\temp.xml"
}
Notes:
If you want the strategy to apply to properties that already have property names specified via JsonPropertyAttribute.PropertyName, set NamingStrategy.OverrideSpecifiedNames == true.
To apply your naming strategy to all types rather than setting it on each object, you can set the naming strategy in DefaultContractResolver.NamingStrategy, then set the contract resolver in JsonSerializerSettings.ContractResolver.
The naming strategy maps from the c# property name to the JSON property name, not vice versa. Thus you need to insert spaces rather than "pluck them out" and replace "Number" with "#". The mapping is then cached by the contract resolver and a reverse lookup is done during deserialization.
回答2:
Yes a ContractResolver
is the way to go.
The problem is that these only seem to work going from the destination property to the source, i.e. "JobType" -> "Job Type"
, not the other way as you would like. This makes the solution a bit more flaky than you might want.
First we make our ContractResolver
, inheriting from DefaultContractResolver
, so it all works as normal apart from the bit we want to customize:
public class JobContractResolver : DefaultContractResolver
{
protected override string ResolvePropertyName(string propertyName)
{
// first replace all capital letters with space then letter ("A" => " A"). This might include the first letter, so trim the result.
string result = Regex.Replace(propertyName, "[A-Z]", x => " " + x.Value).Trim();
// now replace Number with a hash
result = result.Replace("Number", "#");
return result;
}
}
Then in our deserialization, we set the ContractResolver
in the JsonSerializerSettings
:
static void Main(string[] args)
{
string input = @"{""Job #"": ""1"", ""Job Type"": ""A""}";
var job1 = JsonConvert.DeserializeObject<Job1>(input, new JsonSerializerSettings
{
ContractResolver = new JobContractResolver()
});
Console.WriteLine("JobType: {0}", job1.JobType);
Console.WriteLine("JobNumber: {0}", job1.JobNumber);
}
来源:https://stackoverflow.com/questions/46476903/how-to-apply-a-general-rule-for-remapping-all-property-names-when-serializing-wi