Как конвертировать JSON в YAML, используя YamlDotNet

Я пытаюсь конвертировать JSON в YAML, используя YamlDotNet. Вот код, который у меня есть:

class Program
{
    static void Main(string[] args)
    {
        var json = "{\"swagger\":\"2.0\",\"info\":{\"title\":\"UberAPI\",\"description\":\"MoveyourappforwardwiththeUberAPI\",\"version\":\"1.0.0\"},\"host\":\"api.uber.com\",\"schemes\":[\"https\"],\"basePath\":\"/v1\",\"produces\":[\"application/json\"]}";
        var swaggerDocument = JsonConvert.DeserializeObject(json);

        var serializer = new YamlDotNet.Serialization.Serializer();

        using (var writer = new StringWriter())
        {
            serializer.Serialize(writer, swaggerDocument);
            var yaml = writer.ToString();
            Console.WriteLine(yaml);
        }
    }
}

Это JSON, который я предоставляю:

{
   "swagger":"2.0",
   "info":{
      "title":"UberAPI",
      "description":"MoveyourappforwardwiththeUberAPI",
      "version":"1.0.0"
   },
   "host":"api.uber.com",
   "schemes":[
      "https"
   ],
   "basePath":"/v1",
   "produces":[
      "application/json"
   ]
}

Это YAML, я ожидаю:

swagger: '2.0'
info:
  title: UberAPI
  description: MoveyourappforwardwiththeUberAPI
  version: 1.0.0
host: api.uber.com
schemes:
  - https
basePath: /v1
produces:
  - application/json

Тем не менее, это вывод, который я получаю:

swagger: []
info:
  title: []
  description: []
  version: []
host: []
schemes:
- []
basePath: []
produces:
- []

Я понятия не имею, почему все свойства являются пустыми массивами.

Я также попробовал набрать десериализацию и сериализацию следующим образом:

var specification = JsonConvert.DeserializeObject<SwaggerDocument>(json);
...
serializer.Serialize(writer, swaggerDocument, typeof(SwaggerDocument));

Но это производит

{}

Буду признателен за любую оказанную помощь.

8 ответов

Решение

Я думаю, что есть проблема, когда возвращается десериализация JSON JObject, Похоже, сериализатору yaml это не нравится.

Я использовал десериализацию с указанным типом, как вы упомянули JsonConvert.DeserializeObject<SwaggerDocument>(json) и это то, что я получаю

Swagger: 2.0
Info:
  Title: UberAPI
  Description: MoveyourappforwardwiththeUberAPI
  Version: 1.0.0
Host: api.uber.com
Schemes:
- https
BasePath: /v1
Produces:
- application/json

Это весь мой код:

class Program
{
    static void Main(string[] args)
    {
        var json = "{\"Swagger\":\"2.0\",\"Info\":{\"Title\":\"UberAPI\",\"Description\":\"MoveyourappforwardwiththeUberAPI\",\"Version\":\"1.0.0\"},\"Host\":\"api.uber.com\",\"Schemes\":[\"https\"],\"BasePath\":\"/v1\",\"Produces\":[\"application/json\"]}";
        var swaggerDocument = JsonConvert.DeserializeObject<SwaggerDocument>(json);

        var serializer = new YamlDotNet.Serialization.Serializer();

        using (var writer = new StringWriter())
        {
            serializer.Serialize(writer, swaggerDocument);
            var yaml = writer.ToString();
            Console.WriteLine(yaml);
        }
    }
}

public class Info
{
    public string Title { get; set; }
    public string Description { get; set; }
    public string Version { get; set; }
}

public class SwaggerDocument
{
    public string Swagger { get; set; }
    public Info Info { get; set; }
    public string Host { get; set; }
    public List<string> Schemes { get; set; }
    public string BasePath { get; set; }
    public List<string> Produces { get; set; }
}

Обновить

Два вопроса здесь.

При десериализации класса с полями по умолчанию json.net не будет принимать их во внимание при выполнении своей работы. Для этого нам нужно настроить процессы десериализации, создав собственный распознаватель контрактов. Мы можем легко сделать это,

var swaggerDocument = JsonConvert.DeserializeObject<SwaggerDocument>(json, new JsonSerializerSettings
{
    ContractResolver = new MyContractResolver()
});

