JSON のソート

Python での JSON のソートサンプル

data.json

{
  "title": "サンプル",
  "records":
   [
     {"item": "item 1", "point": 4.63 , "date":"2020-02-02" , "memo":"" },
     {"item": "item 2", "point": 74.68, "date":"2020-02-01" , "memo":"" },
     {"item": "item 3", "point": 9.38,  "date":"2020-02-05" , "memo":null },
     {"item": "item 4", "point": 5.78,  "date":"2020-02-14" , "memo":"" },
     {"item": "item 5", "point": 5.53,  "date":"2020-02-07" , "memo":"" }
   ]
}

sorted を使用してソートする。

# -*- coding: UTF-8 -*-
import json

with open('data.json', 'r', encoding='utf-8') as f:
    # JSONファイル→辞書
    data = json.load(f)
    data['records'] = sorted(data['records'], key=lambda k: k['point'], reverse=True)
    str = json.dumps(data, indent=4, skipkeys=True, ensure_ascii=False)
    print(str)

日付の昇順ソート

    data['records'] = sorted(data['records'], key=lambda k: k['date'], reverse=False)

memo のソート、
null がある場合、そのままではソートできないので、'' 空文字でソートさせる必要がある。

    data['records'] = sorted(data['records'], key=lambda k:  '' if k['memo']==None else k['memo'], reverse=False)

↑ memo のソートの結果は、以下のようになる。

{
    "title": "サンプル",
    "records": [
        {
            "item": "item 3",
            "point": 9.38,
            "date": "2020-02-05",
            "memo": null
        },
        {
            "item": "item 1",
            "point": 4.63,
            "date": "2020-02-02",
            "memo": ""
        },
        {
            "item": "item 4",
            "point": 5.78,
            "date": "2020-02-14",
            "memo": ""
        },
        {
            "item": "item 2",
            "point": 74.68,
            "date": "2020-02-01",
            "memo": ""
        },
        {
            "item": "item 5",
            "point": 5.53,
            "date": "2020-02-07",
            "memo": ""
        }
    ]
}

BATファイルへのドラッグ&ドロップ

なぜか、気がつかなった。。。
BATソース内、
変数名 %1 , %2 , %3 , .....

BATファイルアイコンに、Window で選択したファイルをドラッグ&ドロップすると
ドロップしたファイルパスが渡る。

以下のように、BATをを書いてみればわかる。

@echo off
set f1=%1
echo %f1%
pause

では、複数ファイルを選択したファイルをドラッグ&ドロップしたのは、渡せるのか?

@echo off

set f1=%1
set f2=%2
set f3=%3

echo %f1%
echo %f2%
echo %f3%

pause

渡せる!ただし、この選択した順番どおりに、%1, %2 , %3 と並ばなかった!

去年、作ったツールのBATファイルも、この方法で、BATを書けば良かったので修正。
Home · yipuran/daria Wiki · GitHub

JSONの整形

JavaScript , Python, Java(gson) それぞれのJSON整形

JSON 素材

{"a":1,"b":"ABC","c":[1,2,3],"d":{"A":"x", "e":{ "B":23, "C":45},"f":null}}

JavaScript
JSON.stringify で充分、詳細は、JSON.stringify() - JavaScript | MDN
をよく読むこと。

const data = {"a":1,"b":"ABC","c":[1,2,3],"d":{"A":"x", "e":{ "B":23, "C":45},"f":null}}
pretty = JSON.stringify(data, null, 4)

結果

{
    "a": 1,
    "b": "ABC",
    "c": [
        1,
        2,
        3
    ],
    "d": {
        "A": "x",
        "e": {
            "B": 23,
            "C": 45
        },
        "f": null
    }
}

replacer を指定する。

// 値が文字列なら出力させない置換関数
function replacer(key, value) {
  if (typeof value === 'string') {
    return undefined;
  }
  return value;
}
pretty = JSON.stringify(data, replacer, 4)
console.log(pretty)

結果

{
    "a": 1,
    "c": [
        1,
        2,
        3
    ],
    "d": {
        "e": {
            "B": 23,
            "C": 45
        },
        "f": null
    }
}

Python
json をインポートして、load で辞書として読み込む

# -*- coding: UTF-8 -*-
import json

