Language/C#

[C#] JSON 파싱 및 DLL 배포(1)

nowkoes 2024. 7. 9. 14:16

JSON 파싱

개요

출처: https://www.json.org/json-en.html

 

 이전 게시글에선 nlohmann의 json.hpp를 이용해 JSON 파일을 파싱하는 방법을 다루었다. 이번에는 C#에서 제공하는 System.Text.Json 라이브러리를 사용하여 JSON 파일을 파싱하고, 해당 구조를 클래스로 묶어 DLL 파일로 배포하는 방법을 알아보겠다. JSON에 대한 내용은 해당 링크에 간략하게 설명해뒀으니, 해당 게시글을 읽기 전 필수적인 정보(키와 값의 개념) 정도만 확인하고 오자.


본문

JSON 파일 파싱

 

 C# 에서는 Nuget 패키지 관리자를 통해 Json 파일을 파싱하는 라이브러리를 임포트할 수 있다. 이때 Newtonsoft.Json를 임포트하는 방법이 있고, System.Text.Json을 임포트하는 방법이 있다. 필자는 Visual Studio 2015 dotnet framework 4.8 버전을 쓰고 있어, 전자의 뉴턴소프트를 사용할 수가 없다. 따라서 후자의 라이브러리를 사용하여 설명하도록 하겠다. 해당 패키지가 없다면, Nuget 패키지 관리를 열어 설치해 주도록 하자. 

 

 

 다음과 같은 JSON 파일이 있다고 가정해 보자. 예를 들어 slcan 프로토콜로 연결된 PC에서 t27580101010101010101이라는 메시지를 받았으면, 해당 문자열에서 id, dlc, signal을 추출하는 것이 목표다. (다음 접은글은 CAN 통신에 대한 이해가 없다면 생략해도 좋음) 

더보기

해당 구조는 CAN DBC를 참조하여 JSON 파일을 만들어둔 것으로, PC에서 CAN Controller에 보내는 input message, CAN Controller에서 PC로 보내는 output message 두 개가 존재한다. 이때 각 메시지마다 id, dlc, signal이라는 값이 있다.

 

using System;
using System.IO;
using System.Text.Json;

namespace program
{
    public class Program
    {
        public static void Main(string[] args)
        {
            string path = @"your_path";
        }
    }
}
string path = "C:\\Users\\Username\\Documents\\file.txt"; // @ 사용하기 전
string path = @"C:\Users\Username\Documents\file.txt"; // @ 사용하기 후

 

 먼저 다음과 같이 System.Text.Json 라이브러리를 호출하고, json 파일이 저장되어 있는 경로를 문자열로 초기화해 준다. 이때 경로를 포함한 문자열은 일반적으로 축자 문자열 리터럴(verbatim string literal)로 만들기 위해서인데, 이를 사용하면 문자열 내의 특수 문자를 그대로 사용할 수 있다. 즉, 백슬래시를 이스케이프하지 않고도 사용할 수 있다. 또한 JSON 파일에서 내용을 읽기 위해 System.IO도 임포트하였다.

 

    public static void Main(string[] args)
    {
        string path = @"your_path";
        string json_file = File.ReadAllText(path);

        Console.WriteLine(json_file);
    }

출력

 

 그리고 File.ReadAllText 함수를 이용하여 지정된 파일의 내용을 모두 읽어 들여 문자열로 반환시키자. 해당 메소드 외에도 파일을 읽는 방법은 여러 가지가 존재하나, 일단은 JSON 파일을 파싱하는 것에 집중하고 추후에 따로 다루도록 하겠다. 

 

public class Signal
    {
        [JsonPropertyName("name")]
        public string Name { get; set; }

        [JsonPropertyName("start_bit")]
        public int StartBit { get; set; }

        [JsonPropertyName("bit_length")]
        public int BitLength { get; set; }

        [JsonPropertyName("factor")]
        public double Factor { get; set; }

        [JsonPropertyName("offset")]
        public int Offset { get; set; }
    }

    public class Message
    {
        [JsonPropertyName("id")]
        public int Id { get; set; }

        [JsonPropertyName("dlc")]
        public int Dlc { get; set; }

        [JsonPropertyName("signal")]
        public List<Signal> Signals { get; set; }
    }

    public class InputData
    {
        [JsonPropertyName("message1")]
        public Message Message1 { get; set; }

        [JsonPropertyName("message2")]
        public Message Message2 { get; set; }
    }

    public class OutputData
    {
        [JsonPropertyName("message1")]
        public Message Message1 { get; set; }

        [JsonPropertyName("message2")]
        public Message Message2 { get; set; }
    }

    public class Config
    {
        [JsonPropertyName("input_data")]
        public InputData InputData { get; set; }

        [JsonPropertyName("output_data")]
        public OutputData OutputData { get; set; }
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            string path = @"your_path";
            string json_file = File.ReadAllText(path);

            var options = new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true
            };

            var config = JsonSerializer.Deserialize<Config>(json_file, options);
            if (config?.InputData?.Message1 != null)
            {
                Console.WriteLine(config.InputData.Message1.Id);
            }
            else
            {
                Console.WriteLine("Message1 not found in input_data.");
            }
        }
    }

 

 맨 왼쪽에 있는 그림은 JSON 파일의 구조를 계층도로 표시한 것이다. 그리고 해당 계층도를 기반으로 총 5가지 클래스를 만들어 HAS-A의 관계로 만들었다. 즉, JSON 파일의 계층의 개수(5개)와 class의 개수(5개)가 일치해야 한다. 참고로 단순히 파싱하는 것이 목적이라면 Python을 사용하는 것을 추천한다. 해당 이유는 아래의 접은글을 확인 하자.

 