public class MyContractResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Select(p => base.CreateProperty(p, memberSerialization))
            .Union(type.GetFields(BindingFlags.Public | BindingFlags.Instance)
                .Select(f => base.CreateProperty(f, memberSerialization)))
            .ToList();
        props.ForEach(p => { p.Writable = true; p.Readable = true; });
        return props;
    }
}

Есть вторая проблема, когда мы хотим сериализовать класс с полями. Значения из полей не будут включены в результат yaml. Я еще не понял, как с этим бороться.

Вы должны использовать Swashbuckle.Swagger типа или вы можете просто создать оболочку / декоратор /DTO для этого типа?

Я надеюсь, что это поможет вам.

На самом деле вам не нужно десериализовать JSON в строго типизированный объект, вы можете конвертировать JSON в YAML, используя динамический объект Expando. Вот небольшой пример:

var json = @"{
        'Name':'Peter',
        'Age':22,
        'CourseDet':{
                'CourseName':'CS',
                'CourseDescription':'Computer Science',
                },
        'Subjects':['Computer Languages','Operating Systems']
        }";

        var expConverter = new ExpandoObjectConverter();
        dynamic deserializedObject = JsonConvert.DeserializeObject<ExpandoObject>(json, expConverter);

        var serializer = new YamlDotNet.Serialization.Serializer();
        string yaml = serializer.Serialize(deserializedObject);

Вы можете увидеть подробное объяснение обоих методов, то есть использование строго типизированного объекта и динамического объекта здесь.

Вы можете конвертировать JObject к более простому объекту, который YamlDotNet может сериализовать:

class Program
{
    static void Main(string[] args)
    {
        var json = "{\"swagger\":\"2.0\",\"info\":{\"title\":\"UberAPI\",\"description\":\"MoveyourappforwardwiththeUberAPI\",\"version\":\"1.0.0\"},\"host\":\"api.uber.com\",\"schemes\":[\"https\"],\"basePath\":\"/v1\",\"produces\":[\"application/json\"]}";
        var swaggerDocument = ConvertJTokenToObject(JsonConvert.DeserializeObject<JToken>(json));

        var serializer = new YamlDotNet.Serialization.Serializer();

        using (var writer = new StringWriter())
        {
            serializer.Serialize(writer, swaggerDocument);
            var yaml = writer.ToString();
            Console.WriteLine(yaml);
        }
    }

    static object ConvertJTokenToObject(JToken token)
    {
        if (token is JValue)
            return ((JValue)token).Value;
        if (token is JArray)
            return token.AsEnumerable().Select(ConvertJTokenToObject).ToList();
        if (token is JObject)
            return token.AsEnumerable().Cast<JProperty>().ToDictionary(x => x.Name, x => ConvertJTokenToObject(x.Value));
        throw new InvalidOperationException("Unexpected token: " + token);
    }
}

FWIW Я написал библиотеку nuget, чтобы YamlDotNet отлично работал с Json.Net, соблюдая все атрибуты сериализации JSON.net.

          var yaml = YamlConvert.SerializeObject(obj);
    var obj2 = YamlConvert.DeserializeObject<T>(yaml);

Он работает, добавляя класс сериализации типа YamlDotNet для JTokens (JObject/JArray/JValue)

          var serializer = new SerializerBuilder()
         .WithTypeConverter(new JTokenYamlConverter())
         .Build();

Если вы начинаете со строки JSON, как в OP, и вам нужен идиоматический вывод YAML, стоит отметить, что JSON является действительным YAML, поэтому YamlDotNet может загрузить его напрямую, тогда простой посетитель может настроить «стиль» перед записью это обратно.

      using System;
using System.IO;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.RepresentationModel;

public static void Main()
{
    var json = "{\"swagger\":\"2.0\",\"info\":{\"title\":\"UberAPI\",\"description\":\"MoveyourappforwardwiththeUberAPI\",\"version\":\"1.0.0\"},\"host\":\"api.uber.com\",\"schemes\":[\"https\"],\"basePath\":\"/v1\",\"produces\":[\"application/json\"]}";
    var yaml = new YamlStream();
    yaml.Load(new StringReader(json));
    yaml.Accept(new JsonToYamlConverterVisitor());
    var outputYaml = new StringWriter();
    yaml.Save(outputYaml, false);
    outputYaml.Flush();
    var yamlString = outputYaml.ToString();
    Console.WriteLine(yamlString);
}