with open('sample.json', 'r') as f:
    # JSONファイル→辞書
    dict = json.load(f)
    str = json.dumps(dict, indent=4)
    print(str)
    # 書込み
    with open('test.json', 'w') as fw:
        json.dump(dict, fw, indent=4)

Json Value に、日本語がある場合は、json.dump を以下のように、ensure_ascii=False を指定しないと \uxxxx と出力されてしまう。

       json.dump(dict, fw, indent=4, ensure_ascii=False)

Java(Gson)
GsonBuilder で、setPrettyPrinting() を指定して Gsonインスタンスを生成する。

Gson gson = new GsonBuilder().serializeNulls().setPrettyPrinting().create();

JsonReader reader	 = new JsonReader(new StringReader(str));
Map<String, Object> map = gson.fromJson(reader, new TypeToken<Map<String, Object>>(){}.getType());

System.out.println(gson.toJson(map));

Gson#toJson() が生成するJSON文字列は、setPrettyPrinting() を実行している場合、
インデントがデフォルトで、空白2文字固定になっている。
このインデントサイズが、気にいらなければ、以下のように、JsonWriter で、setIndent( インデント文字列 ) を指定するしかない。

StringWriter sw = new StringWriter();
JsonWriter jw = gson.newJsonWriter(sw);
jw.setIndent("    ");
gson.toJson(map, new TypeToken<Map<String, Object>>(){}.getType(), jw);
jw.flush();
jw.close();

System.out.println(sw.toString());

入れ子構造のBean の中の Obejct を取得する(2)

入れ子構造のBean の中の Obejct を取得する(1) - Oboe吹きプログラマの黙示録
の続き。。。

変数の宣言名でなく型を指定して、Bean の中を探索→入れ子構造も探索で指定型のインスタンスを求める。
ただし、探索に指定する型が重複して持っている場合はうまく取得できない。
List や、Map、Collection は指定して取得できない。
配列も指定して取得できない。
という限定ならば、
以下、Function<T, R> を生成するもので、目的を達成できる。

import java.lang.reflect.Field;
import java.util.function.Function;
/**
 * UniqueFieldfinder
 */
@FunctionalInterface
public interface UniqueFieldfinder<T, R>{
   R find(T u);

   @SuppressWarnings("unchecked")
   static <T, R> Function<T, R> of(Class<R> c){
      return t->(R)search(c, t);
   }

   static <T> Object search(Class<T> c, Object o){
      Field[] fs = o.getClass().getDeclaredFields();
      String cname = c.getName();
      for(Field f:fs){
         String typename = f.getType().getName();
         if (typename.startsWith("[")) continue;
         try{
            f.setAccessible(true);
            if (typename.startsWith("java.") || typename.startsWith("jdk.")
               || typename.startsWith("javax.")   || typename.startsWith("org.xml.")
               || typename.startsWith("org.w3c.")|| typename.startsWith("org.omg.")){
               if (cname.equals(f.getType().getName())){
                  return f.get(o);
               }
               continue;
            }
            if (typename.equals("double") || typename.equals("float")
               || typename.equals("long") || typename.equals("short")
               || typename.equals("int") || typename.equals("byte")
               || typename.equals("char") || typename.equals("boolean")){
               if (c.isPrimitive()){
                  return f.get(o);
               }
               continue;
            }
            if (cname.equals(f.getType().getName())){
               return f.get(o);
            }
            Object v = f.get(o);
            if (v==null) continue;
            return search(c, v);
         }catch(IllegalArgumentException e){
         }catch(IllegalAccessException e){
         }
      }
      return null;
   }
}

使用例、
入れ子構造のBean の中の Obejct を取得する(1) - Oboe吹きプログラマの黙示録
のクラスの場合、、

Aunit a = new Aunit();
Cunit c = UniqueFieldfinder.of(Cunit.class).apply(a);

とにかく、Bean や DTO の設計として、あまりにも入れ子構造が深いのは、
本当によくない。

入れ子構造のBean の中の Obejct を取得する(1)

入れ子構造のクラス、例えば以下のようなクラス、
(setterやgetterなどはここでは省略)

public class Aunit{
	private int id;
	private String name;
	private Bunit bunit;
}
public class Bunit{
	private String name;
	private Cunit cunit;
}
public class Cunit{
	public int id ;
	public String name;
}