더보기

파이썬은 다음과 같이 코드 몇 줄만 작성하면 딕셔너리 형태로 쉽게 사용할 수 있다.

 

import sys
import json

def parse_json(json_file_path):
    with open(json_file_path, 'r') as file:
        data = json.load(file)
    return data

 

 먼저 읽은 문자열을 C# 객체로 변환한 후, 변환된 객체의 특정 속성에 접근하여 값을 출력하게 코드를 설정하였다. 좀 더 상세히 line-by-line으로 설명하면 다음과 같다.

 

var options = new JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true
};

 

 객체나 데이터 구조를 직렬화(C# → JSON) 혹은 역직렬화(JSON → C#) 할 때의 옵션을 설정하는 변수 options다. 다른 건 따로 설정하지 않았고, JSON 데이터의 속성 이름이 대소문자와 상관없이 C# 클래스의 속성 이름과 매핑되게 설정하였다.

 

var config = JsonSerializer.Deserialize<Config>(json_file, options);

 

 JSON 문자열을 C# 객체로 변환(Deserialize)하는 메서드다. JSON 문자열에 생성된 Config 객체를 받아 config라는 변수에 할당하여 get으로 접근할 수 있게 하였다.

 

if (config?.InputData?.Message1 != null)
{
    Console.WriteLine(config.InputData.Message1.Id);
}
else
{
    Console.WriteLine("Message1 not found in input_data.");
}

 

 JSON을 파싱하고 난 후 항상 null 참조 예외 방지를 위해 if문과 Null 조건부 연산자 '?'를 항상 활용하는 것이 좋다. 이 연산자는 C#에서 널 참조 예외를 방지하고, 객체가 null인지 확인하는 편리한 방법이다. 즉, 위의 코드에서는 config 객체가 널이 아니고, InputData가 널이 아니며, Message1이 널이 아닐 때에만 접근하게 코드를 작성하였다. DLL 파일로 말아서 배포하는 방법은 다음 시간에 알아보도록 하자.

 

총합본

더보기
using System;
using System.IO;
using System.Text.Json;

namespace program
{
    public class Signal
    {
        [JsonPropertyName("name")]
        public string Name { get; set; }

        [JsonPropertyName("start_bit")]
        public int StartBit { get; set; }

        [JsonPropertyName("bit_length")]
        public int BitLength { get; set; }

        [JsonPropertyName("factor")]
        public double Factor { get; set; }

        [JsonPropertyName("offset")]
        public int Offset { get; set; }
    }

    public class Message
    {
        [JsonPropertyName("id")]
        public int Id { get; set; }

        [JsonPropertyName("dlc")]
        public int Dlc { get; set; }

        [JsonPropertyName("signal")]
        public List<Signal> Signals { get; set; }
    }

    public class InputData
    {
        [JsonPropertyName("message1")]
        public Message Message1 { get; set; }

        [JsonPropertyName("message2")]
        public Message Message2 { get; set; }
    }

    public class OutputData
    {
        [JsonPropertyName("message1")]
        public Message Message1 { get; set; }

        [JsonPropertyName("message2")]
        public Message Message2 { get; set; }
    }

    public class Config
    {
        [JsonPropertyName("input_data")]
        public InputData InputData { get; set; }

        [JsonPropertyName("output_data")]
        public OutputData OutputData { get; set; }
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            string path = @"your_path";
            string json_file = File.ReadAllText(path);

            var options = new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true
            };

            var config = JsonSerializer.Deserialize<Config>(json_file, options);
            if (config?.InputData?.Message1 != null)
            {
                Console.WriteLine(config.InputData.Message1.Id);
            }
            else
            {
                Console.WriteLine("Message1 not found in input_data.");
            }
        }
    }
}
반응형

'Language > C#' 카테고리의 다른 글

[C#] 타입(1) 값 형식의 변수  (0) 2024.08.06
[C#] C# 개요 및 입출력  (0) 2024.08.05
[C#] WinForm Console 띄우기  (0) 2024.07.10
[C#] 반응형 GUI  (0) 2024.07.07