Самый простой способ сделать то, что вы хотите, это использовать набор классов моделей, которые соответствуют вашему JSON.(Вы можете создать классы в Visual Studio, скопировав полный образец JSON в буфер обмена и затем используя функцию Edit -> Paste Special -> Paste JSON as Classes
.) Заставьте классы модели использовать верхний регистр верблюда для имен свойств (что является стандартным соглашением об именахC # в любом случае), и используйте строки вместо числовых свойств.
Итак, для вашего примера JSON классы моделей будут выглядеть так:
public class RootObject
{
public FirstObject FirstObject { get; set; }
}
public class FirstObject
{
public NestedObject NestedObject { get; set; }
public List<OtherObject> OtherObject { get; set; }
}
public class NestedObject
{
public string AttributeString { get; set; }
public string AttributeNumeric { get; set; }
}
public class OtherObject
{
public string ArrayVal { get; set; } // using string instead of int here
}
Затем, чтобы преобразовать из верхнегослучай верблюда JSON или случай змеи, вы можете сделать это:
var obj = JsonConvert.DeserializeObject<RootObject>(json);
var settings = new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy { ProcessDictionaryKeys = true }
},
Formatting = Formatting.Indented
};
json = JsonConvert.SerializeObject(obj, settings);
Исходный JSON будет десериализован в модель естественным образом, потому что имена свойств уже совпадают.Json.Net автоматически преобразует числовые значения в JSON в строки, необходимые для соответствия свойствам класса.При сериализации в игру вступает SnakeCaseNamingStrategy
, чтобы изменить имена свойств на случай змеи.Числовые значения записываются в виде строк, потому что таким образом свойства объявляются в классах.
Чтобы вернуться другим путем, вы должны сделать:
obj = JsonConvert.DeserializeObject<RootObject>(json, settings); // same settings as above
json = JsonConvert.SerializeObject(obj, Formatting.Indented);
Здесь, во время десериализацииJson.Net использует SnakeCaseNamingStrategy
для преобразования имен свойств модели в случай змеи, чтобы сопоставить их со свойствами JSON.Числовые значения уже являются строками в JSON, поэтому преобразование не требуется.При сериализации мы не используем никаких специальных настроек, поэтому имена свойств записываются точно так, как объявлено, что в верхнем верблюжьем регистре.Строковые свойства, содержащие числовые значения, остаются строками (вы сказали, что это нормально в вашем вопросе).
Вот демонстрация в оба конца: https://dotnetfiddle.net/3Pb1fT
Если вы этого не сделаетеЕсли у вас есть модель, с которой можно работать, все еще возможно выполнить это преобразование, используя предложенный вами подход JsonReader
/ JsonWriter
, но потребуется немного больше кода, чтобы склеить их и выполнить преобразования.Вот вспомогательный метод, который выполнит большую часть тяжелой работы:
public static void ConvertJson(TextReader textReader, TextWriter textWriter,
NamingStrategy strategy,
Formatting formatting = Formatting.Indented)
{
using (JsonReader reader = new JsonTextReader(textReader))
using (JsonWriter writer = new JsonTextWriter(textWriter))
{
writer.Formatting = formatting;
if (reader.TokenType == JsonToken.None)
{
reader.Read();
ConvertJsonValue(reader, writer, strategy);
}
}
}
private static void ConvertJsonValue(JsonReader reader, JsonWriter writer,
NamingStrategy strategy)
{
if (reader.TokenType == JsonToken.StartObject)
{
writer.WriteStartObject();
while (reader.Read() && reader.TokenType != JsonToken.EndObject)
{
string name = strategy.GetPropertyName((string)reader.Value, false);
writer.WritePropertyName(name);
reader.Read();
ConvertJsonValue(reader, writer, strategy);
}
writer.WriteEndObject();
}
else if (reader.TokenType == JsonToken.StartArray)
{
writer.WriteStartArray();
while (reader.Read() && reader.TokenType != JsonToken.EndArray)
{
ConvertJsonValue(reader, writer, strategy);
}
writer.WriteEndArray();
}
else if (reader.TokenType == JsonToken.Integer)
{
// convert integer values to string
writer.WriteValue(Convert.ToString((long)reader.Value));
}
else if (reader.TokenType == JsonToken.Float)
{
// convert floating point values to string
writer.WriteValue(Convert.ToString((double)reader.Value,
System.Globalization.CultureInfo.InvariantCulture));
}
else // string, bool, date, etc.
{
writer.WriteValue(reader.Value);
}
}
Чтобы использовать его, вам просто нужно установить TextReader
для вашего входного JSON и TextWriter
для выходногои передайте соответствующий NamingStrategy
, который вы хотите использовать для преобразования.Например, чтобы преобразовать исходную строку JSON в случай змеи, вы должны сделать следующее:
using (StringReader sr = new StringReader(upperCamelCaseJson))
using (StringWriter sw = new StringWriter())
{
ConvertJson(sr, sw, new SnakeCaseConverter(), formatting);
string snakeCaseJson = sw.ToString();
...
}
Или, если источником и / или местом назначения для вашего JSON является какой-либо поток, вы можете использоватьStreamReader
/ StreamWriter
вместо:
using (StreamReader sr = new StreamReader(inputStream))
using (StreamWriter sw = new StreamWriter(outputStream))
{
ConvertJson(sr, sw, new SnakeCaseConverter(), formatting);
}
Теперь для обратной поездки есть небольшая проблема.A NamingStrategy
работает только в одном направлении;он не предоставляет возможности для обращения конверсии.Это означает, что ни один из NamingStrategy
классов, предоставленных Newtonsoft, не будет работать для преобразования змеиного случая обратно в верхний верблюжий случай, как вы хотите.CamelCaseNamingStrategy
не будет работать, потому что он не ожидает запуска со случая змеи (ему нужен верхний регистр верблюда), и его вывод в любом случае не является верхним верблюдом.DefaultNamingStrategy
тоже не сработает, потому что на самом деле он вообще не выполняет никаких преобразований - это просто проход.
Решение состоит в том, чтобы сделать свой собственный NamingStrategy
.К счастью, это не сложно сделать.Просто наследуйте от базового NamingStrategy
класса и реализуйте абстрактный метод ResolvePropertyName
:
// This naming strategy converts snake case names to upper camel case (a.k.a. proper case)
public class ProperCaseFromSnakeCaseNamingStrategy : NamingStrategy
{
protected override string ResolvePropertyName(string name)
{
StringBuilder sb = new StringBuilder(name.Length);
for (int i = 0; i < name.Length; i++)
{
char c = name[i];
if (i == 0 || name[i - 1] == '_')
c = char.ToUpper(c);
if (c != '_')
sb.Append(c);
}
return sb.ToString();
}
}
Теперь вы можете передать эту новую стратегию методу ConvertJson
, как описано выше, чтобы преобразовать JSON-случай обратнок верхнему верблюжьему делу