書いているロジック内、先頭のクラスのインスタンスだけ持っていって、
一番下の階層のクラスインスタンスを抽出するのは、getterメソッドのチェインを書かなくてはならないか、
いちいち1つ1つ宣言とgetter による取得を書かなくてはならない。

フィールド数が多いと見るのも厭になるし、3階層、4階層とネストが深くなる設計をされると、
非難の的であろう。

タイプセーフではないし、配列、リスト、マップでは使えないが、
以下のような簡単なユーティリティを用意する。

( org.yipuran.util.Fieldgetter :
yipuran-core/Fieldgetter.java at master · yipuran/yipuran-core · GitHub
を使ってる。)

import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import org.yipuran.util.Fieldgetter;

/**
 * GenericGetter
 */
public class GenericGetter{
   private List<String> nlist;

   private GenericGetter() {
      nlist = new ArrayList<>();
   }
   public static  GenericGetter of() {
      return new GenericGetter();
   }
   public GenericGetter with(Supplier<String> s) {
      nlist.add(s.get());
      return this;
   }
   public GenericGetter with(String s) {
      nlist.add(s);
      return this;
   }
   @SuppressWarnings("unchecked")
   public <T, R> R get(T t) {
      Object o = t;
      for(String s:nlist) {
         o = Fieldgetter.of(e->s).apply(o);
      }
      return (R)o;
   }
}

ちょっと大胆な名前だけど、サンプルなので、ご勘弁。

Aunit a = new Aunit();
//  Aunit 内をセットした後で、、、

int id = GenericGetter.of()
      .with(()->"bunit")
      .with(()->"cunit")
      .with(()->"id")
      .get(a);

String cname = GenericGetter.of()
      .with("bunit")
      .with("cunit")
      .with("name")
      .get(a);

with 順番で指定しなければならないが、パスとして確実な指定はできる。

今回は、フィールドの宣言名称を参照していく方法で、プロジェクトのコーディング規則、命名規則
影響を受けやすい。

つぎは、フィールドの宣言名でなく、クラス定義、型で参照する方法を考える。。

markdown ファイルをPDFに変換(ATOMで)

ATOM markdown-preview-enhanced でPDF保存する方法もあるが、
直接、md ファイルからPDF に変換するパッケージもある。

https://atom.io/packages/markdown-pdf

ATOMインストールする時のパッケージ検索キーは、markdown-pdf
Ctrl+Shft+C 押下で同じフォルダ内に、拡張子 pdf で作ってくれる。

インストールすると、AROMのメニューに、、、
f:id:posturan:20200105163711j:plain

右端に結果を書いた表のCSVから結果を求める

昨日は、表の最終行に結果を書いた表CSVだったが、右端に置いた表の場合での
プログラムはどうなるか、
f:id:posturan:20191231112948j:plain
とても簡単になる。

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.yipuran.csv.CsvStreamProcessor;
import org.yipuran.csv4j.ParseException;
import org.yipuran.csv4j.ProcessingException;
/**
 * MatrixRight
 */
public class MatrixRight<T>{
   private Map<String, T> map = new HashMap<>();
   public MatrixRight(File file, Function<String, T> resultFunc){
      try(InputStream in = new FileInputStream(file)){
         Csvprocess csvproces = new Csvprocess();
         csvproces.read(new InputStreamReader(in, "MS932"), h->{}, (n, p)->{
            map.put(IntStream.range(1, p.size()-1).boxed()
                     .map(i->p.get(i)).collect(Collectors.joining("_"))
                  , resultFunc.apply(p.get(p.size()-1)));
         });
      }catch(IOException ex){
         ex.printStackTrace();
      }catch(ParseException ex){
         ex.printStackTrace();
      }catch(ProcessingException ex){
         ex.printStackTrace();
      }
   }
   public Optional<T> query(String...ary){
      return Optional.ofNullable(map.get(Arrays.stream(ary).collect(Collectors.joining("_"))));
   }
   public Optional<T> query(List<String> list){
      return Optional.ofNullable(map.get(list.stream().collect(Collectors.joining("_"))));
   }
}

Excel で書いた表をCSVにしてるので、"MS932" 文字コードエイリアスで読込む、
使用例

MatrixRight<Boolean> mr =   new MatrixRight<>(file2, e->Boolean.valueOf(e));
Boolean b = mr.query("Y", "N", "N").get();