class JsonToYamlConverterVisitor : YamlVisitorBase
{
    public override void Visit(YamlScalarNode scalar)
    {
        if (scalar.Style == ScalarStyle.DoubleQuoted && (scalar.Value == "null" || scalar.Value == "true" || scalar.Value == "false"))
            scalar.Style = ScalarStyle.DoubleQuoted;
        else scalar.Style = ScalarStyle.Plain;
        base.Visit(scalar);
    }

    public override void Visit(YamlSequenceNode sequence)
    {
        sequence.Style = SequenceStyle.Block;
        base.Visit(sequence);
    }

    public override void Visit(YamlMappingNode mapping)
    {
        mapping.Style = MappingStyle.Block;
        base.Visit(mapping);
    }
}

который выводит

      swagger: 2.0
info:
  title: UberAPI
  description: MoveyourappforwardwiththeUberAPI
  version: 1.0.0
host: api.uber.com
schemes:
- https
basePath: /v1
produces:
- application/json
...

Запустите этот пример на DotNetFiddle: https://dotnetfiddle.net/YUJjzQ .

      var serializer = new SharpYaml.Serialization.Serializer();
var yaml = serializer.Deserialize(jObject.ToString());
return serializer.Serialize(yaml);

YAML поддерживает десериализацию JSON, поэтому вы можете либо преобразовать JObject в строку, либо просто передать сериализатору свой JSON напрямую, без использования JObject.

Используйте Cinchoo ETL — библиотеку с открытым исходным кодом, чтобы легко выполнить такое преобразование.

      using (var r = new ChoJSONReader("*** YOUR JSON FILEPATH ***"))
{
    using (var w = new ChoYamlWriter("*** YAML FILE OUTPUT PATH ***").SingleDocument())
    {
        w.Write(r);
    }
}

Выход:

      swagger: 2.0
info:
  title: UberAPI
  description: MoveyourappforwardwiththeUberAPI
  version: 1.0.0
host: api.uber.com
schemes:
  - https
basePath: /v1
produces:
  - application/json

Пример скрипта: https://dotnetfiddle.net/rbOD0o

Отказ от ответственности: я являюсь автором этой библиотеки.

Я использую следующий фрагмент кода для создания элемента Yaml из JSON и записи его в файл.

Вот код:

    public static void BuildParametrizedYAML(string element, string element1)
    {
        var jsonBreakers = @"
        {
            'watchers' : {
                'timer' : '10',
                'watcherPool' : '5',
                's3fileExtension' : '.avr.gz',
                'maxRetriesTask' : '3',
                'telemetryFolder' : '/data',
                'telemetryProcessor' : { 
                    'url' : '"+ element1 + @"'
                },
                'breakers' : 
                [
                    {
                        'breakerId' : 'COMMANDER',
                        'firstRetryTimeout' : '1000',
                        'secondRetryTimeout' : '6000',
                        'retries' : '5'
                    },
                    {
                        'breakerId' : 'PROCESSOR',
                        'firstRetryTimeout' : '1000',
                        'secondRetryTimeout' : '6000',
                        'retries' : '30'
                    }
                ],
                'servers' : 
                [
                    {
                        'serverId' : 'vmax',
                        'url' : '"+ element + @"'
                    }
                ]
            }
        }";

        var expConverter = new ExpandoObjectConverter();
        dynamic deserializedObject = JsonConvert.DeserializeObject<ExpandoObject>(jsonBreakers, expConverter);           
        var serializer = new Serializer();
        string JSONContent = serializer.Serialize(deserializedObject);

        var streamLoad = new StringReader(JSONContent);
        var stream = new YamlStream();
        stream.Load(streamLoad);

        using (TextWriter writer = File.CreateText("application.yml"))
        {
            stream.Save(writer, false);
        }
    }

И вот вывод:

watchers:
  timer: 10
  watcherPool: 5
  s3fileExtension: .avr.gz
  maxRetriesTask: 3
  telemetryFolder: /data
  telemetryProcessor:
    url: TELEMETRYPROCESSORURL
  breakers:
  - breakerId: COMMANDER
    firstRetryTimeout: 1000
    secondRetryTimeout: 6000
    retries: 5
  - breakerId: PROCESSOR
    firstRetryTimeout: 1000
    secondRetryTimeout: 6000
    retries: 30
  servers:
  - serverId: vmax
    url: TELEMETRYWATCHERVMAXURL
...

Не стесняйтесь писать мне об этом.

Другие вопросы по тегам